Here is my code:
#include <iostream>
class MyBaseClass
{
public:
static int StaticInt;
};
int MyBaseClass::StaticInt = 0;
template <int N> class MyClassT : public MyBaseClass
{
public:
MyClassT()
{
StaticInt = N;
};
};
template <int N> static MyClassT<N> AnchorObjT = {};
class UserClass
{
friend void fn()
{
std::cout << "in fn()" << std::endl; //this never runs
(void)AnchorObjT<123>;
};
};
int main()
{
std::cout << MyBaseClass::StaticInt << std::endl;
return 0;
}
The output is:
123
...indicating MyClassT()
constructor was called despite that fn()
was never called.
Tested on gcc
and clang
with -O0
, -O3
, -Os
and even -Ofast
Does this program have undefined behavior according to C++ standard?
In other words: if later versions of compilers manage to detect that fn()
will never be called can they optimize away template instantiation together with running the constructor?
Can this code somehow be made deterministic i.e. force the constructor to run - without referencing function name fn
or the template parameter value 123
outside of the UserClass
?
UPDATE: A moderator truncated my question and suggested further truncation. Original verbose version can be viewed here.
Template instantiation is a function of the code, not a function of any kind of dynamic runtime conditions. As a simplistic example:
template <typename T> void bar();
void foo(bool b) {
if (b) {
bar<int>();
} else {
bar<double>();
}
}
Both bar<int>
and bar<double>
are instantiated here, even if foo
is never invoked or even if foo
is only ever invoked with true
.
For variable template, specifically, the rule is [temp.inst]/6:
Unless a variable template specialization has been explicitly instantiated or explicitly specialized, the variable template specialization is implicitly instantiated when it is referenced in a context that requires a variable definition to exist or if the existence of the definition affects the semantics of the program.
In your function:
friend void fn() { (void)AnchorObjT<123>; };
AnchorObjT<123>
is referenced in a context that requires a definition (regardless of whether fn()
is ever called or even, in this case, it is even possible to call), hence it is instantiated.
But AnchorObjT<123>
is a global variable, so its instantiation means we have an object that is constructed before main()
- by the time we enter main()
, AnchorObjT<123>
's constructor will have been run, setting StaticInt
to 123
. Note that we do not need to actually run fn()
to invoke this constructor - fn()
's role here is just to instantiate the variable template, its constructor is invoked elsewhere.
Printing 123 is the correct, expected behavior.
Note that while the language requires the global object AnchorObjT<123>
to exist, the linker may still the object because there is no reference to it. Assuming your real program does more with this object, if you need it to exist, you may need to do more to it to prevent the linker from removing it (e.g. gcc has the used
attribute).