Constant expressions
定义一个可在编译时求值的 expression (表达式)。
此类表达式可用作常量模板参数、数组大小,以及其他需要常量表达式的场景,例如。
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 前) |
||
|
下列实体属于 常量表达式允许的结果 :
常量表达式 要么是一个指代常量表达式允许结果的 核心常量表达式 泛左值,要么是满足以下约束的纯右值核心常量表达式:
|
(since C++14)
(until C++26) |
||
|
常量表达式
要么是一个指代对象或非
|
(始于 C++26) |
在确定表达式是否为常量表达式时, copy elision 被假定为不会执行。
C++98 对常量表达式的定义完全包含在折叠框内。以下描述适用于 C++11 及后续 C++ 版本。
字面类型
以下类型统称为 literal types :
-
- 它拥有 平凡析构函数 (C++20 前) constexpr 析构函数 (C++20 起) 。
- 其所有非静态非变体数据成员和基类均为非易失性字面类型。
- 它是以下类型之一:
| (since C++17) |
只有字面类型的对象才能在常量表达式中创建。
核心常量表达式
core constant expression 是指其求值过程 不会 涉及以下任一语言结构的表达式:
| 语言结构 | 版本 | 文献 |
|---|---|---|
this
指针,除非在作为表达式组成部分被求值的
constexpr
函数
中,或出现在隐式或显式类成员访问表达式中
|
N2235 | |
| 控制流穿过具有静态或线程 存储期 且不 可用于常量表达式 的 块作用域变量 声明 | (C++23 起) | P2242R3 |
|
本部分内容尚不完整
原因:请将下方原始HTML有序列表中的内容转移至上方的维基表格中,同时添加将对应项目引入标准的论文/CWG问题。迷你示例不予保留,它们可以合并形成本页底部的一个大型示例。 |
-
a function call expression that calls a function (or a constructor) that is not declared
constexpr
constexpr int n = std::numeric_limits<int>::max(); // 正确:max() 是 constexpr 函数 constexpr int m = std::time(nullptr); // 错误:std::time() 不是 constexpr 函数
- a function call to a constexpr function which is declared, but not defined
- a function call to a constexpr function/constructor template instantiation where the instantiation fails to satisfy constexpr 函数/构造函数 requirements.
- a function call to a constexpr virtual function, invoked on an object whose dynamic type is constexpr-unknown
- an expression that would exceed the implementation-defined limits
-
an expression whose evaluation leads to any form of core language
undefined
或错误的
(始于 C++26)
behavior, except for any potential undefined behavior introduced by
标准属性
.
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 前) a lambda 表达式
-
an lvalue-to-rvalue
隐式转换
unless applied to...
- 类型为(可能带有 cv 限定符) std::nullptr_t 的泛左值
-
指定了
可在常量表达式中使用
的对象的非易失性字面类型的泛左值
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize 是常量表达式 // 因为 tabsize 可在常量表达式中使用 // 因为它具有 const 限定的整型,且 // 其初始化器是常量初始化器 std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // 错误:sz 不是常量表达式 // 因为 sz 不可在常量表达式中使用 // 因为其初始化器不是常量初始化器 }
- 引用其生存期始于该表达式求值内的非易失性对象的非易失性字面类型的泛左值
T*
除非该指针持有空指针值或指向其类型与
similar
于
T
的对象
(C++26 起)
dynamic_cast
其操作数为引用动态类型为constexpr未知对象的glvalue
(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
expression applied to a glvalue of polymorphic type
且该泛左值所引用的对象具有常量表达式未知的动态类型
(C++20 起)
|
(since C++20) |
|
(since C++26) |
constexpr void check(int i) { if (i < 0) throw i; } constexpr bool is_ok(int i) { try { check(i); } catch (...) { return false; } return true; } constexpr bool always_throw() { throw 12; return true; } static_assert(is_ok(5)); // 正确 static_assert(!is_ok(-1)); // C++26 起正确 static_assert(always_throw()); // 错误:未捕获异常
goto
语句
dynamic_cast
或
typeid
表达式
或
new
表达式
(C++26 起)
会抛出异常的情况
且异常类型定义不可达时
(C++26 起)
void g() { const int n = 0; constexpr int j = *&n; // 正确:在 lambda 表达式外 [=] { constexpr int i = n; // 正确:'n' 未在此处被 ODR 使用且未被捕获 constexpr int j = *&n; // 错误:'&n' 将构成对 'n' 的 ODR 使用 }; }
|
注意:若 ODR 使用发生在对闭包的函数调用中,则不会引用 this 或外部变量,因为此时访问的是闭包的数据成员 // 正确:'v' 和 'm' 被 ODR 使用但未在嵌套 lambda 的常量表达式中出现 // auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // 允许捕获在常量表达式求值期间创建的自动对象 static_assert(bind(monad(2))(monad)() == monad(2)()); |
(C++17 起) |
额外要求
即使表达式 E 未出现上述任何求值情形,若对 E 进行求值会导致 运行时未定义行为 ,则 E 是否为核心常量表达式由实现定义。
即使表达式 E 未计算上述任何内容,若对 E 进行求值将涉及以下任意情形,则 E 是否为核心常量表达式是未指定的:
- 标准库中具有未定义行为的操作。
- 对 va_start 宏的调用。
为确定表达式是否为核心常量表达式,若
T
为字面类型,则忽略
std::
allocator
<
T
>
成员函数体的求值过程。
为确定表达式是否为核心常量表达式,对 union 的平凡拷贝/移动构造函数或拷贝/移动赋值运算符的调用,其求值过程被视为拷贝/移动该union的活动成员(如果存在)。
|
为确定表达式是否为核心常量表达式,对命名 结构化绑定 bd 的标识符表达式求值时遵循以下语义:
|
(C++26 起) |
在将表达式作为核心常量表达式求值期间,所有标识符表达式以及使用 * this 引用其生命周期始于表达式求值之外的对象或引用的情形,均被视为引用该对象或引用的特定实例,该实例及其所有子对象(包括所有联合成员)的生命周期涵盖整个常量求值过程。
- 对于这样的对象 不可在 常量表达式中使用 (C++20 起) 的,该对象的动态类型为 constexpr-unknown 。
- 对于这样的引用 不可在常量表达式中使用 (C++20 起) 的,该引用被视为绑定到被引用类型的一个未指定对象,该对象及其所有子对象的生存期涵盖整个常量求值过程,且其动态类型为 constexpr-unknown。
整型常量表达式
整型常量表达式 是指隐式转换为纯右值的整型或无作用域枚举类型表达式,其中转换后的表达式为核心常量表达式。
如果需要在期望整型常量表达式的地方使用类类型的表达式,该表达式将被 上下文隐式转换 为整型或无作用域枚举类型。
转换后的常量表达式
转换后的常量表达式
是指一个表达式被
隐式转换
为类型
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
在点
显式常量求值表达式下列表达式(包括到目标类型的转换)是 明显常量求值 :
是否在明显常量求值上下文中进行评估可以通过
std::is_constant_evaluated
和
|
(自 C++20 起) |
常量求值所需的函数与变量
下列表达式或转换是 可能被常量求值的 :
- 显式常量求值表达式
- 潜在求值表达式
- 花括号初始化列表 的直接子表达式 (可能需要常量求值以确定 转换是否属于窄化转换 )
- 出现在 模板实体 内的取址表达式 (可能需要常量求值以确定此类表达式是否属于 值依赖表达式 )
- 上述表达式的子表达式中,不属于嵌套 未求值操作数 子表达式的部分
函数在以下情况下 需要用于常量求值 :如果它是 constexpr 函数,且 被可能进行常量求值的表达式 所命名。
变量在以下情况下 需要进行常量求值 :如果它是 constexpr 变量,或者是非 volatile const 限定的整型类型或引用类型,且 标识符表达式 所指向的内容可能被常量求值。
默认函数的定义以及 函数模板 特化的实例化 或 变量模板 特化 (C++14 起) 会在该函数 或变量 (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++ 标准。
| DR | 适用范围 | 发布时行为 | 正确行为 |
|---|---|---|---|
| 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 |
允许未定义行为;
禁止所有指针减法运算 |
禁止未定义行为;同数组
指针减法运算允许 |
| CWG 1405 | C++11 |
对于可在常量表达式中使用的对象,
其可变子对象也可使用 |
它们不可使用 |
| 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 |
求值中的局部非成员引用
导致该求值无法成为常量表达式 |
允许非成员
引用 |
| 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 |
没有初始化器的变量仅当其默认初始化
会导致某些初始化操作被执行时 才能进行常量初始化 |
仅当其类型为
常量默认可初始化时 才能进行常量初始化 |
| 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 文档
关于
常量表达式
|
|