6.6 常量表达式
IMPORTANT
“常量表达式”不是简单的“看起来像常量”。标准在这里把它分成了若干层:整数常量表达式、算术常量表达式、地址常量、命名常量、复合字面量常量。很多能在初始化器里出现的东西,未必能出现在位字段宽度或枚举值的位置。
6.6 基本定义
常量表达式的语法就是条件表达式。
常量表达式可以在翻译期求值,而不是等到运行期;因此它可以用于那些要求“必须是常量”的上下文。
约束
常量表达式中不得包含以下运算符:
- 赋值运算符;
- 自增运算符;
- 自减运算符;
- 函数调用运算符;
- 逗号运算符。
但如果这些运算符只出现在不会被求值的子表达式中,则允许存在。
每个常量表达式最终都必须求值为一个常量,并且该值必须落在其类型的可表示范围内。
NOTE
typeof、sizeof、alignof 的操作数通常不求值,所以相关子表达式里即使出现平常受限的东西,也不一定违反本节约束。
语义总则
某些上下文要求表达式必须能在翻译期求值。
如果某个浮点表达式是在翻译环境中求值的,那么其算术范围和精度至少应与执行环境中求值时一样高。
带
constexpr存储类说明符的复合字面量,是复合字面量常量。对结构体或联合体类型的这种常量继续使用.成员访问运算符,递归地仍然得到复合字面量常量。下列实体属于命名常量:
- 枚举常量;
- 预定义常量;
- 使用
constexpr声明且具有对象类型的对象。
对结构体或联合体类型的命名常量继续使用
.成员访问运算符,递归地仍然得到命名常量。
整数常量表达式
整数常量表达式必须具有整数类型。
它只能使用下列操作数:
- 整数常量;
- 整数类型的命名常量与复合字面量常量;
- 字符常量;
- 结果为整数常量的
sizeof表达式; alignof表达式;- 作为强制转换直接操作数出现的浮点、命名或复合字面量算术常量。
在整数常量表达式里,强制转换只能把算术类型转换为整数类型;但若该强制转换处于
typeof、sizeof或alignof的操作数内部,则不受这条限制。典型要求整数常量表达式的场景包括:
- 位字段宽度;
- 枚举常量值;
- 非变长数组的大小。
初始化器中的常量表达式
初始化器中的常量表达式允许更宽。
这类常量表达式必须是下列之一,或求值为下列之一:
- 命名常量;
- 复合字面量常量;
- 算术常量表达式;
- 空指针常量;
- 地址常量;
- 完整对象类型的地址常量再加或减一个整数常量表达式。
算术常量表达式
算术常量表达式必须具有算术类型。
它只能使用下列操作数:
- 整数常量;
- 浮点常量;
- 算术类型的命名常量或复合字面量常量;
- 字符常量;
- 结果为整数常量的
sizeof表达式; alignof表达式。
在算术常量表达式中,强制转换只能在算术类型之间进行;但如果该强制转换位于
typeof、sizeof或alignof的操作数内部,则不受这条限制。
地址常量
地址常量可以是:
- 空指针;
- 指向静态存储期对象左值的指针;
- 指向函数设计符的指针。
地址常量必须通过以下方式显式或隐式形成:
- 显式使用一元
&; - 把整数常量强制转换为指针类型;
- 或隐式通过数组类型/函数类型表达式形成。
- 显式使用一元
在构造地址常量时,可以使用:
- 下标运算符
[] - 成员访问
-> - 一元
& - 一元
* - 指针强制转换
- 下标运算符
但即使可以使用这些运算符,也不能借此访问对象的值本身;这里允许的是构造地址,不是取对象内容。
结构体、联合体与成员访问
结构体常量或联合体常量,是具有结构体或联合体类型的命名常量或复合字面量常量。
从结构体或联合体常量出发,可以继续用
.运算符形成前面说的命名常量或复合字面量常量。如果
.运算符访问的是联合体常量的成员,那么被访问成员必须与该联合体初始化时所初始化的成员相同。
其他说明
实现可以接受其他形式的常量表达式;但这些额外形式是否也算整数常量表达式,由实现定义。
常量表达式的求值语义,与普通非常量表达式的求值语义相同。
标准脚注中的重要提醒
例如下面这个初始化:
static int i = 2 || 1 / 0;它仍然是一个有效的整数常量表达式,值为 1。因为右边的 1 / 0 位于不会被求值的分支中。