Search code examples
templatesc++17factoryderived-classcrtp

Why does a derived class from CRTP template not initialise the static variables?


So I'm trying to create a factory based on CRTP. For simplicity, I will just include whatever is relevant here. I got two questions which might be irrelevant to each other, but was stuck trying to find the keywords.

#include <bits/stdc++.h>
using namespace std;

static auto& GetFactoryMap() {
    static vector<string> GLOBAL_FACTORY_MAP;
    return GLOBAL_FACTORY_MAP;
}

template <typename Derived>
struct Registrar {
    static int DoRegister() {
        auto name = typeid(Derived).name();
        GetFactoryMap().emplace_back(name);
        return 1;
    };
    inline static const int _hello = DoRegister();
};

struct Foo : public Registrar<Foo> {};

int main() {
    cout << GetFactoryMap().size();
}

Compiled with x86-64 clang-8.0.0, flag -std=c++17.

QUESTION 1: The program returned 0: https://godbolt.org/z/5c74TePT5. I expected it to print 1 as defining the class Foo should also define a Registrar<Foo>, which in turn will initialise the _hello and call DoRegister().

But when I do this:

int main() {
    cout << Foo::_hello << endl;
    cout << GetFactoryMap().size();
}

Then it did print 1 (the _hello) and 1 (the map size).

QUESTION 2: so I found out how to solve the first issue by dummy-calling _hello inside Registrar constructor.

template <typename Derived>
struct Registrar {
    Registrar() {
        (void)_hello;
    }
    ...
}

But thing is, I still don't know WHY it can fix the issue. And even after I did that, the DoRegister seemed to only get called if I have an empty constructor OR a default constructor OUTSIDE the class definition, as seen by the global map size is only 2: https://godbolt.org/z/Me53q1x86.

What is happening here?


Solution

    1. Implicit instantiation

      Unless a member of a templated class is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program; in particular, the initialization (and any associated side effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

      TL;DR If it's not used, it's not instantiated.

    2. Explicitly-defaulted functions

      A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or needed for constant evaluation ([expr.const]).

      TL;DR =default in the class only declares a thing; if it's not used it's not defined.