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

时间:2016-06-14 15:25:11

标签: c++ templates c++14 constexpr crtp

请考虑以下代码:

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 等待它接受答案。

1 个答案:

答案 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模式下符合标准。