Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/templates/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 通过使用派生类的静态constexpr数据成员初始化基类的静态constexpr数据成员_C++_Templates_C++14_Constexpr_Crtp - Fatal编程技术网

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)