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

sky*_*ack 14 c++ templates crtp constexpr c++14

请考虑以下代码:

template<typename T>
struct S { static constexpr int bar = T::foo; };

struct U: S<U> { static constexpr int foo = 42; };

int main() { }
Run Code Online (Sandbox Code Playgroud)

GCC v6.1编译它,clang 3.8拒绝它并带有错误:

2:错误:'U'
结构中没有名为'foo'的成员S {static constexpr int bar = T :: foo; };

哪个编译器是对的?
可能是因为我们尝试在其中使用它时U 不是一个完整的类型S吗?
在这种情况下,它应该被认为是GCC的一个错误,但我想知道我是否正好在bug跟踪器上搜索/打开一个问题...

编辑

与此同时,我向GCC 打开了一个错误.
等待它接受答案.

bog*_*dan 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符在类定义中声明文字类型的静态数据成员; 如果是这样,它的声明应指定一个大括号或等于初始化器,其中作为赋值表达式的每个initializer子句都是一个常量表达式.[...]如果程序中使用了odr-used(3.2),并且命名空间作用域定义不包含初始化程序,则仍应在名称空间作用域中定义该成员.

根据上面引述的一段,声明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");
}
Run Code Online (Sandbox Code Playgroud)

在将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)
Run Code Online (Sandbox Code Playgroud)

这使得GCC的行为在C++ 1z模式下符合标准.