6

I have the following piece of code, that behaves as expected on gcc and clang. However, MSVC gives me an unexpected result.

Lets first look at the problematic code.

#include <iostream>

// -----------------------------------------------

class Test // Dummy for MCVE
{
public:
    Test();
    void Print();
private:
    int arr[5];
};

Test tst;

// -----------------------------------------------

template<typename T>
struct range // some stuff not needed by example removed
{
    constexpr range(T n) : b(0), e(n) {}
    constexpr range(T b, T e) : b(b), e(e) {}
    struct iterator
    {
        T operator*() { return i; }
        iterator& operator++() { ++i; return *this; }
        bool operator!=(iterator other) { return i != other.i ; }
        T i;
    };
    iterator begin() const { return{ b }; }
    iterator end() const { return{ e }; }
private:
    T b,e;
};

constexpr range<int> coord(5);

// -----------------------------------------------

Test::Test()
{
    for(auto i : coord)
        arr[i]=i;
}

void Test::Print()
{
    for(auto i : coord)
        std::cout << arr[i] << std::endl;
}

// -----------------------------------------------

int main()
{
    tst.Print();
}


Now, on both clang and gcc this prints '0 1 2 3 4'
However, on MSVC this prints '0 0 0 0 0'
The reason being that when the constructor on the global variable tst runs, 'coord' have yet not been initialized (to 0,5), but it is also not random, but rather (0,0).
To me it would make sense that constexpr initialization happens before regular initialization. Is MSVC conformant in this behaviour?

I should perhaps note that I'm using MSVC version 14.0.22823.1, and that the expected result can be obtained by changing the order of the declarations

sp2danny
  • 7,488
  • 3
  • 31
  • 53

1 Answers1

5

For static storage duration objects initialization must happen in this order:

  1. Zero-initialization.
  2. Constant initialization (i.e. constexpr).
  3. Dynamic initialization.

Relevant standardeese,

C++14 §3.6.2/2:

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. […] Constant initialization is performed: […] if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object; […] 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.

The same paragraph defines (breaking the flow of text, so I removed it above)

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.


In the reported example, which I've verified with Visual C++ 2015, the dynamic initialization of the static storage duration object tst takes place before the constant initialization of the static storage duration object coord, and that's a compiler bug.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • [According to B. Stroustrup](http://www.stroustrup.com/C++11FAQ.html#constexpr), *constexpr's primary function is to extend the range of what can be computed at compile time, making such computation type safe. Objects declared constexpr have their initializer evaluated at compile time; they are basically values kept in the compiler's tables and only emitted into the generated code if needed.* That seems to be different from what the standard is saying. – R Sahu Jul 17 '15 at 04:32
  • 1
    @RSahu: I see no conflict. Consider a static variable `int const x = 3`. That can be used as e.g. raw array size, so it's very compile time. All the same, if the address of that variable is taken, looking at the memory contents there should better be a `3` there. It must be placed there at some time after loading the program and before execution of the inspecting statement. The standardese about initialization is about exactly when each kind of such initialization takes place. – Cheers and hth. - Alf Jul 17 '15 at 04:39
  • That makes sense now. Thanks for the clarification. In the OP's use case, since the `range` object `coord` is used to make function calls it makes sense that it needs to be initialized at run time also. – R Sahu Jul 17 '15 at 04:45
  • Note, the rule here have [changed a bit recently](http://stackoverflow.com/a/34276374/1708801). Although it does not really change that this is a bug. – Shafik Yaghmour Dec 24 '15 at 02:57
  • This compiler bug is fixed in VS 2015 Update 3 (cl.exe version 19.00.24213.1, VS version 14.0.25425.01) – Bruce Dawson Nov 01 '16 at 19:04
  • Looks to have been re-fixed (or a related issue fixed) in VS 2019 16.5 Preview 2: https://developercommunity.visualstudio.com/content/problem/262083/compiler-emits-dynamic-initializer-for-variable-wi.html?childToView=894585#comment-894585 – Thief Jan 23 '20 at 10:30