Search code examples
c++c++11initializationstatic-membersstatic-order-fiasco

Initialization order guarantees for inline-initialized static const class member


Say I have a static const int class member variable. It is initialized directly in the class definition, but it doesn't have a definition in a .cpp (which is ok since it's not odr-used).

Further, say this constant is used in the constructor initializer list of another class, and a global instance is created of that other class.

// mytype1.hpp
class MyType1
{
public:
    static const int g_myConstant = 42; // no definition in cpp
};

// mytype2.hpp
class MyType2
{
public:
    MyType2();
private:
    int m_myMember;
};

// mytype2.cpp
MyType2::MyType2()
    : m_myMember(MyType1::g_myConstant)
{
}

// otherfile.cpp
// is the reference to MyType1::g_myConstant in the ctor well defined?
MyType2 myType2GlobalInstance;

Is the construction of myType2GlobalInstance well defined? In other words: What guarantees does C++ make about static initialization order of static const class member variables?

Since there is no definition of that constant, there is probably no memory for it that needs to be initialized, and the variable behaves more like a preprocessor macro.. but is this guaranteed? Does it make a difference if the constant is defined or not?

Does it change anything if the member variable is static constexpr?


Solution

  • For static initialization, until C++14, zero initialization happens before constant initialization. Since C++14, constant initialization happens instead of zero initialization. Constant initialization basically happens for objects and references that are value-initialized by constant expressions (or constexpr constructors). Details here.

    The compiler is permitted to initialize other static objects using constant initialization, if it can guarantee that the value would be the same as if the standard order of initialization was followed. In practice, constant initialization is performed at compile time and pre-calculated object representations are stored as part of the program image. If the compiler doesn't do that, it still has to guarantee that this initialization happens before any dynamic initialization.

    m_myMember is not static initialized, because its class constructor is not constexpr. g_myConstant is constant initialized. Using static constexpr forces the compiler to initialize the value at compiler time, without constexpr, the compiler may initialize at compile time. Your construction is well-defined.

    Look this example from cppreference:

    #include <iostream>
    #include <array>
    
    struct S {
        static const int c;
    };
    const int d = 10 * S::c; // not a constant expression: S::c has no preceding
                             // initializer, this initialization happens after const
    const int S::c = 5;      // constant initialization, guaranteed to happen first
    int main()
    {
        std::cout << "d = " << d << '\n';
        std::array<int, S::c> a1; // OK: S::c is a constant expression
    //  std::array<int, d> a2;    // error: d is not a constant expression
    }
    

    But now, if we change init order, it compiles:

    #include <iostream>
    #include <array>
    
    struct S {
        static const int c;
    };
    //both inits are now constant initialization
    const int S::c = 5;
    const int d = 10 * S::c;
    int main()
    {
        std::cout << "d = " << d << '\n';
        std::array<int, S::c> a1;
        std::array<int, d> a2; //now, it's ok
    }