【问题标题】:Why is this circular template instantiation legal?为什么这个循环模板实例化是合法的?
【发布时间】:2017-04-07 11:21:08
【问题描述】:

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

#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 的两个实例应该用这些值初始化的结论的呢?

【问题讨论】:

  • 不,不是,这是未定义的行为。 A::a 在 A:a 之前没有被赋值,这意味着你只是在读取随机内存

标签: c++ templates instantiation circular-reference


【解决方案1】:

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

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

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

这正是这里发生的事情:

  • A&lt;int&gt;::aA&lt;float&gt;::a 都是 0 初始化
  • 然后按照定义的顺序对其进行初始化
    • 首先A&lt;int&gt;::a 读取A&lt;float&gt;::a 中的内容,因为之前的0 初始化为0,加1 并用1 完全初始化
    • 然后A&lt;float&gt;::a 得到现在完全初始化的A&lt;int&gt;::a 的值,即1,加1,完全初始化为2

这意味着这是一个格式良好的程序。

但同一段稍后会说:

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

所以我不确定是否要求输出为1 2,或者如果A&lt;int&gt;::a的动态初始化首先触发A&lt;float&gt;::a的动态初始化是否可以是2、1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-05
    • 2019-03-25
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    • 1970-01-01
    • 2017-06-22
    相关资源
    最近更新 更多