请考虑以下代码:
template<typename T>
struct S { static constexpr int bar = T::foo; };
struct U: S<U> { static constexpr int foo = 42; };
int main() { }
GCC v6.1编译它,clang 3.8拒绝它并带有错误:
2:错误:'U'中没有名为'foo'的成员 struct S {static constexpr int bar = T :: foo; };
哪种编译器是对的?
可能是因为U
is not a complete type我们试图在S
内使用它吗?
在这种情况下,它应该被认为是GCC的一个错误,但我想知道我是否正好在bug跟踪器上搜索/打开一个问题......
修改
与此同时,我向GCC开了一个bug 等待它接受答案。
答案 0 :(得分:6)
对于C ++ 14和11,Clang是对的;然而,最新的工作草案(未来的C ++ 17)已经发生了变化 - 请参阅下一节。
要查找的标准引用(来自N4140,最接近C ++ 14的草案):
[temp.inst] / 1:
[...]类模板特化的隐式实例化 导致声明的隐式实例化,而不是 定义,默认参数或例外规范 类成员函数,成员类,作用域成员枚举, 静态数据成员和成员模板; [...]
[temp.point] / 4:
对于类模板特化,[...]实例化的要点 对于这样的特化,紧接在命名空间范围之前 涉及专业化的声明或定义。
因此,S<U>
的实例化时间恰好在U
的声明之前,只是在概念上插入了前向声明struct U;
,因此名称U
找到了。
[class.static.data] / 3:
[...]可以在文件中声明文字类型的静态数据成员 使用
constexpr
说明符的类定义;如果是的话,它 声明应指定 brace-or-equal-initializer 每个 initializer-clause 是一个赋值表达式 不断表达。 [...]该成员仍应定义为 如果在程序中使用了odr-used(3.2),则命名空间范围 命名空间范围定义不应包含初始值设定项。
根据上面引用的段落,在bar
的定义中S
的声明,即使它有一个初始化器,仍然只是一个声明,而不是一个定义,所以它&#39;在S<U>
被隐式实例化时被实例化,并且当时没有U::foo
。
解决方法是使bar
成为一个函数;根据第一个引用,函数的定义将不会在S<U>
的隐式实例化时实例化。只要在看到bar
的定义之后(或U
的其他成员函数的主体内部)使用S
,因为这些只会被单独实例化如果需要 - [14.6.4.1p1]),这样的事情会起作用:
template<class T> struct S
{
static constexpr int bar() { return T::foo; }
};
struct U : S<U> { static constexpr int foo = 42; };
int main()
{
constexpr int b = U::bar();
static_assert(b == 42, "oops");
}
在P0386R2通过工作草案(目前为N4606)后,[class.static.data] / 3已经修订;相关部分现在写道:
[...]可以在类定义中定义内联静态数据成员 并可以指定大括号或等于初始化程序。如果是会员 使用
constexpr
说明符声明,可以重新声明 没有初始化程序的命名空间范围(不推荐使用此用法;请参阅 D.1)。 [...]
对[basic.def] /2.3:
的更改进行了补充声明是定义,除非:
[...]
- 它在类定义(9.2,9.2.3)中声明了非内联静态数据成员,
[...]
因此,如果它是内联的,那么它就是一个定义(有或没有初始化器)。 [dcl.constexpr] / 1说:
[...]使用
constexpr
声明的函数或静态数据成员 说明符隐式地是内联函数或变量(7.1.6)。 [...]
这意味着bar
的声明现在是一个定义,并且根据上一节中的引用,它没有为S<U>
的隐式实例化实例化;只有bar
的声明,它不包含初始值设定项,在那时被实例化。
本案例中的更改在当前工作草案的[depr.static_constexpr]中的示例中得到了很好的总结:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)
这使得GCC的行为在C ++ 1z模式下符合标准。