0

I'm trying to have a better understanding about the initialization of static members in templated classes / structs.

Let's take the following minimal example (available in Coliru):

#include <iostream>

struct A {
  int value;
  A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};

struct Static {
  static inline A s_a{42};
  
  static void use() {
    std::cout << s_a.value++ << '\n';
    std::cout << s_a.value << '\n';
  }
};

struct User {
   User() {
    Static::use();
  }
};

User s_user{};

int main() {
  return 0;
}

The output is (as I should expect):

A(42)
42
43

Now, I'd like to be able to have several versions of Static for different types, so I convert it into a template (Coliru):

#include <iostream>

struct A {
  int value;
  A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};

template<class T>
struct Static {
  static inline A s_a{42};
  
  static void use() {
    std::cout << s_a.value++ << '\n';
    std::cout << s_a.value << '\n';
  }
};

struct User {
   User() {
    Static<int>::use();
  }
};

User s_user{};

int main() {
  return 0;
}

but then the output is

0
1
A(42)

As you can see, A::value is being used uninitialized, and A's constructor is called after Static<int>::use has been executed.

I can change the definition of the s_a so it is specialized in advanced:

template<class T>
struct Static {
  static A s_a;
  // ...
};

template<> A Static<int>::s_a{42};

and it works correctly (Coliru). The counterpart is that I have to define every single specialization and I'd like to make it as easier to use as possible (as you can suppose actual code is more complex).

So, my questions are:

  1. Why is Static<T>::use() using s_a before it (s_a) is initialized? I can think about some kind of delayed initialization until specialization (sorry, don't know the correct name for it), and that User< is calling a static function that, somehow, is not related to the static attribute (only that they are in the same encapsulation), but I don't know exactly why nor what the standard has to say about it.

  2. How should I change this code to get the same behavior as the non-templated version? It is, that the attributes of any Static<T> can be initialized before their static methods are called.

Note: struct A is just a minimum example here, in the project I'm working on it is a templated class that is specialized based on Static<T>.

cbuchart
  • 10,847
  • 9
  • 53
  • 93

1 Answers1

2

Static initialization in C++ is a nightmare... Draft n4659 for C++17 says in 6.6.3 Dynamic initialization of non-local variables[basic.start.dynamic] ยง1

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.

When the Static class is not templated, you have a partially-ordered intialization to guarantee the initialization of Static::s_a to happen before the initialization of s_user. But when you use templates, the initialization becomes unordered and the implementation can choose what it wants. In my tests, gcc 10 gives same results as what you have got (s_users initialized before Static::s_a), but clang 12 gives the opposite order...

You are lucky to have used gcc and seen the problem. Had you used Clang, you would have proceed at dev time and problem could have happened later. The worse here, is that I am not sure whether explicit instanciation really guarantees the initialization order. If I have correctly understood the standard, I would say no and anyway, I would never rely on that for production code.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thanks! So, the conclusion is not to rely on this. What can I do then? All I can think about is a singleton pattern, which though not statically linked, will offer the _global-like_ visibility of the static version, and as the member is no longer static, we can be sure it is correctly initialized _before_ it is used. Right? I've made a test [here](https://coliru.stacked-crooked.com/a/1bae027340bc0053) and in VS, both working correctly, but I'd like to know your thoughts. Thanks! โ€“ cbuchart Jun 04 '21 at 06:31
  • 1
    @cbuchart: The best references I can give you come from that other [SO question](https://stackoverflow.com/q/29822181/3545273). C++ hates static initialization, and the recommended pattern is "construct of first use". If you use that on your singleton pattern, then you will be safe... โ€“ Serge Ballesta Jun 04 '21 at 07:10
  • Thanks Serge! and yes, the singleton pattern basically is doing that precisely: ensures the static object is created before first used. โ€“ cbuchart Jun 04 '21 at 08:15