理解constexpr变量初始化的完整表达式

7
下面的程序在所有主要编译器上都可以成功编译
struct S {
    constexpr S(const S&){};
    constexpr S() = default;
};

int main(void) {
    S s1{};
    constexpr S s2{ s1 };
}

“constexpr”变量初始化的规则是[dcl.constexpr]/10: (重点在于)

在对象声明中使用“constexpr”修饰符将对象声明为const。这样的对象应具有字面类型并应进行初始化。 在任何“constexpr”变量声明中,初始化的完整表达式应为常量表达式(7.7)。 “constexpr”变量应具有恒定的销毁。

根据加粗部分,初始化的完整表达式应为常量表达式。 据我理解,这里的完整表达式是指[into.execution]/5中的init-declarator

完整表达式

  • [..] (5.4) init-declarator ([dcl.decl]) [..]

init-declarator 的语法规则规定,init-declarator 是一个 declarator 后跟一个 initializer

init-declarator:
    declarator initializer

根据这些信息,我们可以得出结论:初始化的完整表达式constexpr S s2{ s1 };s2{ s1 },其中s2是一个声明符{ s1 }是一个初始化器
现在,[dcl.constexpr]/10告诉我们,完整表达式(即s2{ s1 } init-declarator)必须是常量表达式。我卡在这一点上了。根据[expr.const]/11常量表达式是glvalue或prvalue核心常量表达式(带有一些附加限制)。
针对上面的例子,我发现自己将[dcl.constexpr]/10解读为:“完整表达式s2{s1}应该是glvalue或prvalue核心常量表达式”。那么,初始化声明符s2{s1}如何成为glvalue或prvalue核心常量表达式呢?正如您所看到的,我的解释导致了一个错误的理论。那么,我在这里错读/混淆了什么?
直观地说,我只是看到初始化声明符s2{s1}是一个带有s1作为参数的复制构造函数调用。但这不是我想要理解的问题。此外,为什么上面的程序是良好形式也不是我要问的。相反,我需要知道为什么我的解释没有得出合理的结论。

编辑

注意,我们在这个简单的例子(Demo)中也会遇到同样的问题:

int main(void) {
    static const int i = 42;
    constexpr int j = i; 
    constexpr const int &r = i;
}

constexpr int j = i; 的完整表达式是初始化声明符 j = i,其中 j 是一个声明符,= i 是一个初始化器。那么,如何将完整表达式 j = i 作为 prvalue 核心常量表达式?

constexpr const int &r = i; 的完整表达式是初始化声明符 &r = i,其中 &r 是一个声明符,= i 是一个初始化器。那么,如何将完整表达式 &r = i 作为 glvalue 核心常量表达式?


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Samuel Liew
1个回答

1
显然,标准中存在措辞上的差异。您的理解是正确的:标准要求constexpr变量的“初始化完整表达式”必须是“常量表达式”,而标准还规定“常量表达式”是“glvalue核心常量表达式”或“prvalue核心常量表达式”(每个表达式都必须满足一些进一步的要求),因此看起来标准告诉我们初始化的完整表达式并非真正的表达式,因此永远不能是glvalue或prvalue(因为值类别仅适用于表达式),既不是“glvalue核心常量表达式”也不是“prvalue核心常量表达式”,因此根本不是“常量表达式”。这种解释显然不是预期的,因为它将使得任何constexpr变量都无法存在。
实际上,编译器似乎只在[expr.const] / 11的目的下采用以下解释:
- 引用变量r的初始化是指代r的glvalue, - 非引用变量o的初始化是一个将其结果对象存储为o值的prvalue,并且 - 在这两种情况下,初始化的完整表达式包括各种由初始化所需的子表达式,例如初始化程序(如果有),初始化变量的构造函数调用(如果有),将初始化程序子句转换为聚合元素类型以及任何隐式调用转换函数。需要检查这个完整表达式作为核心常量表达式(即,我们必须将其视为[expr.const]/5中的E)。
任何其他解释都会极不直观并导致荒谬。

非正式地说,constexpr 引用变量的初始化必须符合“glvalue 核心常量表达式约束”,否则,初始化必须符合“prvalue 核心常量表达式约束”?[ 否则 = 非引用类型的任何 constexpr 变量的初始化 ] - mada
[expr.const]/5 以 "表达式 E 是核心常量表达式,除非..." 开始。那么在这个初始化语句 constexpr int j = i; 中,E 是什么?是 j = i 这个声明符吗? - mada
我的意思是,当我输入[expr.const]/5时,我需要知道E是什么。即使我在原帖中提出了这个问题,但你在回答中没有提到它。例如,在这个初始化constexpr int j = i;中,E是什么?请告诉我它是否等于j = i - mada
你感激地说初始化constexpr int j = i;是指定j的prvalue。那么接下来呢?你必须检查是否在此处评估了[expr.const]/5约束条件之一。要知道这一点,你必须知道E是什么。我在我的问题中提到了所有这些。那么你能为我完成答案吗? - mada
@mada 我在第三个要点中解释了这个问题。 "初始化的完整表达式" 包括了初始化所做的一切。这意味着对初始化器的评估,初始化器的左值到右值转换以及实际将初始值赋予 i。它不等同于 j = i(这是一个赋值语句)。 - Brian Bi
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接