c++c++14variable-templates

Order of dynamic initialization of implicitly instantiated variable templates


I filed an issue for what I thought was a bug in MSVC, but turns out to be implementation-defined behavior. I want to make sure I fully understand the reason. I have this in a single translation unit:

#include <limits>
#include <utility>
#include <type_traits>
#include <cmath>
#include <iostream>

struct MyClass {
  double value_of;
  MyClass(double d): value_of(d) {}
};

template<class T> struct MyTraits { static constexpr bool do_enable = false; };
template<> struct MyTraits<MyClass> { static constexpr bool do_enable = true; typedef double value_type; };

template<typename T> using EnableIfFP = std::enable_if_t<std::is_floating_point<T>::value>;
template<typename T> using EnableIfMyClass = std::enable_if_t<MyTraits<T>::do_enable>;

template<typename T> constexpr int EXP = std::numeric_limits<T>::max_exponent / 2;

template<typename T, typename Enabler = void> const T huge = T{ 0 };
template<typename T> const T huge<T, EnableIfFP<T> > = std::scalbn(1.0, EXP<T>);
template<typename T> const T huge<T, EnableIfMyClass<T> > = T{ huge<typename MyTraits<T>::value_type> };

int main() {
  // huge<double>; // PRESENCE OF THIS LINE AFFECTS OUTPUT IN MSVC
  std::cout << huge<MyClass>.value_of << std::endl;
  return 0;
}

https://godbolt.org/z/Iwpwzf

My expectation was for huge<MyClass>.value_of to be 1.34078e+154: the 3rd definition of huge should use the 2nd definition for its init. Clang 7, GCC 8 and ICC 19 all do that, but MSVC 2017 prints 0 (from either T{ 0 } or zero-initialization, idk) unless the commented line is enabled.

As I now understand, main() is implicitly instantiating both huge<MyClass> and (indirectly) huge<double>, but their initialization is unordered:

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. [Note: An explicitly specialized non-inline static data member or variable template specialization has ordered initialization. — end note] [basic.start.dynamic]

This scenario falls under the criteria that I've bolded, so it's unordered. There's that note at the end, which I think reads as "an explicitly specialized variable template specialization has ordered initialization," but I'm not fully (explicitly) specializing, so it doesn't apply.

I also saw this answer, which seems to conflict with the above part of the spec:

Global variables in a single translation unit (source file) are initialized in the order in which they are defined.


  1. Is the above reasoning correct?
  2. Would having constexpr std::scalbn solve this?
  3. As far as making the code legal/portable, MSVC croaks when I try to explicitly instantiate the specialization by adding template const MyClass huge<MyClass>. Pretending to use the huge<double> specialization works, but is unused code (GCC warns). I know I can instead make these static local variables in a function (at the risk of locks/guards being added) or return values of function or class templates, but this option is the most concise, if it's legal.

Solution

    1. You’re correct—the “conflicting” answer predates variable templates entirely. (The statement is still true given that a variable template is not a “global variable”, and that its instantiations are not defined (i.e., declared) textually.)
    2. Yes, std::scalbn being constexpr would help—it’s only dynamic non-local initialization that is (sometimes) unordered, and if a partial specialization of a variable template (of literal type) is initialized with a constant expression, no such dynamic initialization occurs. Conveniently, such constexprification has been approved for C++20, although it may or may not pass wording review in time for it.
    3. No amount of implicit or explicit instantiation will help here—it’s only explicit specialization that can do that, as you quoted, or the various tricks of burying things in functions. You could of course use the functions to initialize the variables (and anything else that might be initialized before they are) and use the variables everywhere else.