我提出了一个我认为是 MSVC 中的错误的问题,但结果是实现定义的行为。我想确保我完全理解原因。我在一个翻译单元中有这个:
#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;
}
我的期望是huge<MyClass>.value_of
1.34078e+154:第 3 个定义huge
应该使用第 2 个定义作为它的 init。Clang 7、GCC 8 和 ICC 19 都这样做,但 MSVC 2017 打印 0(来自T{ 0 }
初始化或零初始化,idk),除非启用了注释行。
正如我现在所理解main()
的,隐式实例化了huge<MyClass>
and (indirectly) huge<double>
,但它们的初始化是无序的:
如果变量是隐式或显式实例化的特化,则具有静态存储持续时间的非局部变量的动态初始化是无序的,如果变量是不是隐式或显式实例化的特化的内联变量,则是部分排序的,否则是有序的. [注意:显式专门化的非内联静态数据成员或变量模板专门化已订购初始化。— 尾注] [basic.start.dynamic]
这个场景属于我加粗的标准,所以它是无序的。最后有那个注释,我认为它读作“一个明确专门化的变量模板专门化已订购初始化”,但我没有完全(明确)专门化,所以它不适用。
我也看到了这个答案,这似乎与规范的上述部分冲突:
单个翻译单元(源文件)中的全局变量按照定义的顺序进行初始化。
- 上述推理是否正确?
- 会
constexpr std::scalbn
解决这个问题吗? - 就使代码合法/可移植而言,当我尝试通过添加
template const MyClass huge<MyClass>
. 假装使用huge<double>
专业化有效,但未使用代码(GCC 警告)。我知道我可以在函数中创建这些静态局部变量(冒着添加锁/防护的风险)或返回函数或类模板的值,但如果它是合法的,这个选项是最简洁的。