在模板

时间:2015-07-13 12:42:20

标签: c++ templates visual-studio-2015 explicit-instantiation

这是一个最小的例子:

#include <iostream>

struct B {
  B() { x = 42; }
  static int x;
};  

int B::x;

template <int N>
struct A {
  int foo() { return b.x; }
  static B b;
};

template<int N>
B A<N>::b;

//template struct A<2>; // explicit instantiation with N = 2 (!)

int main(int argc, char **argv) {
  std::cout << A<1>().foo() << std::endl;
  return 0;
}

该程序使用g ++ 4.9.2写入42,但使用Visual Studio 2015 RC写入0。此外,如果我取消注释显式实例化,VS2015RC也会给出42,这非常有趣,因为此处的模板参数与main函数中使用的模板参数不同。

这是一个错误吗?我假设g ++是正确的,因为b中有foo的引用,因此应该调用B的构造函数。

编辑:有一个简单的解决方法 - 如果B中有一个非静态变量,在A中引用,VS2015RC将正确编译:

// ...

struct B {
  B() { x = 42; }
  static int x;
  int y;                         // <- non-static variable
};

// ...

template <int N>
struct A {
  int foo() { b.y; return b.x; } // <- reference to b.y
  static B b;
};

这似乎有效,即使b.y作为陈述,显然是NOP。

2 个答案:

答案 0 :(得分:6)

来自[basic.start.init]:

  

具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量应为零初始化(8.5)   在进行任何其他初始化之前。对象o的常量初始值设定项是a的表达式   常量表达式,除了它甚至可以为o及其子对象调用constexpr构造函数   如果这些对象是非文字类类型。 [...]

     

一起,零初始化和常量初始化称为静态初始化;所有其他初始化是   动态初始化。在进行任何动态初始化之前,应执行静态初始化。

在我们的例子中,b是静态初始化的,但b.x是动态初始化的(构造函数不是constexpr)。但我们也有:

  

实现定义是否使用静态存储动态初始化非局部变量   持续时间在主要的第一个陈述之前完成。如果初始化延迟到某个时间点   在第一个main语句之后,它应该在任何函数或变量的第一次使用(3.2)之前发生   在与要初始化的变量相同的翻译单元中定义。

Odr-used表示来自[basic.def.odr]:

  

变量x的名称显示为潜在评估的表达式ex,除非应用,否则由ex使用   左值到右值的转换(4.1)到x产生一个不调用任何非平凡的常量表达式(5.20)   功能,如果[...]

但评估b.x不会产生常量表达式,因此我们可以停在那里 - b.x 使用 A<N>::foo(),这也是第一个 odr-use 。因此,虽然初始化不必在main()之前发生,但它必须在foo()之前发生。所以如果你得到0,那就是编译器错误。

答案 1 :(得分:-2)

我倾向于写这样的代码:

struct B {
   B() {} 
   static int x;
};

int B::x = 42;

毕竟,静态(x)是在最后一行定义的(因此应该初始化)。将初始化放在B的构造函数中意味着静态x(只有其中一个!)将在每次构造一个B时重新初始化。有一个静态,你应该只初始化一次。