C++ 通过使用派生类的静态constexpr数据成员初始化基类的静态constexpr数据成员
考虑以下代码:C++ 通过使用派生类的静态constexpr数据成员初始化基类的静态constexpr数据成员,c++,templates,c++14,constexpr,crtp,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() { } 模板 结构S{static constexpr int bar=T::foo;}; 结构U:S{static constexpr int foo=42;}; int main(){}
template<typename T>
struct S { static constexpr int bar = T::foo; };
struct U: S<U> { static constexpr int foo = 42; };
int main() { }
模板
结构S{static constexpr int bar=T::foo;};
结构U:S{static constexpr int foo=42;};
int main(){}
编译它,拒绝它,并出现以下错误:
2:错误:“U”中没有名为“foo”的成员结构S{static constexpr int bar=T::foo;} 哪个编译器是正确的?
这可能是因为我们试图在
S
中使用U
这个点吗?在这种情况下,它应该被认为是一个错误的GCC,但我想知道我是否正确之前,搜索/打开一个问题的错误跟踪 编辑 同时,我已经为GCC打开了一个新的窗口。
等待它接受答案。对于C++14和11,Clang是正确的;然而,在最新的工作草案(未来的C++17)中,情况发生了变化——请参阅下一节 要查找的标准引号是(来自N4140,最接近C++14的草稿): [临时安装]/1: […]类模板专门化的隐式实例化 导致声明的隐式实例化,而不是 的定义、默认参数或异常规范 类成员函数、成员类、作用域成员枚举、, 静态数据成员和成员模板;[……] [温度点]/4: 对于类模板专门化,[…]实例化点 因为这样的专门化直接位于名称空间范围之前 引用专门化的声明或定义 因此,
S
的实例化点正好在U
的声明之前,只有一个前向声明struct U
概念上插入在前面,以便找到名称U
[class.static.data]/3:
[…]文本类型的静态数据成员可以在
使用constexpr
说明符的类定义;若有,原因是什么?
声明应指定一个括号或相等的初始值设定项,其中
作为赋值表达式的每个初始值设定项子句都是
恒定表达式。[……]该成员仍应在
命名空间范围(如果在程序和
命名空间范围定义不应包含初始值设定项
根据上面引用的段落,在S
的定义中bar
的声明,即使它有一个初始值设定项,仍然只是一个声明,而不是一个定义,因此它是在隐式实例化S
时实例化的,当时没有U::foo
一个解决方法是将条
变成一个函数;根据第一个引号,函数的定义在隐式实例化s
时不会被实例化。只要您在看到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");
}
这使得GCC的行为符合C++1z模式的标准。标准工作草案的最新一批更新包括[9.2.3.2p3]中与内联变量新概念相关的更改(
constexpr
变量现在隐式内联,就像函数一样),因此C++17的答案可能会改变;对于C++14及以下版本,当前的方法仍然适用。我将等待最新版本的规范在官方邮件中发布,然后用C++17的特定信息更新答案。@bogdan哇,非常感谢。非常感谢。@bogdan哇,谢谢你!不幸的是,我不能两次投票给你,但我可以接受答案,因为我上次忘了这么做。对不起,再次谢谢你,不用担心。干杯我认为没有插入任何U
的前向声明,也不需要插入:S
不需要查找名称U
,只需要名称t
,它是概念上插入的类U
@DavisHerring的typedef名称。这只是思考专业化是如何产生的一种方式;编译器不必这样做,但这是考虑与标准规则一致的实例化点的一种方式(当然,允许使用特殊的依赖名称查找规则等等)。这不是我自己编的,我见过编译器编写人员使用这个模型来讨论这个问题。@DavisHerring,即使您更愿意考虑使用t
作为typedef名称,它在生成每个专门化之前都会在概念上重新定义,以便定义该别名,您需要对它之前可见的别名进行声明,因此您仍然需要类U
的前向声明。这些特殊的依赖名称规则对(例如)在其自身定义中用作模板参数的函数f
是否“声明”很敏感为了被如此引用,我很早就开始了。如果模板依次尝试通过依赖ADL查找f
,如果f
尚未声明,则为格式错误的NDR。不过,对于一个类来说,这可能无关紧要,尽管我不同意typedef名称的目标必须存在声明;考虑<代码>使用x= DeCyType(new int **);,其中X
指定了一个从未声明过且从未使用过的类型。@DavisHerring Yes,因此“允许特殊的依赖名称查找规则等”。这个概念性声明仅仅是可视化被替换模板参数的一种简单方法,仅此而已。它不应该被视为影响其他任何东西(如果它的行为像一个真正的声明,我可以想到至少有一个人为的场景也会影响类名,但这不是重点)。关于别名,“它的别名”是我
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
const int A::n; // redundant declaration (definition in C++ 2014)