Dynamic initialization
Dynamic initialization concerns non-local variables that are not initialized with constexpr
expresssions.
int foo();
int global = foo();
As cppreference explains in quite some details, the compiler has some leeway and can either schedule dynamic initialization with the static one or deffer it as long as it will not change the behavior of the program.
Please treat all initialization with =foo()
as intendent to be dynamic with enough side-effects to create UB for unordered initialization. E.g.:
int foo(){//Pretend to be flexible on the return type
static int i =0;
return ++i;
}
The following behaviour is governed by [basic.start.dynamic], from now on referred as rules:
Dynamic initialization of a non-local variable with static storage duration is unordered
if the variable is an implicitly or explicitly instantiated
specialization, is partially-ordered if the variable is an inline
variable that is not an implicitly or explicitly instantiated
specialization, and otherwise is ordered.
[Note: An explicitly
specialized non-inline static data member or variable template
specialization has ordered initialization. — end note]
A declaration D is appearance-ordered before a declaration E if
- D appears in the same translation unit as E, or
- the translation
unit containing E has an interface dependency on the translation unit
containing D, in either case prior to E.
Dynamic initialization of non-local variables V and W with static storage duration are ordered as follows:
- If V and W have ordered
initialization and the definition of V is appearance-ordered before
the definition of W, or if V has partially-ordered initialization, W
does not have unordered initialization, and for every definition E of
W there exists a definition D of V such that D is appearance-ordered
before E, then
- if the program does not start a thread
([intro.multithread]) other than the main thread ([basic.start.main])
or V and W have ordered initialization and they are defined in the
same translation unit, the initialization of V is sequenced before the
initialization of W;
- otherwise, the initialization of V
strongly happens before the initialization of W.
- Otherwise, if
the program starts a thread other than the main thread before either V
or W is initialized, it is unspecified in which threads the
initializations of V and W occur; the initializations are unsequenced
if they occur in the same thread.
- Otherwise, the initializations
of V and W are indeterminately sequenced.
[Note: This definition
permits initialization of a sequence of ordered variables concurrently
with another sequence. — end note]
...
The rest of the section deals with sequencing the initialization with the main and other threads.
Unordered dynamic initialization
The first rule is relevant. Note that explicitly instantiated specialization is different from explicit specialization:
template<typename T>
struct A{
static int x = foo();
}
// Rules are the same as for non-templates
template<>
struct A<char>{
//C++17 inline definition
inline static int x = foo();
// Only declaration, must be defined in some translation unit
static int y;
}
//Explicit instantion of `A` class template's `double` specialization.
template class A<double>;
tempalte<> struct A<char>::y=foo();
int main(){
// Implicit instantiation of `A` class template's `int` specialization.
A<int> v1;
// Implicit instantiation of `A` class template's explicit `int` specialization.
A<char> va2;
}
Order:
A<int>::x, A<double>::x, A<char>::y
are unordered with respect to all other dynamic initialization, including themselves.
A<char>::y
is ordered with other ordered variables in the same translation unit it is defined in. It is NOT ordered w.r.t the ^ three variables (because they are unordered).
The same rules are for variable templates.
Partially ordered initialization
The first rule says the partial order applies to C++17 inline
static variables in non-templated classes. The second and third rules define the order. Cppreference summs it up pretty nicely.
[Side note
C++17 inline
attribute for static member variables allows to define and initialize the variables immediately in the class definition and not require the programmer to pick a favourite translation unit(.cpp) in which they would have to place the definition separately. This lead to countless SO questions where the author forgot to do just that. Originally, it was this way to enforce One Definition Rul, but the example above already allows to "break" it with templates. In this case, the compiler must define the A<T>::x
correctly, no matter in how many TUs is A<T>
instantiated for the same T
. Hence C++17 allows the same "violation" for non-templates. It is the responsibility of the compiler (rather linker though) to deal with more than one source-code-identical inline static member variable definition. ]
Let's have this example:
class B{
inline static int b = foo();
};
class C{
inline static int c = foo();
};
The initialization of a,b
is ordered only if in all translation units where both class definitions appear, they appear in the same order.
Furthermore, the third rule establishes the order with other ordered variables as the same order in which they appear in the source code:
#include "B.h"
static int global = foo();
#include "C.h"
If there are multiple TUs with such structure, the order of initialization should be:
B::b
- All
global
variables in undefined order.
C::c
Thinking about it, I believe this forces a C++ compiler to create a directed graph with "appear before" relationship, check if it has topological ordering and initialize the variables in such ordering.
Ordered initialization
Applies to all other non-local variables:
- non-inline static members,
- "ordinary" global static variables.
I.e. exactly the variables governed by ODR meaning the initialization must appear only in one translation unit.
- They are initialized w.r.t to other varibles in the same TU exactly in the order they appear in the source code.
- They are unordered w.r.t to variables from other TUs.