本答案中提供的所有引用均来自当前工作草案。
根据你的例子:
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b;
return 0;
}
首先,让我们逐行分析你的例子。第一行是一个定义常量表达式变量的语句,变量名为标识符
a
:
constexpr int a = 10
初始化器
10
是一个整数常量表达式,因此它是一个核心常量表达式,根据
[expr.const]/9:
整数常量表达式是一个具有整数或未作用域枚举类型的表达式,隐式转换为prvalue,其中转换的表达式是核心常量表达式。[注意:这些表达式可以用作位域长度,如果基础类型不固定,则用作枚举器初始化程序,并用作对齐方式。 - end note]
它是一个
常量表达式,因为它是满足
[expr.const]/12约束的prvalue核心常量表达式:
常量表达式是一个prvalue核心常量表达式,其值满足以下约束条件:
- (12.1)如果该值是类类型的对象,则每个非静态数据成员引用的引用类型指向的实体是常量表达式的允许结果;
- (12.2)如果该值是指针类型,则它包含具有静态存储持续时间的对象的地址,这样一个对象的结尾之后的地址([expr.add])、非立即函数的地址或空指针值;
- (12.3)如果该值为指向成员函数的指针类型,则不指定立即函数;
- (12.4)如果该值是类或数组类型的对象,则每个子对象都符合该值的这些约束条件。
因此,初始化表达式`10`是一个既不是指针类型也不是类类型或数组类型的prvalue核心常量表达式。因此,它是一个常量表达式。
第二行声明/定义了一个名为标识符`b`的引用变量,它引用了标识符`a`。
const int &b = a;
为了知道初始化表达式
a
是否是核心常量表达式,您必须调用
[expr.const]/5:
除非按照抽象机器的规则([intro.execution])评估E将评估以下之一,否则表达式E
是核心常量表达式:[..]
此时,
E
的评估不会评估
[expr.const]/5中定义的任何约束。因此,表达式
E
是核心常量表达式。
但是,
E
是一个glvalue核心常量表达式,并不意味着
E
也是常量表达式;因此,您必须检查
[expr.const]/12:
一个常量表达式可以是一个glvalue核心常量表达式,它引用的实体是常量表达式的允许结果之一[...]如果一个实体是具有静态存储期的对象,它既不是临时对象,也是一个满足上述约束条件的临时对象的值,或者是一个非立即函数,则该实体是常量表达式的允许结果之一。
因此,实体a不是常量表达式的允许结果,因为该实体既不引用具有静态存储期的对象,也不是临时对象或非立即函数。因此,glvalue核心常量表达式a不是常量表达式。
第三行定义了一个名为标识符c的constexpr变量,由变量b初始化:
constexpr int c = b
你需要再次调用[expr.const]/5以了解表达式b
是否是核心常量表达式。正如你已经注意到的,因为初始化c
涉及lvalue-to-rvalue转换,所以可能会满足(5.8)这个条件。根据[expr.const]/5规定:
表达式E
是核心常量表达式,除非评估E
[...]会评估出以下之一:
- (5.8)一个lvalue-to-rvalue转换,除非它应用于
- (5.8.1) 一个指向在常量表达式中可用的对象的非易失glvalue;或者
- (5.8.2)一个指向在E的评估内开始生存期的非易失文字类型的对象的非易失glvalue;
请注意,我所提到的文档中没有以下项目符号。但是C++20文档有它。我不知道为什么它被从当前工作草案中删除了。(
[expr.const]/(5.12)):
(5.12)一个id表达式,它引用引用类型的变量或数据成员,除非该引用具有前置初始化并且满足以下条件之一:
(5.12.1)它可用于常量表达式;
(5.12.2)其生命周期始于E的评估内部;
即使检查过了,表达式E仍然不是核心常量表达式,因为该引用在常量表达式中不可用(见下文),并且其生命周期不会在E的评估内开始。
回到我们的解释。由于表达式E计算了lvalue-to-rvalue转换,因此满足项目符号(5.8)。因此首先尝试(5.8.1)。
实际上,左值到右值的转换应用于非易失性 glvalue(b
)。但是,这个 glvalue 是否指的是在常量表达式中可用的对象?首先,您必须检查它是否是一个常量初始化变量,然后还必须检查它是否是一个潜在常量变量,因此首先检查[expr.const]/2:
如果变量或临时对象o
被满足以下条件,则其为常量初始化:
- (2.1) 它具有初始化程序或其默认初始化导致执行某些初始化,并且
- (2.2) 其初始化的完整表达式在解释为常量表达式时是常量表达式[..]
第一个子弹点(2.1)
已经满足,因为变量b
具有初始化程序。但是第二个子弹点不满足,因为其初始化的完整表达式不是常量表达式,因为其初始化程序a
不是常量表达式,如前所述。因此,变量b
不是常量初始化。
仅为完整起见,[expr.const]/3也被尝试:
如果一个变量是constexpr
或具有引用或const限定的整数或枚举类型,则它可能是常量。
由于变量b
具有引用类型,因此它被认为是可能是常量的。请注意,无需检查变量b
是否为潜在常量,因为从直觉上讲,您甚至不能进入[expr.const]/4,因为它要求变量是常量初始化的:
常量初始化的可能常量变量可在某一点P处的常量表达式中使用,如果其初始化声明D可以从P到达[..]
(强调我自己)
尽管变量可能是常量,但由于它没有被常量初始化,因此无法在常量表达式中使用。因此,我们得出结论,变量b
不能在常量表达式中使用(正如GCC诊断提示的那样)。因此,子条款(5.8.1)
未满足:因此,表达式E
不是核心常量表达式,因此也不是常量表达式。