C++ Static Member Initialization (Template Fun Inside)

C++ Static member initialization (template fun inside)

This was discussed on usenet some time ago, while i was trying to answer another question on stackoverflow: Point of Instantiation of Static Data Members. I think it's worth reducing the test-case, and considering each scenario in isolation, so let's look at it more general first:


struct C { C(int n) { printf("%d\n", n); } };

template<int N>
struct A {
static C c;
};

template<int N>
C A<N>::c(N);

A<1> a; // implicit instantiation of A<1> and 2
A<2> b;

You have the definition of a static data member template. This does not yet create any data members, because of 14.7.1:

"... in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist."

The definition of something (= entity) is needed when that entity is "used", according to the one definition rule which defines that word (at 3.2/2). In particular, if all references are from uninstantiated templates, members of a template or a sizeof expressions or similar things that don't "use" the entity (since they are either not potentially evaluating it, or they just don't exist yet as functions/member functions that are itself used), such a static data member is not instantiated.

An implicit instantiation by 14.7.1/7 instantiates declarations of static data members - that is to say, it will instantiate any template needed to process that declaration. It won't, however, instantiate definitions - that is to say, initializers are not instantiated and constructors of the type of that static data member are not implicitly defined (marked as used).

That all means, the above code will output nothing yet. Let's cause implicit instantiations of the static data members now.

int main() { 
A<1>::c; // reference them
A<2>::c;
}

This will cause the two static data members to exist, but the question is - how is the order of initialization? On a simple read, one might think that 3.6.2/1 applies, which says (emphasis by me):

"Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit."

Now as said in the usenet post and explained in this defect report, these static data members are not defined in a translation unit, but they are instantiated in a instantiation unit, as explained at 2.1/1:

Each translated translation unit is examined to produce a list of required instantiations. [Note: this may include instantiations which have been explicitly requested (14.7.2). ] The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. [Note: an implementation could encode sufficient information into the translated translation unit so as to ensure the source is not required here. ] All the required instantiations are performed to produce instantiation units. [Note: these are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. ] The program is ill-formed if any instantiation fails.

The Point of Instantiation of such a member also does not really matter, because such a point of instantiation is the context link between an instantiation and its translation units - it defines the declarations that are visible (as specified at 14.6.4.1, and each of those point of instantiations must give instantiations the same meaning, as specified in the one definition rule at 3.2/5, last bullet).

If we want ordered initialization, we have to arrange so we don't mess with instantiations, but with explicit declarations - this is the area of explicit specializations, as these are not really different to normal declarations. In fact, C++0x changed its wording of 3.6.2 to the following:

Dynamic initialization of a non-local object with static storage duration is either ordered or unordered.
Definitions of explicitly specialized class template static data members have ordered initialization. Other
class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization.


This means to your code, that:

  • [1] and [2] commented: No reference to the static data members exist, so their definitions (and also not their declarations, since there is no need for instantiation of B<int>) are not instantiated. No side effect occurs.
  • [1] uncommented: B<int>::getB() is used, which in itself uses B<int>::mB, which requires that static member to exist. The string is initialized prior to main (at any case before that statement, as part of initializing non-local objects). Nothing uses B<int>::mInit, so it's not instantiated, and so no object of B<int>::InitHelper is ever created, which makes its constructor not being used, which in turn will never assign something to B<int>::mB: You will just output an empty string.
  • [1] and [2] uncommented: That this worked for you is luck (or the opposite :)). There is no requirement for a particular order of initialization calls, as explained above. It might work on VC++, fail on GCC and work on clang. We don't know.
  • [1] commented, [2] uncommented: Same problem - again, both static data members are used: B<int>::mInit is used by B<int>::getHelper, and the instantiation of B<int>::mInit will cause its constructor to be instantiated, which will use B<int>::mB - but for your compiler, the order is different in this particular run (unspecified behavior is not required to be consistent among different runs): It initializes B<int>::mInit first, which will operate on a not-yet-constructed string object.

