Search code examples
c++staticcrtp

CRTP static variable not initialized without constructor


I was trying to write a factory that types could register to using CRTP, like this question, and I was having some issues.

#include <iostream>
#include <map>
#include <functional>


class A
{
public:
        virtual ~A() = default;
        virtual void junk() = 0;
};

class Factory
{
public:
        static A* get(const std::string& name)
        {
                return map()[name]();
        }

        template<typename T>
        static bool reg()
        {
                map()[T::asdf] = [](){ return new T(); };
                return true;
        }

        static std::map<std::string, std::function<A*()>>& map()
        {
                static std::map<std::string, std::function<A*()>> m;
                return m;
        }
};

template<typename T>
class AutoReg : public A
{
public:
        AutoReg(){ std::ignore = registered; }
        static inline bool registered = Factory::reg<T>();
};

class B : public AutoReg<B>
{
public:
        //B(){}
        void junk() override { std::cout << "B" << std::endl; }
        static const inline std::string asdf = "asdf";
};

int main(int argc, char** argv)
{
        Factory::get("asdf")->junk();
        return 0;
}

The code above (yes, I'm aware it leaks memory) fails when the constructor of B is commented out, but works when the constructor of B is uncommented. My understanding is that the default constructor of B should call the default constructor of AutoReg<B> and it shouldn't matter if I define the constructor myself or not. But for some reason, AutoReg<B>::registered never gets initialized if I don't manually define a constructor for B (B() = default; also fails). Why is this the case?


Solution

  • According to https://en.cppreference.com/w/cpp/language/default_constructor:

    If the constructor is implicitly-declared(until C++11)the implicitly-declared or explicitly-defaulted default constructor is not defined as deleted(since C++11), it is defined (that is, a function body is generated and compiled) by the compiler if odr-used or needed for constant evaluation(since C++11), and it has the same effect as a user-defined constructor with empty body and empty initializer list.

    Since B's constructor is never odr-used, it doesn't get generated, so AutoReg<B>'s constructor is never odr-used, and AutoReg<B>::registered never gets odr-used, and so it never gets initialized or registered to the factory.