c++templatesgccstaticclang

Class template static field initialization different results on GCC and Clang


Consider the following C++ code example:

#include<iostream>

class B {
public:
    int val;

    B(int v):val(v){
        std::cout<< val << "\n";
    }
};

template<typename T>
class A {
public:
    static B b;
};

template<typename T>
B A<T>::b = B(1);

int main() {
    A<int>::b.val;
    return 0;
}

On GCC 11.4.0, build with g++ main.cc -g got output 1.

On Clang 14.0.0, build with clang++ main.cc -g got segfault.

Ubuntu 22.04.4 LTS

Cannot understand the reason of such behavior, would be very grateful for any help.


Solution

  • The program has undefined behavior because you use std::cout without having any guarantee that it is initialized.

    The standard streams like std::cout are not automatically initialized and usable. Instead the header <iostream> behaves as if it declared a global static storage duration variable of type std::ios_base::Init. When a variable of this type is initialized, it will initialize the standard streams and make them usable.

    The initialization of the static storage duration variable A<int>::b is dynamic initialization, because the constructor of B is not constexpr and even if it was constexpr still, because it calls a non-constexpr operator<<. And because it is a non-local variable instantiated from a template, it has unordered dynamic initialization, meaning that its initialization is unordered with any other dynamic initialization of non-local variables.

    Because the initialization of A<int>::b is unordered with initialization of <iostream>'s std::ios_base::Init instance, it may happen prior to the initialization of std::cout.

    To assure that the streams are initialized when you need to use them before main is entered, you need to initialize a std::ios_base::Init instance yourself:

    B(int v):val(v){
        std::ios_base::Init _;
        std::cout<< val << "\n";
    }
    

    Or better, avoid global variables with dynamic initialization before main and instead use local static storage duration variables which are initialized at their first point of use from main. See e.g. the Meyers' singleton pattern.