Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable templates and std::cout -- order of construction

It looks like we can safely use std::cout object in constructors of objects with static storage duration as stated in this question.

However, I'm not entirely sure that we can safely use them in case of variable templates:

#include <iostream>

template<class T>
T x = T{};

void foo()
{
    class Test
    {
    public:
        Test() { std::cout << "Test::Test\n"; }
    };

    Test t = x<Test>;
}


int main()
{
    std::cout << "main\n";
}

This code crashes in clang (live example) and I'm not sure whether it's a bug or not.

like image 521
FrozenHeart Avatar asked Jun 08 '16 14:06

FrozenHeart


1 Answers

As explained in that question, one effect of

#include <iostream>

is the equivalent of defining a global variable

static std::ios_base::Init __init;

which (assuming that you include it at the start of the TU) guarantees that for all static storage duration objects with ordered initialization in the same TU, the stream objects have been set up.

However, explicitly and implicitly instantiated template specializations have unordered initialization ([basic.start.dynamic]/1)1:

Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, and otherwise is ordered [note omitted]. Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit.

And since

If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization. Otherwise, the unordered initialization of a variable is indeterminately sequenced with respect to every other dynamic initialization.

there's no guarantee that the stream objects have been initialized at the time the variable template specialization x<Test> is initialized.

In this case, as one of the possible executions results in undefined behavior (using the stream objects before they are initialized), the behavior of the entire program is undefined (see [intro.execution]/5).

The fix is to construct a std::ios_base::Init object yourself in Test's constructor.


1 This is actually underspecified for variable templates when C++14 was published, but it's always been the intent.

like image 197
T.C. Avatar answered Oct 20 '22 03:10

T.C.