考虑以下代码:
template<typename T>
struct S { static constexpr int bar = T::foo; };
struct U: S<U> { static constexpr int foo = 42; };
int main() { }
2 : error: no member named 'foo' in 'U'
struct S { static constexpr int bar = T::foo; };
哪个编译器是正确的?
难道是因为 U
is not a complete type在我们尝试在 S
中使用它的地方?
在这种情况下,它应该被认为是 GCC 的错误,但我想知道我是否适合在错误跟踪器上搜索/打开问题...
编辑
同时我打开了一个 bug到海湾合作委员会。
等待它接受答案。
最佳答案
对于 C++14 和 11,Clang 是对的;但是,在最新的工作草案( future 的 C++17)中情况发生了变化 - 请参阅下一节。
要查找的标准引用是(来自 N4140,最接近 C++14 的草案):
[temp.inst]/1:
[...] The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; [...]
[温度点]/4:
For a class template specialization, [...] the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.
所以,S<U>
的实例化点就在 U
的声明之前, 只有一个前向声明 struct U;
在概念上插入之前,因此名称 U
找到了。
[class.static.data]/3:
[...] A static data member of literal type can be declared in the class definition with the
constexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.
根据上面引用的段落,bar
的声明在 S
的定义内, 即使它有一个初始化器,它仍然只是一个声明,而不是一个定义,所以它在 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 已被修改;相关部分现在为:
[...] An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the
constexpr
specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). [...]
这由对 [basic.def]/2.3 的更改补充:
A declaration is a definition unless:
[...]
- it declares a non-inline static data member in a class definition (9.2, 9.2.3),
[...]
所以,如果它是内联的,它就是一个定义(有或没有初始化器)。 [dcl.constexpr]/1 说:
[...] A function or static data member declared with the
constexpr
specifier is implicitly an inline function or variable (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 模式下的行为符合标准。
关于c++ - 使用派生类的静态 constexpr 数据成员初始化基类的静态 constexpr 数据成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37816186/