Template class's static variable initialization, c++

Compiler will remove duplicate template instantiations on its own. If you turn your template class into regular one, then its your duty to make sure only one definition of static variable exists (otherwise linker error will appear). Also remember that static data members are not shared between instatiations of templates for different types. With c++11 you can control instatiations on your own using extern templates: using extern template (C++11).

As for the point of instatiation for static members:

14.6.4.1 Point of instantiation [temp.point]
1 For a function template specialization, a member function template specialization, or a specialization for a
member function or static data member of a class template, if the specialization is implicitly instantiated
because it is referenced from within another template specialization and the context from which it is referenced
depends on a template parameter, the point of instantiation of the specialization is the point of
instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition that refers to the specialization.

so point of instatiation should be ie. right after main() if you use your type for the first time inside main().

C++ static variable initialization inside a template function

The literal "one" is a const char [4].

this code:

test("one")

would ideally like to call test(const char (&)[4])

This works for test(const T&) (because const char (&) [4] can bind to const char (const&) [4]).

But it cannot work for test(T t) because you can't pass string literals by value. They are passed by reference.

However, const char[4] can decay to const char*, which can match template<class T> void func(T t).

The proof is in the pudding:

#include <cstdint>
#include <iostream>
#include <typeinfo>

template <typename T, std::size_t N>
void test_const(const T(&t)[N])
{
std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << " and N is " << N << std::endl;
}

