This question is on how to use global variable initialization to achieve some side effect before main()
is run (e.g., factory class registration before main()
). This can be tricky when template is involved. Consider the following piece of code:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
template <bool&>
struct Dummy {};
// Important: Do not delete.
// This line forces the instantiation and initialization of `flag`.
using DummyType = Dummy<flag>;
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is instantiated and.
// A<int>::do_something will be called before main to initialize A<int>::flag.
class B : A<int> {};
If we remove the using
, the behavior will be different:
template <class T>
class A {
public:
static bool flag;
static bool do_something() {
// Side effect: do something here.
}
// The `using` dummy part is missing.
};
template <class T>
bool A<T>::flag = A<T>::do_something();
// A<int>::flag is not instantiated.
// A<int>::do_something will not be called.
class B : A<int> {};
My question: is the using
trick here a compiler specific thing, or is it backed up by the C++ standard? I did some search and found some relevant information about the standard 14.7.1, but still not sure about the rules on using
as it's not mentioned in 14.7.1 (my source is this one, not sure if it should be considered as the ground truth). Also, the using Dummy
seems somewhat hacky to me. Are there any other workarounds to make it more elegant? The goal is to resolve the instantiation within class A
itself so that the side effect related code segments remain private.
Thanks to new wording in C++20, it is possible to give a definitive answer to your question. In the comments, Igor Tandetnik mentioned the relevant paragraph in C++17, namely [temp.inst]/3. In C++20, it is [temp.inst]/4, which has some new relevant wording that I have bolded:
Later in the same section, paragraph 8:
In short, if the alias-declaration
using DummyType = Dummy<flag>;
is instantiated, then the static data memberflag
is needed for constant evaluation, therefore it is instantiated.Informally, "needed for constant evaluation" refers to certain situations where a variable or function is not odr-used, but the way it is used in constant evaluation still requires its definition.
Formally, the definition of "needed for constant evaluation" is given in [expr.const]/15:
"Manifestly constant-evaluated" is defined in p14 of that section:
"constant-expression" is a grammar production that is used in various contexts to denote a conditional-expression that must be a constant expression. We can check the grammar for simple-template-id (which is what
Dummy<flag>
is):Here, id-expression refers to a template template parameter. In
using DummyType = Dummy<flag>;
,flag
is an argument for a non-type template parameter, so it is a constant-expression.Since the expression
flag
is a manifestly constant-evaluated expression, it is a potentially constant-evaluated expression. The variableflag
's name "appears as a potentially constant evaluated expression" and it's a variable of reference type, so it is needed for constant evaluation.Note that the concept of "needed for constant evaluation" was introduced by P0589 to resolve CWG 1581. This was voted as a DR, so its resolution is retroactive. The concepts of "needed for constant evaluation" and "manifestly constant-evaluated" always needed to be in C++; their absence led to issues like the one in this question.