为什么这个循环模板实例化合法?

时间:2017-04-07 11:21:08

标签: c++ templates instantiation circular-reference

我不明白编译器在这里做了什么:

#include <iostream>
using namespace std;

// non-default-constructable struct
struct X
{
    X(int v) : x(v) {}
    const int x;
};

template< typename T>
struct A
{
    static const X a;
};

// trigger a compiler error if we try to instantiate the default template
template< typename T >
const X A<T>::a;


template<>
struct A<int>
{
    static const X a;
};

template<>
struct A<float>
{
    static const X a;
};

// is this not infinitely circular?
const X A<int>::a = X(A<float>::a.x + 1);
const X A<float>::a = X(A<int>::a.x + 1);

int main() {
    // error as expected, A<bool>::a cannot be default-constructed
    // cout << A<bool>::a.x << endl; 

    // this compiles and prints "1 2"
    cout << A<int>::a.x << " " << A<float>::a.x << endl;
    return 0;
}

我原本期望a的两个专门定义产生编译器错误,因为它们都是使用另一个的值初始化的,并且甚至没有默认构造函数可以依赖。但显然,这会在ideone中编译并打印1 2。那么编译器如何得出X的两个实例应该用这些值初始化的结论呢?

1 个答案:

答案 0 :(得分:4)

这恰好是非局部变量初始化的副作用。标准说:

  

3.6.2非局部变量的初始化[basic.start.init]

     

...
...具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量应为零初始化(8.5)   在进行任何其他初始化之前... ... ...在进行任何动态初始化之前,应执行静态初始化。   如果变量是a,则具有静态存储持续时间的非局部变量的动态初始化是无序的   隐式或显式实例化的特化,否则是有序的[注意:一个明确的专门化   静态数据成员或变量模板特化已经有序初始化。 - 尾注]变量   在单个翻译单元中定义的有序初始化应按其顺序初始化   翻译单位中的定义。

这就是这里发生的事情:

  • A<int>::aA<float>::a均为0初始化
  • 然后按照其定义的顺序初始化它们
    • first A<int>::a读取A<float>::a中的内容,因为之前的0初始化为0,添加1并完全初始化为1
    • 然后A<float>::a获取现已完全初始化的A<int>::a的值,该值为1,加1并完全初始化为2

这意味着这是一个结构良好的计划。

但同一段后来说:

  

如果对象obj1的初始化引用了命名空间范围的对象obj2   可能需要动态初始化并在稍后的同一翻译单元中定义,它是未指定的   是否使用obj2的值将是完全初始化的obj2的值(因为obj2是静态的   初始化)或将是obj2的值仅为零初始化

所以我不确定输出是否需要为1 2,或者如果A<int>::a的动态初始化首先触发A<float>::a

的动态初始化,它是否为2,1