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

标签 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 : 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> 的隐式实例化时被实例化。 .只要你用barU 的定义之后已经看到(或从 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/

相关文章:

c++ - 如何将模板参数限制为模板化接口(interface)特化的后代?

C++:尝试通过组合和帕斯卡三角来理解 constexpr

c++ - 我尝试使用 wchar_t、char16_t 和 char32_t 类型打印汉字,但无济于事。

javascript - Nunjacks 图标计数宏

c++ - 我们可以使用检测成语来检查类是否具有具有特定签名的成员函数吗?

c++ - 重写 String 类中的下标运算符

c++ - cairo_show_text 内存泄漏

c++ - 在插入列表之前重复使用相同的字符串似乎是按值传递的?

c++ - 在 SFML 2.4 中使用字体 Sprite 表作为字体

c++ - 从派生类访问基类中的类型别名