The definition of a static data member of a class template specialization is implicitly instantiated only if it is used in such a way that a definition would be required.
For the class B
you are, unconditionally, defining the default constructor. The default constructor uses the default constructor of Super<B>
to initialize the base, meaning that the definition of the Super<B>::Super()
constructor will be implicitly instantiated. This constructor's definition is odr-using m
in (void)m;
and therefore Super<B>::m
's definition will also be implicitly instantiated.
In the case of class A
, you are not explicitly defining any constructor. The implicit special member functions will be defined only when they are used in such a way that a definition would be required. In the line A a {};
you are calling the implicit default constructor of A
and hence it will be defined. The definition will be calling the default constructor of Super<A>
as before, requiring the Super<A>::m
's definition to be instantiated. Without A a {};
there is nothing in the code requiring a definition of any special member function of A
or the default constructor of Super<A>
or the definition of m
. Therefore none of them will be defined.
In the case of C
, there is no template for which we would need to consider instantiation. C::m
is explicitly defined.
Given that the static data member is defined, it must (generally) be initialized eventually. All of the inline static data members here have dynamic initialization with observable side effects, so the initialization must happen at runtime. It is implementation-defined whether they will be initialized before main
's body starts execution or whether initialization will be deferred upto the first non-initialization odr-use of the inline static data member. (This is meant to allow for dynamic libraries.)
You aren't actually non-initialization odr-using any of the inline static data members, so it is implementation-defined whether they will actually be initialized at all. If the implementation does define the initialization to not be deferred, then all of these inline static data members which have been defined will also be initialized before main
is entered.
The order in which the initializations will happen is indeterminate. The static data members of the class template specializations have unordered initialization, meaning they have no ordering guarantees with any other dynamic initialization. And there is only one static data member which isn't specialized from a template and that one is inline
and therefore only partially ordered, although there is nothing else it could be ordered with.
Actually, there is one additional static storage duration object which will be initialized here, a global variable of type std::ios_base::Init
included through <iostream>
. The initialization of this variable causes the initialization of the standard streams (std::cout
, etc.). Because your inline static data members from the templates have unordered initialization, they will not be ordered with this initialization. Similarly if you had multiple translation units containing C::m
, it would also not be ordered with it. As a consequence you might be using std::cout
before it is initialized, causing undefined behavior. You can cause early initialization of the standard streams by constructing an object of type std::ios_base::Init
:
class X {
public:
X(const char* s) {
[[maybe_unused]] std::ios_base::Init ios_base_init;
std::cout << s << "\n";
};
};
Aside from considerations such as above, the compiler is not allowed to remove static data members if their initialization has observable side effects. Of course the as-if rule still applies as always meaning that the compiler can compile to whatever machine instructions which will result in the same observable behavior as described above.
For practical purposes you should also be careful. There are some compiler flags that are sometimes used for code size optimization which will eliminate dynamic initialization if the variable seems to be unused. (Although that is not standard-conforming behavior.) For example the --gc-sections
linker flag together with GCC's -ffunction-section -fdata-section
can have this effect.
As you can see dynamic initialization of static storage duration objects is kind of complicated in C++. In your case here there are only minor dependency issues, but this can quickly become very messy, which is why it is usually recommended to avoid it as much as possible.
int
member would be just as easy to see in the compiler's asm output. Probably the compiler isn't allowed to omit calling constructors for objects that exist; the rules that allow skipping copy-constructors in some cases are specific to copy construction. Your test case is looking for a change in visible side-effects (cout <<
), not just optimizing away some unreferenced storage. – Adowastatic int
member that gets constructed once but never read (or written?) after that? A [language-lawyer] tag would be appropriate then, because the C++ standard would have to explicitly allow such optimizations, not via the as-if rule. – AdowaC
andSuper
are different cases as templates follow different sequencing rules. – Lampe