Search code examples
c++templatesc++14standardsunused-variables

Can an unused function instantiate a variable template with side-effects according to C++14?


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


Question

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.


Solution

  • 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).