[英]Initializing a static constexpr data member of the base class by using a static constexpr data member of the derived class
Consider the following code: 请考虑以下代码:
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 rejects it with the error: GCC v6.1编译它, clang 3.8拒绝它并带有错误:
2 : error: no member named 'foo' in 'U'
2:错误:'U'中没有名为'foo'的成员
struct S { static constexpr int bar = T::foo;struct S {static constexpr int bar = T :: foo; };
};
Which compiler is right? 哪个编译器是对的?
Could it be due to the fact that U
is not a complete type at the point where we try to use it within S
? 可能是因为
U
在我们尝试在S
使用它时不是一个完整的类型吗?
In this case, it should be considered a bug of GCC, but I'd like to know if I'm right before to search/open an issue on the bug tracker... 在这种情况下,它应该被认为是GCC的一个错误,但我想知道我是否正好在bug跟踪器上搜索/打开一个问题...
EDIT 编辑
Meanwhile I've opened a bug to GCC. 与此同时,我向GCC打开了一个错误 。
Waiting for it to accept the answer. 等待它接受答案。
For C++14 and 11, Clang is right; 对于C ++ 14和11,Clang是对的; however, things have changed in the latest working draft (the future C++17) - see the next section.
然而,最新的工作草案(未来的C ++ 17)已经发生了变化 - 请参阅下一节。
The Standard quotes to look for are (from N4140, the draft closest to C++14): 要查找的标准报价是(从N4140开始,最接近C ++ 14的草稿):
[temp.inst]/1: [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: [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.
对于类模板特化,[...]这种特化的实例化点紧接在引用特化的命名空间范围声明或定义之前。
So, the point of instantiation for S<U>
is right before the declaration of U
, with only a forward declaration struct U;
这样,实例化的点
S<U>
是宣布前右U
,仅前向声明struct U;
conceptually inserted before, so that the name U
is found. 在概念上插入之前,以便找到名称
U
[class.static.data]/3: [class.static.data] / 3:
[...] A static data member of literal type can be declared in the class definition with the
constexpr
specifier;[...]可以使用
constexpr
说明符在类定义中声明文字类型的静态数据成员; 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.如果是这样,它的声明应指定一个大括号或等于初始化器 ,其中作为赋值表达式的每个initializer子句都是一个常量表达式。 [...] 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 .
[...]如果程序中使用了odr-used(3.2),并且命名空间作用域定义不包含初始化程序,则仍应在名称空间作用域中定义该成员。
According to the paragraph quoted above, the declaration of bar
within the definition of S
, even though it has an initializer, is still just a declaration, not a definition, so it's instantiated when S<U>
is implicitly instantiated, and there's no U::foo
at that time. 根据上面引用的段落,
S
的定义中的bar
声明,即使它有一个初始化器,仍然只是一个声明,而不是一个定义,所以当S<U>
被隐式实例化时,它被实例化,并且没有U::foo
那个时候U::foo
。
A workaround is to make bar
a function; 解决方法是使
bar
成为一个功能; according to the first quote, the function's definition will not be instantiated at the time of the implicit instantiation of S<U>
. 根据第一个引用,在
S<U>
的隐式实例化时,函数的定义不会被实例化。 As long as you use bar
after the definition of U
has been seen (or from within the bodies of other member functions of S
, since those, in turn, will only be instantiated separately when needed - [14.6.4.1p1]), something like this will work: 只要你看到
U
的定义后(或者从S
的其他成员函数的主体内部使用bar
,因为那些反过来只会在需要时单独实例化 - [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");
}
Following the adoption of P0386R2 into the working draft (currently N4606 ), [class.static.data]/3 has been amended; 在将P0386R2纳入工作草案(目前为N4606 )之后,[class.static.data] / 3已经修订; the relevant part now reads:
相关部分现为:
[...] 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).如果使用
constexpr
说明符声明成员,则可以在没有初始化程序的命名空间作用域中重新声明该成员(不推荐使用此用法;请参阅D.1)。 [...][...]
This is complemented by the change to [basic.def]/2.3: 这是对[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),
它在类定义(9.2,9.2.3)中声明了一个非内联静态数据成员,
[...]
[...]
So, if it's inline, it's a definition (with or without an initializer). 因此,如果它是内联的,那么它就是一个定义(有或没有初始化器)。 And [dcl.constexpr]/1 says:
[dcl.constexpr] / 1说:
[...] A function or static data member declared with the
constexpr
specifier is implicitly an inline function or variable (7.1.6).[...]使用
constexpr
说明符声明的函数或静态数据成员隐式地是内联函数或变量(7.1.6)。 [...][...]
Which means the declaration of bar
is now a definition, and according to the quotes in the previous section it's not instantiated for the implicit instantiation of S<U>
; 这意味着
bar
的声明现在是一个定义,并且根据上一节中的引用,它没有被实例化为S<U>
的隐式实例化; only a declaration of bar
, which doesn't include the initializer, is instantiated at that time. 当时只实例化了一个不包含初始值设定项的
bar
声明。
The changes in this case are nicely summarized in the example in [depr.static_constexpr] in the current working draft: 在当前工作草案的[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)
This makes GCC's behaviour standard-conformant in C++1z mode. 这使得GCC的行为在C ++ 1z模式下符合标准。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.