Constant expressions
定义可在编译时求值的 表达式 。
此类表达式可用作常量模板参数、数组大小,以及其他需要常量表达式的场景,例如
int n = 1; std::array<int, n> a1; // 错误:“n”不是常量表达式 const int cn = 2; std::array<int, cn> a2; // 正确:“cn”是常量表达式
目录 |
定义
|
属于以下任一常量表达式类别的表达式即为 常量表达式 。
|
(C++11 前) | ||
|
以下表达式统称为 常量表达式 :
|
(C++11 起)
(C++14 前) |
||
|
以下实体是 常量表达式的允许结果 : 常量表达式 是指向常量表达式允许结果的左值 核心常量表达式 ,或值满足以下约束的纯右值核心常量表达式:
|
(C++14 起)
(C++26 前) |
||
| (C++26 起) |
在判断表达式是否为常量表达式时,假定 复制消除 不会被执行。
C++98 关于常量表达式的定义完全折叠在隐藏框内。以下描述适用于 C++11 及后续 C++ 版本。
字面类型
以下类型统称为 字面类型 :
-
- 它拥有 平凡析构函数 (C++20 前) constexpr 析构函数 (C++20 起) 。
- 其所有非静态非变体数据成员和基类均为非易失性字面类型。
- 它是以下类型之一:
|
(自 C++17 起) |
只有字面类型的对象才能在常量表达式中创建。
核心常量表达式
一个 核心常量表达式 是指其求值 不会 涉及以下任一语言结构的表达式:
| 语言结构 | 版本 | 文献 |
|---|---|---|
除作为表达式一部分被求值的
constexpr
函数
中,或出现在隐式或显式类成员访问表达式中的情况外,
this
指针
|
N2235 | |
| 控制流经过具有静态或线程 存储期 且 不可用于常量表达式 的 块作用域变量 声明 | (自 C++23 起) | P2242R3 |
|
本部分内容尚不完整
原因:请将下方原始HTML有序列表中的内容转移至上方的维基表格中,同时补充将对应条目引入标准的论文/CWG问题编号。迷你示例无需保留,可合并构成页面底部的大型示例。 |
-
调用未声明为
constexpr
的函数(或构造函数)的函数调用表达式
constexpr int n = std::numeric_limits<int>::max(); // 正确:max() 是 constexpr constexpr int m = std::time(nullptr); // 错误:std::time() 不是 constexpr
- 调用已声明但未定义的 constexpr 函数的函数调用
- 调用 constexpr 函数/构造函数模板实例化,但该实例化不满足 constexpr 函数/构造函数 要求
- 调用 constexpr 虚函数,且该调用作用于动态类型为 constexpr-未知 的对象
- 超出实现定义限制的表达式
-
求值会导致任何形式的核心语言
未定义
或错误
(C++26 起)
行为的表达式,除非是由
标准属性
引入的潜在未定义行为
constexpr double d1 = 2.0 / 1.0; // 正确 constexpr double d2 = 2.0 / 0.0; // 错误:未定义 constexpr int n = std::numeric_limits<int>::max() + 1; // 错误:溢出 int x, y, z[30]; constexpr auto e1 = &y - &x; // 错误:未定义 constexpr auto e2 = &z[20] - &z[3]; // 正确 constexpr std::bitset<2> a; constexpr bool b = a[2]; // 未定义行为,但未指定是否会被检测
- (C++17 前) lambda 表达式
-
左值到右值的
隐式转换
,除非应用于...
- 类型为(可能 cv 限定的) std::nullptr_t 的泛左值
-
非易失性字面类型的泛左值,且其指代的对象
可在常量表达式中使用
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // 正确:tabsize 是常量表达式 // 因为 tabsize 可在常量表达式中使用 // 因为它具有 const 限定的整型类型,且 // 其初始化器是常量初始化器 std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // 错误:sz 不是常量表达式 // 因为 sz 不可在常量表达式中使用 // 因为其初始化器不是常量初始化器 }
- 非易失性字面类型的泛左值,且其引用的非易失性对象的生存期始于该表达式的求值过程中
T*
的转换
,除非该指针持有空指针值或指向类型与
T
相似
的对象
(C++26 起)
dynamic_cast
,且其操作数是引用动态类型为 constexpr-未知 的对象的泛左值
(C++20 起)
reinterpret_cast
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // 错误:incr(k) 不是核心常量 // 表达式,因为 k 的生存期 // 始于表达式 incr(k) 之外 return x; } constexpr int h(int k) { int x = incr(k); // 正确:x 不需要用核心常量表达式初始化 // return x; } constexpr int y = h(1); // 正确:用值 2 初始化 y // h(1) 是核心常量表达式,因为 // k 的生存期始于表达式 h(1) 内部
typeid
表达式
,且该泛左值引用动态类型为 constexpr-未知 的对象
(C++20 起)
|
(C++20 起) |
|
额外要求
即使表达式 E 未评估上述任何情况,若对 E 的求值会导致 运行时未定义行为 ,则 E 是否为核心常量表达式由实现定义。
即使表达式 E 未计算上述任何内容,若计算 E 将计算以下任何操作,则 E 是否为核心常量表达式是未指定的:
为判断表达式是否为核心常量表达式,若
T
为字面类型,则忽略
std::
allocator
<
T
>
成员函数体的求值过程。
为判断表达式是否为核心常量表达式,对 联合体 的平凡复制/移动构造函数或复制/移动赋值运算符的调用,其求值过程被视为复制/移动该联合体的活跃成员(若存在)。
|
为确定表达式是否为核心常量表达式,对命名 结构化绑定 bd 的标识符表达式求值时遵循以下语义:
|
(since C++26) |
在将表达式作为核心常量表达式求值期间,所有引用生命周期始于该表达式求值之外的对象的标识符表达式及 * this 的使用,均被视为引用该对象或引用的特定实例,该实例及其所有子对象(包括所有联合体成员)的生命周期需涵盖整个常量求值过程。
- 对于此类 不可在常量表达式中使用的 (C++20 起) 对象,该对象的动态类型为 constexpr-unknown 。
- 对于此类 不可在常量表达式中使用的 (C++20 起) 引用,该引用被视为绑定到被引用类型的一个未指定对象,该对象及其所有子对象的生存期涵盖整个常量求值过程,且其动态类型为 constexpr-unknown。
整型常量表达式
整型常量表达式 是指整型或无作用域枚举类型的表达式,该表达式被隐式转换为纯右值,且转换后的表达式为核心常量表达式。
当需要整型常量表达式的地方使用了类类型的表达式时,该表达式会被 上下文隐式转换 为整型或无作用域枚举类型。
转换后的常量表达式
类型
T
的
转换后常量表达式
是指一个被
隐式转换
为类型
T
的表达式,其中被转换的表达式是常量表达式,且隐式转换序列仅包含:
|
(C++17 起) |
以下上下文需要转换后的常量表达式:
| (C++14 起) | |
| (C++26 起) |
一个 上下文转换为 bool 类型的常量表达式 是指: 被上下文转换为 bool 的表达式,其中被转换的表达式是常量表达式,且转换序列仅包含上述转换规则。
以下上下文需要类型为 bool 的上下文转换常量表达式:
| (直至 C++23) | |
|
(自 C++17 起)
(直至 C++23) |
|
| (自 C++20 起) |
组成实体对象 obj 的 组成值 定义如下: 对象 obj 的 组成引用 包括以下引用:
变量 var 的 组成值 和 组成引用 定义如下:
对于变量 var 的任何组成引用 ref ,若 ref 绑定到临时对象或其子对象,且该临时对象的生存期被延长至 ref 的生存期,则该临时对象的组成值和引用也是 var 的组成值和引用(递归地)。 常量表达式可表示实体具有静态存储期的对象在程序的任何点都是 常量表达式可引用 的。
具有自动存储期的对象
obj
在点
对象或引用
x
在点
|
(C++26 起) | |||||||
常量初始化实体
可用于常量表达式变量是 潜在常量 的,如果它是 constexpr 变量 ,或者它具有引用类型或非 volatile const 限定的整型或枚举类型。
常量初始化的潜在常量变量
var
在点
显式常量求值表达式以下表达式(包括到目标类型的转换)是 显式常量求值 的:
|
常量求值所需的函数与变量
以下表达式或转换属于 潜在常量求值 :
- 明显常量求值表达式
- 潜在求值表达式
- 花括号初始化列表 的直接子表达式(可能需要常量求值以确定 转换是否属于窄化转换 )
- 出现在 模板实体 内的取址表达式(可能需要常量求值以确定此类表达式是否属于 值依赖表达式 )
- 上述表达式的子表达式,且不属于嵌套 未求值操作数 的子表达式
一个函数在以下情况下 需要用于常量求值 :如果它是 constexpr 函数,且被可能进行常量求值的表达式 命名 。
一个变量在以下情况下 需要用于常量求值 :如果它是constexpr变量,或者是非volatile const限定的整型类型或引用类型,并且表示该变量的 标识符表达式 可能被常量求值。
默认函数的定义以及 函数模板 特化 或 变量模板 特化 (since C++14) 的实例化会在函数 或变量 (since C++14) 被常量求值需要时触发。
常量子表达式
一个 常量子表达式 是指这样的表达式:作为表达式 e 的子表达式进行求值时,不会阻碍 e 成为 核心常量表达式 ,其中 e 不属于以下任一表达式:
| (自 C++20 起) |
注释
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_constexpr_in_decltype
|
201711L
|
(C++20)
(DR11) |
在 常量求值需要时 生成函数和变量定义 |
__cpp_constexpr_dynamic_alloc
|
201907L
|
(C++20) | constexpr 函数中的动态存储期操作 |
__cpp_constexpr
|
202306L
|
(C++26) | constexpr 从 void * 转换:实现 constexpr 类型擦除 |
202406L
|
(C++26) | constexpr 布局 new 和 new [ ] | |
__cpp_constexpr_exceptions
|
202411L
|
(C++26) | constexpr 异常: [1] , [2] |
示例
|
本节内容不完整
原因:缺少示例 |
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用版本 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 94 | C++98 |
算术常量表达式不能
涉及变量和静态数据成员 |
允许涉及 |
| CWG 366 | C++98 |
涉及字符串字面量的表达式
可以是整型常量表达式 |
不允许 |
| CWG 457 | C++98 |
涉及volatile变量的表达式
可以是整型常量表达式 |
不允许 |
| CWG 1293 | C++11 |
未明确字符串字面量
是否可用于常量表达式 |
允许使用 |
| CWG 1311 | C++11 | volatile泛左值可用于常量表达式 | 禁止使用 |
| CWG 1312 | C++11 |
reinterpret_cast
在常量表达式中被禁止,
但转换到和自 void * 可实现相同效果 |
禁止从类型
cv
void
*
到
对象指针类型的转换 |
| CWG 1313 | C++11 |
允许未定义行为;
禁止所有指针减法 |
禁止UB;允许
同数组指针减法 |
| CWG 1405 | C++11 |
对于可在常量表达式中使用的对象,
其mutable子对象也可使用 |
不可使用 |
| CWG 1454 | C++11 |
不允许通过引用将常量
传递给constexpr函数 |
允许 |
| CWG 1455 | C++11 | 转换后的常量表达式只能是纯右值 | 可以是左值 |
| CWG 1456 | C++11 |
地址常量表达式不能
指定数组末尾后一位置的地址 |
允许 |
| CWG 1535 | C++11 |
操作数为多态类类型的
typeid
表达式
即使不涉及运行时检查也不是核心常量表达式 |
操作数约束
仅限于多态类类型的 泛左值 |
| CWG 1581 | C++11 |
常量求值所需的函数
不需要定义或实例化 |
需要 |
| CWG 1613 | C++11 |
核心常量表达式可以求值lambda表达式内
任何odr使用的引用 |
部分引用
无法求值 |
| CWG 1694 | C++11 |
将临时值绑定到静态存储
持续期引用是常量表达式 |
不是
常量表达式 |
| CWG 1872 | C++11 |
核心常量表达式可以调用不满足
constexpr
函数要求的
constexpr 函数模板实例化 |
此类实例化
无法调用 |
| CWG 1952 | C++11 |
标准库未定义行为
需要被诊断 |
未指定是否
需要诊断 |
| CWG 2022 | C++98 |
常量表达式的确定可能
取决于是否进行复制消除 |
假定始终进行
复制消除 |
| CWG 2126 | C++11 |
常量初始化的const限定字面类型的
生命周期延长临时对象不可用于常量表达式 |
可用 |
| CWG 2129 | C++11 | 整数字面量不是常量表达式 | 是常量表达式 |
| CWG 2167 | C++11 |
求值局部的非成员引用
使求值非constexpr |
允许非成员
引用 |
| CWG 2278 | C++98 | CWG 2022 的解决方案不可实现 |
假定从不进行
复制消除 |
| CWG 2299 | C++14 |
未明确
<cstdarg>
中的宏
是否可用于常量求值 |
va_arg
禁止使用,
va_start
未指定
|
| CWG 2400 | C++11 |
对不可用于常量表达式且其生命期
始于包含调用的表达式之外的对象 调用constexpr虚函数可以是常量表达式 |
不是
常量表达式 |
| CWG 2490 | C++20 |
(伪)析构函数调用在常量求值中
缺乏限制 |
已添加限制 |
| CWG 2552 | C++23 |
求值核心常量表达式时,控制流
不能通过非块变量的声明 |
允许通过 |
| CWG 2558 | C++11 | 不确定值可以是常量表达式 | 不是常量表达式 |
| CWG 2647 | C++20 | volatile限定类型的变量可以是潜在常量 | 不是潜在常量 |
| CWG 2763 | C++11 |
不要求在常量求值期间
检测
[[
noreturn
]]
的违反
|
要求检测 |
| CWG 2851 | C++11 |
转换后的常量表达式
不允许浮点转换 |
允许非窄化
浮点转换 |
| CWG 2907 | C++11 |
核心常量表达式不能对
std::nullptr_t
泛左值
应用左值到右值转换 |
允许应用此类
转换 |
| CWG 2909 | C++20 |
无初始化器的变量仅当其默认初始化
导致执行某些初始化时才能常量初始化 |
仅当其类型为
const默认可初始化时 才能常量初始化 |
| CWG 2924 |
C++11
C++23 |
未指定违反
[[
noreturn
]]
(C++11) 或
[[
assume
]]
(C++23) 约束的表达式
是否是核心常量表达式 |
由实现定义 |
| P2280R4 | C++11 |
求值包含标识符表达式或
*
this
的表达式,
若其引用生命期始于此次求值之外的对象或引用, 则不是常量表达式 |
可以是
常量表达式 |
参见
constexpr
说明符
(C++11)
|
指定变量或函数的值可在编译时计算 |
|
(C++11)
(C++17 中弃用)
(C++20 中移除)
|
检查类型是否为字面类型
(类模板) |
|
C 文档
关于
常量表达式
|
|