template <typename T>
void test_mutable(T &t)
{
std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

template <typename T>
void test_const_ref(const T &t)
{
std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

template <typename T>
void test_copy(T t)
{
std::cout << __func__ << " for literal " << t << " T is a " << typeid(T).name() << std::endl;
}

int main()
{
test_const("one");
test_const("three");
test_mutable("one");
test_mutable("three");
test_const_ref("one");
test_const_ref("three");
test_copy("one");
test_copy("three");
}

example results (clang):

test_const for literal one T is a c and N is 4
test_const for literal three T is a c and N is 6
test_mutable for literal one T is a A4_c
test_mutable for literal three T is a A6_c
test_const_ref for literal one T is a A4_c
test_const_ref for literal three T is a A6_c
test_copy for literal one T is a PKc
test_copy for literal three T is a PKc

Here is a version with demangled names (will compile on clang and gcc):

#include <cstdint>
#include <iostream>
#include <typeinfo>
#include <cstdlib>
#include <cxxabi.h>

std::string demangle(const char* name)
{
int status = -1;
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};

return (status==0) ? res.get() : name ;
}

template <typename T, std::size_t N>
void test_const(const T(&t)[N])
{
std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << " and N is " << N << std::endl;
}

template <typename T>
void test_mutable(T &t)
{
std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

template <typename T>
void test_const_ref(const T &t)
{
std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

template <typename T>
void test_copy(T t)
{
std::cout << __func__ << " for literal " << t << " T is a " << demangle(typeid(T).name()) << std::endl;
}

int main()
{
test_const("one");
test_const("three");
test_mutable("one");
test_mutable("three");
test_const_ref("one");
test_const_ref("three");
test_copy("one");
test_copy("three");
}

expected output:

test_const for literal one T is a char and N is 4
test_const for literal three T is a char and N is 6
test_mutable for literal one T is a char [4]
test_mutable for literal three T is a char [6]
test_const_ref for literal one T is a char [4]
test_const_ref for literal three T is a char [6]
test_copy for literal one T is a char const*
test_copy for literal three T is a char const*

Template static members initialization order

This code is safe because Foo<double>::x has constant initialization, but Foo<double>::y has dynamic initialization.

3.6.2/2:

Constant initialization is performed:

  • ...

  • if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.

Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.

On the other hand, if you had:

double tmp = 1.1;

template <typename T>
T Foo<T>::x = tmp;

template <typename T>
T Foo<T>::y = 2.0 * Foo<T>::x;

that code would not be "safe" - Foo<double>::y could end up being either 2.2 or 0.0 (assuming nothing else modifies tmp during dynamic initializations).

Initialization of a static member inside a template

From [basic.start.init]:

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5)
before any other initialization takes place. A constant initializer for an object o is an expression that is a
constant expression, except that it may also invoke constexpr constructors for o and its subobjects even
if those objects are of non-literal class types. [ ... ]

Together, zero-initialization and constant initialization are called static initialization; all other initialization is
dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.

In our case, b is statically initialized but b.x is dynamically initialized (the constructor isn't constexpr). But we also have:

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage
duration is done before the first statement of main. If the initialization is deferred to some point in time
after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable
defined in the same translation unit as the variable to be initialized.

Odr-used means, from [basic.def.odr]:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying
the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any nontrivial
functions and, if [ ... ]

But evaluating b.x does not yield a constant expression, so we can stop there - b.x is odr-used by A<N>::foo(), which is also the first odr-use. So while the initialization does not have to occur before main(), it does have to occur before foo(). So if you get 0, that's a compiler error.

Non-trivially initializing static member of template class in C++11 without clang warnings

You have to put:

template<> const MathFoo MathFoo::mkFooConst;
// Declaration only, mkFooConst{} would be a definition.

Demo with multiple file

No warning with clang

C++ static member reinitialized after initialization

You are accessing a static object (in this case StaticUtilityBase::all) from the constructor of a different static object (in this case template <typename T> StaticUtility<T> HasUtility<T>::utility;).

As there is no ordering guarantee between initialisation of different static objects across translations units what you are trying to do won't work.

See https://en.cppreference.com/w/cpp/language/siof for more information.

And easy way to verify this is to add a second static object to StaticUtilityBase which prints out when it gets constructed:
static int printer = []()->int{std::cout << "Initing StaticUtilityBase\n"; return 0;}()

Initializing a C++ static member that stores a lambda function

To save place, you might redesign to something like:

template <typename F>
struct A {
F f;
std::vector<int>* v;
A(F f, std::vector<int>* v) : f(f), v(v) {}
int g(int i, int x) { return f(f(x)) + v[i]; }
};

nt main() {
int p, q; std::cin >> p >> q;
auto f1 = [&](int x) -> int { return p * x + q; };
auto f2 = [&](int x) -> int { return p * x - q; };
std::vector<int> v;
for (int i = 0; i < 10'000; i++) {
int k; std::cin >> k;
v.emplace_back(k);
}
A<decltype(f1)> a1(f1, &v);
A<decltype(f2)> a2(f2, &v);
// ...
}

So you store each values f1, f2 and ks only once.

Whereas you store 10.000 time f1, f2 and each ks twice.

It might be a little less convenient to use. There are generally a trade-of between the different kind of optimizations (memory/speed) themselves, safety and readability/simplicity.

Order of initialization of static member of explicitly instantiated template and another static variable

[basic.start.dynamic]:

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 1: An explicitly specialized non-inline static data member or variable template specialization has ordered initialization. — end note]

as standard points out, the initialization of v_ is unordered.

but always we can solve this problem by static function:

template<typename T>
struct X{
static std::vector<T>& v_() noexcept {
static std::vector<T> v;
return v;
}
};
template struct X<int>;
static bool init(){
X<int>::v_().reserve(1000);
// ...
return true;
}
static bool initialized = init();

Class template static data-member definition/declaration/initialization

You hit a bug in MSVC. It has apparently been fixed in Visual Studio 2019 version 16.5 Preview 2.

As an alternative workaround, you could leave your definitions in the header and mark them as inline (since c++17):

template<> 
inline std::array<float,1U> A<1U>::a1{1.};

template<>
inline std::array<float,2U> A<2U>::a1{0.3,0.3};


Related Topics



Leave a reply



Submit