动态初始化隐式实例化变量模板的顺序

时间:2019-06-04 22:29:36

标签: c++ c++14 variable-templates

我提出了一个我认为是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;
}

https://godbolt.org/z/Iwpwzf

我期望huge<MyClass>.value_of为1.34078e + 154:huge的第三个定义应使用第二个定义作为其初始值。 Clang 7,GCC 8和ICC 19都可以这样做,但是除非启用了注释行,否则MSVC 2017会打印0(从T{ 0 }或零初始化,idk)。

据我所知,main()隐式实例化了huge<MyClass>和(间接)huge<double>,但是它们的初始化是无序的:

  

动态初始化具有静态存储持续时间的非本地变量是无序的(如果该变量是隐式或显式实例化的专业化),如果该变量是一个不是隐式或显式实例化的特殊化的内联变量,否则是有序的。 [注意:显式专门化的非内联静态数据成员或变量模板专门化已命令初始化。 —注] [basic.start.dynamic]

这种情况属于我已加粗的条件,因此是无序的。最后有个注释,我认为它是“显式专门化的变量模板专门化已对初始化进行了排序”,但我没有完全(明确地)专门化,因此不适用。

我还看到了this answer,它似乎与规范的上述部分冲突:

  

单个翻译单元(源文件)中的全局变量按照定义顺序进行初始化。


  1. 以上推理正确吗?
  2. constexpr std::scalbn是否可以解决这个问题?
  3. 就使代码合法/可移植而言,当我尝试通过添加template const MyClass huge<MyClass>来显式实例化专业化时,MSVC崩溃了。假装使用huge<double>专业化作品,但未使用的代码(GCC警告)。我知道我可以改为在函数中创建这些静态局部变量(冒着添加锁/防护的危险)或返回函数或类模板的值,但如果合法,此选项最为简洁。

1 个答案:

答案 0 :(得分:1)

  1. 您是正确的-“冲突”答案完全早于变量模板。 (鉴于变量 template 不是“全局变量”,并且其实例化未按文本定义(声明为 ie ),该陈述仍然正确。)
  2. 是的,std::scalbn是constexpr会有所帮助-它只是(有时)无序的动态非本地初始化,以及变量模板(文字类型)的部分专业化使用常量表达式初始化时,不会发生这种动态初始化。方便地,such constexprification已被批准用于C ++ 20,尽管它可能会或可能不会及时通过措辞审查。
  3. 这里没有任何隐式或显式的实例化会有所帮助-正如您所引用的那样,只有显式的专业化可以做到这一点,或者将函数埋入函数的各种技巧。当然,您可以使用函数来初始化变量(以及可能在初始化之前进行的其他任何操作),并在其他地方使用变量。