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;
}
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.
constexpr std::scalbn
solve this?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.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.