Home > database >  Static assert that method cannot be called from constructor or destructor
Static assert that method cannot be called from constructor or destructor

Time:06-03

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

  • Related