3

Is there any trap in static constant field initialization order?

template <typename T>
struct constant_test {

    static const T PI;
    static const T FULL_CIRCLE;
    static const T HALF_CIRCLE;
    static const T DEG_TO_RAD;

};

template <typename T> const T constant_test<T>::PI = 3.141592653589f;
template <typename T> const T constant_test<T>::FULL_CIRCLE = 360.0f;
template <typename T> const T constant_test<T>::HALF_CIRCLE = constant_test<T>::FULL_CIRCLE / 2;
template <typename T> const T constant_test<T>::DEG_TO_RAD = constant_test<T>::PI / constant_test<T>::HALF_CIRCLE;

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // uncomment to make it work
    // float test_ref = constant_test<float>::HALF_CIRCLE;
    char buf[128];
    sprintf_s(buf, 128, "Value: %f", constant_test<float>::DEG_TO_RAD);
    OutputDebugStringA(buf); // prints "Value: 1.#INF00"
    return 0;
}

An expression constant_test<float>::DEG_TO_RAD magically returns -Infinity

If i remove template parameter and make them only float then constant is evaluated correctly (0.017453)

If i add reference to HALF_CIRCLE constant then it's also evaluated correctly

I'm using MSVC 2013 SP 1.

Why? What i'm missing?

acc15
  • 1,205
  • 1
  • 11
  • 22

2 Answers2

1

According to

there are the following types of initialization:

  • static initialization
    • zero initialization: for example int x; in global scope
    • constant initialization: for example int x=1+2; where 1+2 is a constant expression
  • dynamic initialization
    • ordered initialization: for example
      • std::vector<int> x{1, 2, 3}; in global scope, or
      • template<> std::vector<int> A<int>::x{1, 2, 3}; (explicit specialization)
    • unordered initialization: for example template<class T> std::vector<T> A<T>::x;

See What is Unordered dynamic initialization, Partially-ordered dynamic initialization and Ordered dynamic initialization for what the terms mean.

In this case

template <typename T> const T constant_test<T>::PI = 3.141592653589f;
template <typename T> const T constant_test<T>::FULL_CIRCLE = 360.0f;

are constant initialization(s), and because they're floating-point type in this case they're not constexpr, according to c++ - Initializing constexpr with const: Different treatment for int and double - Stack Overflow.

template <typename T> const T constant_test<T>::HALF_CIRCLE = constant_test<T>::FULL_CIRCLE / 2;
template <typename T> const T constant_test<T>::DEG_TO_RAD = constant_test<T>::PI / constant_test<T>::HALF_CIRCLE;

are dynamic unordered initialization.

As such, the initialization order of the 2 last ones are not guaranteed.

user202729
  • 3,358
  • 3
  • 25
  • 36
  • Potentially relevant: They are only not constant-initialized because `T` is a floating-point type. For an integral type they would be. – user17732522 Feb 26 '22 at 03:57
  • 1
    `const` integral type variables initialized by constant expressions behave as if they were `constexpr` when used in expressions. It is a specific exception to the requirement that used variables be `constexpr`. – user17732522 Feb 26 '22 at 04:03
0

When the code is converted to a console application and use g++, it prints out the correct value (g++4.8.2, OS X):

#include <iostream>

using namespace std;

template <typename T>
struct constant_test {

    static const T PI;
    static const T FULL_CIRCLE;
    static const T HALF_CIRCLE;
    static const T DEG_TO_RAD;

};

template <typename T> const T constant_test<T>::PI = 3.141592653589f;
template <typename T> const T constant_test<T>::FULL_CIRCLE = 360.0f;
template <typename T> const T constant_test<T>::HALF_CIRCLE = constant_test<T>::FULL_CIRCLE / 2;
template <typename T> const T constant_test<T>::DEG_TO_RAD = constant_test<T>::PI / constant_test<T>::HALF_CIRCLE;

int main()
{
    // uncomment to make it work
    //float test_ref = constant_test<float>::HALF_CIRCLE;
    cout << "Value: " << constant_test<float>::DEG_TO_RAD << endl; // correct result

    char buf[128];
    sprintf(buf, "Value: %f", constant_test<float>::DEG_TO_RAD );
    cout << buf << endl; // again correct
    
    return 0;
}

Output:

Value: 0.0174533
Value: 0.017453

The static constants are initialized in order, see https://stackoverflow.com/a/10011133/3093378, so you should have constant_test<T>::DEG_TO_RAD initialized when you display it (and it looks it is, from the code above).

user202729
  • 3,358
  • 3
  • 25
  • 36
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • Do u think ..Microsoft? – acc15 May 24 '14 at 02:19
  • Did you try to run the above code in a console-like application? – vsoftco May 24 '14 at 02:21
  • I still get the same correct result if I use the typical `sprintf` as `sprintf(buf, "Value: %f", constant_test::DEG_TO_RAD );`, then `cout << buf << endl;` – vsoftco May 24 '14 at 02:24
  • direct copy-paste of your version. cmd.exe: `constant_test\Debug>constant_test.exe` `Value: 1.#INF`... =) I don't sure is it allowed to reference static field from another static field in the same compilation unit. But why not.. looks like a bug – acc15 May 24 '14 at 02:27
  • I am not sure, I don't have Windows so I cannot reproduce your problem, but on my system it runs fine with both `g++` and `clang++` – vsoftco May 24 '14 at 02:28
  • I guess that the order is sequential only for explicitly specialized templated members (C++ standard, 3.6.2). See also the discussion here: http://stackoverflow.com/questions/1819131/c-static-member-initalization-template-fun-inside Just make them const and initialize them in a constructor. – vsoftco May 24 '14 at 02:37
  • And I also realized that in `g++`, I can change the order the declarations and still the compiler arrange them in the "proper" order, i.e. define the non-dependent types first. – vsoftco May 24 '14 at 02:39
  • If replace `DEG_TO_RAD = constant_test::PI / static_cast(180.0);` then it works. Looks like MSVC bug. – acc15 May 24 '14 at 02:46
  • Now I am not even sure how valid such an initialization is: `template const T constant_test::PI = 3.141592653589f;` The type `T` is not known at this point, so don't really understand how this works. – vsoftco May 24 '14 at 02:50
  • Template code is compiled only on template instantiation. At this point type is known. – acc15 May 24 '14 at 02:54
  • I know, but in your code it looks like the static variables should come into existence before `main()` (where you first instantiate the template), however at the point where you declare the static ones the type is not known. I am a bit confused by this. – vsoftco May 24 '14 at 02:56
  • I guess that only when you instantiate the template for a given type, all of the static variables are initialized (for that given type). – vsoftco May 24 '14 at 03:03
  • Yep. But... MSVC initializes them very strange. If there is no direct reference in code to some constant then it thinks that this constant is zero. Thats why i get `infinity`-- – acc15 May 24 '14 at 03:30
  • Btw, I posted a question similar to yours, hope we can get some answers :) And indeed, according to the standard, a static variable is implicitly zero if you do not initialize it (i.e. if define it outside the class as `static int Foo::i;`, then `A::i` is 0 implicitly), but otherwise should have the value given by the initialization. – vsoftco May 24 '14 at 03:32
  • 1
    Correct initialization order is not guaranteed for the relevant members. See the other answer. – user17732522 Feb 26 '22 at 03:54