c++initializationlanguage-lawyerstatic-membersin-class-initialization

In-class initialization from static member of the same type


Is the following code valid, e.g. doesn't bring undefined behaviour?

struct S
{
    int i = s.i;
    static S s;
};

S S::s;

int main()
{
    S a;  // a.i = 0
    S::s.i = 42;
    S b;  // b.i = 42
}

As far as I know all variables with static storage duration are zero initialized. Hence s.i is 0 on S::s creation, and all is good. But maybe I'm missing something.


Solution

  • I would argue it's well defined.

    [class.static.data]/6

    Static data members are initialized and destroyed exactly like non-local variables.

    [basic.start.static]/2 (emphasis mine)

    A constant initializer for a variable or temporary object o is an initializer whose full-expression is a constant expression, except that if o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [ Note: Such a class may have a non-trivial destructor.  — end note ] Constant initialization is performed if a variable or temporary object with static or thread storage duration is initialized by a constant initializer for the entity. If constant initialization is not performed, a variable with static storage duration or thread storage duration is zero-initialized. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. All static initialization strongly happens before ([intro.races]) any dynamic initialization. [ Note: The dynamic initialization of non-local variables is described in [basic.start.dynamic]; that of local static variables is described in [stmt.dcl].  — end note ]

    [dcl.init]/6 (emphasis mine)

    To zero-initialize an object or reference of type T means:

    • if T is a scalar type, the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;
    • if T is a (possibly cv-qualified) non-union class type, each non-static data member, each non-virtual base class subobject, and, if the object is not a base class subobject, each virtual base class subobject is zero-initialized and padding is initialized to zero bits;
    • if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized and padding is initialized to zero bits;
    • if T is an array type, each element is zero-initialized;
    • if T is a reference type, no initialization is performed.

    Because int i = s.i; means s.i goes through dynamic initialization, it's guaranteed to be zero initialized beforehand. So when it'll be used to initialize itself later, it's value won't be indeterminate. A 0 is to be expected.