Suppose I have the following classes:
template <typename SELF>
class Base {
protected:
template <int X>
void foo();
};
class Child : public Base<Child> {
public:
Child() {
//I want this to throw a static assertion failure
foo<10>();
}
~Child() {
//Should also static assert here
}
};
Is there any way I make the compiler throw a compile-time assertion failure if the templated member method foo() is called within the constructor or destructor? In other words, I want to prevent, at compile time, the method from being called within the constructor or destructor.
Please don't ask why foo is a template; this is just a simplified example of a more complex project.
CodePudding user response:
This solution works only with C 20, only for destructor, and it is not compile time. I also not sure that it works everywhere. I don't recommend to use it in production code.
#include <iostream>
#include <source_location>
#include <cassert>
template <typename SELF>
class Base {
protected:
template <int X>
void foo(std::source_location location = std::source_location::current())
{
assert(std::string_view(location.function_name()).find("::~") == std::string::npos);
std::cout << location.function_name() << '\n';
}
};
class Child : public Base<Child> {
public:
Child() {
//I want this to throw a static assertion failure
foo<10>();
}
~Child() {
//Should also static assert here
foo<10>();
}
};
int main() {
Child child;
return 0;
}
CodePudding user response:
While the OP has acknowledged in the comments that the whole endeavour is probably misguided in their case, the question remains an interesting puzzle.
Performing such a check at runtime is feasible, but it needs to involve code that runs after Child
during construction, and code that runs before Child
during destruction.
One way to do this would to sandwich Child
between Base
and some other utility class. For example:
#include <atomic>
#include <cassert>
template<typename SELF>
struct Activator final : SELF {
#ifndef NDEBUG
Activator() {
SELF* self = static_cast<SELF*>(this);
self->alive_state_ = true;
}
~Activator() {
SELF* self = static_cast<SELF*>(this);
self->alive_state_= false;
}
#endif
};
template <typename SELF>
class Base {
#ifndef NDEBUG
friend class Activator<SELF>;
std::atomic<bool> alive_state_= false;
#endif
public:
Base() {}
protected:
template <int X>
void foo() {
assert(alive_state_);
}
};
class ChildImpl : public Base<ChildImpl> {
public:
ChildImpl() {
// fails at runtime
//foo<10>();
}
~ChildImpl() {
// fails at runtime
//foo<10>();
}
void bar() {
foo<10>();
}
};
using Child = Activator<ChildImpl>;
int main() {
Child child;
child.bar();
return 0;
}
This will correctly catch any and invocation of foo<>()
during construction/destruction, no matter how convoluted the indirection to the call is.
You can check it out on godbolt: https://gcc.godbolt.org/z/szo6Mhef4