使用派生 class 的静态 constexpr 数据成员初始化基 class 的静态 constexpr 数据成员

Initializing a static constexpr data member of the base class by using a static constexpr data member of the derived class

考虑以下代码:

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 compiles it, clang 3.8 拒绝并出现错误:

2 : error: no member named 'foo' in 'U'
struct S { static constexpr int bar = T::foo; };

哪个编译器是正确的?
可能是因为 U is not a complete type 在我们尝试在 S 中使用它的时候?
在这种情况下,它应该被认为是 GCC 的一个错误,但我想知道我之前是否正确 search/open 错误跟踪器上的一个问题...

编辑

与此同时,我已经打开 bug 到 GCC。
等待它接受答案。

对于C++14和11,Clang是对的;然而,在最新的工作草案(未来的 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; [...]

[temp.point]/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.

根据上面引用的段落,S的定义中的bar的声明,即使它有一个初始化器,仍然只是一个声明,而不是一个定义,所以它被实例化了当 S<U> 被隐式实例化时,那时没有 U::foo

一个解决方法是使 bar 成为一个函数;根据第一个引用,函数的定义不会在 S<U> 的隐式实例化时被实例化。只要在看到 U 的定义后使用 bar(或在 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 into the working draft (currently 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 模式下符合标准。