常量表达式
定义一个可以在编译时求值的表达式。
此类表达式可用作非类型模板参数、数组大小以及其他需要常量表达式的上下文,例如
int n = 1; std::array<int, n> a1; // error: n is not a constant expression const int cn = 2; std::array<int, cn> a2; // OK: cn is a constant expression
目录 |
[编辑] 核心常量表达式
核心常量表达式是指其求值不会求值以下任何一项的任何表达式
this
指针,除非是在作为表达式一部分进行求值的 constexpr 函数 中- 穿过具有静态或线程局部存储期的变量声明的控制流,并且在常量表达式中不可用
- 调用未声明为 constexpr 的函数(或构造函数)的函数调用表达式
constexpr int n = std::numeric_limits<int>::max(); // OK: max() is constexpr constexpr int m = std::time(nullptr); // Error: std::time() is not constexpr
- 对已声明但未定义的 constexpr 函数的函数调用
- 对 constexpr 函数/构造函数模板实例化的函数调用,其中实例化未能满足 constexpr 函数/构造函数 的要求。
- 对 constexpr 虚函数的函数调用,在常量表达式中不可用且其生命周期开始于此表达式之外的对象上调用。
- 会超出实现定义限制的表达式
- 其求值会导致任何形式的核心语言未定义或错误(自 C++26 起)行为的表达式,但标准属性引入的任何潜在未定义行为除外(自 C++11 起)。
constexpr double d1 = 2.0 / 1.0; // OK constexpr double d2 = 2.0 / 0.0; // Error: not defined constexpr int n = std::numeric_limits<int>::max() + 1; // Error: overflow int x, y, z[30]; constexpr auto e1 = &y - &x; // Error: undefined constexpr auto e2 = &z[20] - &z[3]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // UB, but unspecified if detected
- (直到 C++17) lambda 表达式
- 左值到右值的隐式转换,除非应用于非易失性字面量类型左值,该左值...
- 指定可在常量表达式中使用的对象,
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize is a constant expression // because tabsize is usable in constant expressions // because it has const-qualified integral type, and // its initializer is a constant initializer std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // error: sz is not a constant expression // because sz is not usable in constant expressions // because its initializer was not a constant initializer }
- 引用其生命周期开始于此表达式求值期间的非易失性对象
- 指定可在常量表达式中使用的对象,
- 应用于联合或其子对象的非活动成员的左值到右值的隐式转换或修改(即使它与活动成员共享一个共同的初始序列)
- 对其值不确定的对象进行左值到右值的隐式转换
- 对生命周期开始于此表达式求值之外的联合(如果存在)的隐式复制/移动构造函数/赋值的调用,其活动成员是可变的
- (直到 C++20) 会更改联合的活动成员的赋值表达式
- 引用变量或引用类型数据成员的 id 表达式,除非该引用可在常量表达式中使用或其生命周期开始于此表达式的求值期间
- 从指向 void 的指针转换为指向对象类型
T*
的指针,除非该指针持有空指针值或指向其类型与T
相似 的对象(自 C++26 起) - (直到 C++20)
dynamic_cast
-
reinterpret_cast
- (直到 C++20) 伪析构函数调用
- (直到 C++14) 自增或自减运算符
-
(自 C++14 起) 对象的修改,除非该对象具有非易失性字面量类型并且其生命周期开始于表达式的求值期间
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // error: incr(k) is not a core constant // expression because lifetime of k // began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: x is not required to be initialized // with a core constant expression return x; } constexpr int y = h(1); // OK: initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside the expression h(1)
- (自 C++20 起) 对其生命周期未开始于此表达式求值期间的对象的析构函数调用或伪析构函数调用
- (直到 C++20) 应用于多态类型左值的
typeid
表达式 - new 表达式,除非满足以下条件之一:(自 C++20 起)
- 选定的分配函数是可替换的全局分配函数,并且分配的存储在此表达式的求值期间被释放。
(自 C++20 起) - 选定的分配函数是非分配形式,其分配类型为
T
,并且放置参数满足以下所有条件
- 它指向与以下内容指针可互换的对象
- 如果
T
不是数组类型,则为T
类型的对象,或 - 如果
T
是数组类型,则为T
类型对象的第一个元素。
- 如果
- 它指向其持续时间开始于此表达式求值期间的存储。
(自 C++26 起) - delete 表达式,除非它释放了在此表达式求值期间分配的存储区域(自 C++20 起)
- (自 C++20 起) 协程:await 表达式或 yield 表达式
- (自 C++20 起) 当结果未指定时进行三路比较
- 结果未指定的相等或关系运算符
- (直到 C++14) 赋值或复合赋值运算符
- throw 表达式
- 一个 asm-声明
- 对 va_arg 宏的调用
- 一个
goto
语句 - 一个会抛出异常的
dynamic_cast
或typeid
表达式 - 在 lambda 表达式内部,对 this 或在 lambda 外部定义的变量的引用,如果该引用是 odr-use
void g() { const int n = 0; constexpr int j = *&n; // OK: outside of a lambda-expression [=] { constexpr int i = n; // OK: 'n' is not odr-used and not captured here. constexpr int j = *&n; // Ill-formed: '&n' would be an odr-use of 'n'. }; }
注意,如果 ODR-use 发生在对闭包的函数调用中,它不会引用 this 或封闭变量,因为它访问的是闭包的数据成员
// OK: 'v' & 'm' are odr-used but do not occur in a constant-expression // within the nested lambda auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // OK to have captures to automatic objects created during constant expression evaluation. static_assert(bind(monad(2))(monad)() == monad(2)());
(自 C++17 起)
即使表达式 E 没有计算上述任何内容,如果计算 E 会计算以下任何一项,则 E 是否为核心常量表达式是未指定的
|
(自 C++11 起) |
|
(自 C++23 起) |
本节不完整 原因:需要更多的小例子,更少的标准术语 |
注意:仅仅是核心常量表达式没有任何直接的语义含义:表达式必须是常量表达式的子集之一(见下文)才能在某些上下文中使用。
[编辑] 常量表达式
常量表达式 是
- 左值(直到 C++14)泛左值(自 C++14 起) 核心常量表达式,它指的是
- 具有静态存储期的非临时对象,或
|
(自 C++14 起) |
- 一个 非立即(自 C++20 起) 函数
- 一个纯右值核心常量表达式,其值满足以下约束
- 如果该值是类类型的对象,则每个非静态引用类型数据成员都引用一个满足上述对 左值(直到 C++14)泛左值(自 C++14 起) 的约束的实体
- 如果该值是标量类型的对象,则它没有 不确定 或错误(自 C++26 起) 值
- 如果该值是指针类型,则它持有
- 具有静态存储期的对象的地址
- 具有静态存储期的对象末尾之后的地址
- 一个 非立即(自 C++20 起) 函数的地址
- 空指针值
|
(自 C++20 起) |
- 如果该值是类或数组类型的对象,则每个子对象都满足这些对值的约束
本节不完整 原因:需要非整数/转换常量表达式的上下文列表? |
void test() { static const int a = std::random_device{}(); constexpr const int& ra = a; // OK: a is a glvalue constant expression constexpr int ia = a; // Error: a is not a prvalue constant expression const int b = 42; constexpr const int& rb = b; // Error: b is not a glvalue constant expression constexpr int ib = b; // OK: b is a prvalue constant expression }
[编辑] 整型常量表达式
整型常量表达式 是一个隐式转换为纯右值的整型或无作用域枚举类型表达式,其中转换后的表达式是核心常量表达式。如果在需要整型常量表达式的地方使用类类型表达式,则该表达式将被 上下文隐式转换 为整型或无作用域枚举类型。
以下上下文需要整型常量表达式
(直到 C++14) |
[编辑] 转换后的常量表达式
类型为 T
的转换后的常量表达式 是一个 隐式转换为 类型 T
的表达式,其中转换后的表达式是一个常量表达式,并且隐式转换序列仅包含
|
(自 C++17 起) |
以下上下文需要转换后的常量表达式
(自 C++14 起) | |
(自 C++26 起) |
类型为 bool 的上下文转换常量表达式 是一个表达式,上下文转换为 bool,其中转换后的表达式是一个常量表达式,并且转换序列仅包含上述转换。
以下上下文需要类型为 bool 的上下文转换常量表达式
(直到 C++23) | |
(自 C++17 起) (直到 C++23) | |
(自 C++20 起) |
[编辑] 历史类别
自 C++14 起,标准中不再使用以下列出的常量表达式类别
- 字面常量表达式 是非指针字面类型的纯右值核心常量表达式(经过上下文所需的转换)。数组或类类型的字面常量表达式要求每个子对象都用常量表达式初始化。
- 引用常量表达式 是一个左值核心常量表达式,它指定一个具有静态存储持续时间或函数的对象。
- 地址常量表达式 是一个纯右值核心常量表达式(经过上下文所需的转换),其类型为 std::nullptr_t 或指针类型,它指向一个具有静态存储持续时间的对象,指向一个具有静态存储持续时间的数组末尾之后的一个位置,指向一个函数,或者是一个空指针。
常量子表达式常量子表达式 是一个表达式,其作为表达式 e 的 子表达式 进行求值不会阻止 e 成为 核心常量表达式,其中 e 不是以下任何表达式
|
(自 C++17 起) |
[编辑] 可在常量表达式中使用
在上面的列表中,如果一个变量满足以下条件,则它在点 P
处可在常量表达式中使用
- 该变量是
- 一个 constexpr 变量,或者
- 它是一个 常量初始化 的变量
- 引用类型或
- const 限定的整数或枚举类型
- 并且可以从
P
访问到该变量的定义
(自 C++20 起) |
如果一个对象或引用满足以下条件,则它可在常量表达式中使用
- 一个可在常量表达式中使用的变量,或者
|
(自 C++20 起) |
const std::size_t sz = 10; // sz is usable in constant expressions
[编辑] 显式常量求值表达式
以下表达式(包括到目标类型的转换)是显式常量求值 的
- 数组边界
- new 表达式 中除第一个以外的维度
- 位字段 长度
- 枚举 初始化器
- 对齐
- case 标签 的 常量表达式
- 非类型 模板参数
- noexcept 规范 中的表达式
- static_assert 声明 中的表达式
- constexpr 变量 的初始化器
|
(自 C++17 起) |
|
(自 C++20 起) |
- 引用类型或 const 限定的整数或枚举类型的变量的初始化器,但前提是初始化器是常量表达式
- 静态和线程局部变量的初始化器,但前提是初始化器的所有子表达式(包括构造函数调用和隐式转换)都是常量表达式(即,如果初始化器是 常量初始化器)
求值是否发生在显式常量求值上下文中可以通过 std::is_constant_evaluated 和 为了测试最后两个条件,编译器可能会首先对初始化器执行试探性常量求值。在这种情况下,不建议依赖结果。 int y = 0; const int a = std::is_constant_evaluated() ? y : 1; // Trial constant evaluation fails. The constant evaluation is discarded. // Variable a is dynamically initialized with 1 const int b = std::is_constant_evaluated() ? 2 : y; // Constant evaluation with std::is_constant_evaluation() == true succeeds. // Variable b is statically initialized with 2 |
(自 C++20 起) |
[编辑] 常量求值所需的函数和变量
以下表达式或转换是潜在常量求值 的
- 显式常量求值表达式
- 潜在求值表达式
- 花括号括起来的初始化器列表 的直接子表达式(可能需要常量求值来确定 转换是否缩窄)
- 出现在 模板化实体 中的取地址(一元
&
)表达式(可能需要常量求值来确定这样的表达式是否是 值依赖的) - 上述之一的子表达式,它不是嵌套的 未求值操作数 的子表达式
如果一个函数是 constexpr 函数,并且被一个潜在常量求值的表达式 命名,则该函数是常量求值所需的。
如果一个变量是 constexpr 变量,或者是非易失性 const 限定的整数类型,或者是指针类型,并且表示它的 id 表达式 是潜在常量求值的,则该变量是常量求值所需的。
如果需要函数或变量(自 C++14 起)进行常量求值,则会触发默认函数的定义和函数模板特化的实例化或变量模板特化(自 C++14 起)。
[编辑] 注释
除非标准规定函数是constexpr,否则不允许将库函数声明为constexpr。 命名返回值优化 (NRVO) 在常量表达式中是不允许的,而返回值优化 (RVO) 是强制性的。
特性测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_constexpr_in_decltype |
201711L | (C++11) (DR) |
在需要进行常量求值时生成函数和变量定义 |
__cpp_constexpr_dynamic_alloc |
201907L | (C++20) | 在constexpr函数中进行动态存储持续时间的操作 |
__cpp_constexpr |
202306L | (C++26) | 从void*进行constexpr转换:迈向constexpr类型擦除 |
202406L | (C++26) | constexpr placement new 和 new[] |
[编辑] 缺陷报告
以下行为更改缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布时的行为 | 正确行为 |
---|---|---|---|
CWG 1293 | C++11 | 未指定字符串字面量 是否可在常量表达式中使用 |
它们可用 |
CWG 1311 | C++11 | volatile glvalues 可在常量表达式中使用 | 禁止 |
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 | 地址常量表达式不能 指定数组末尾之后的地址 |
允许 |
可以 | C++11 | CWG 1535 操作数为 多态类类型的typeid表达式不是核心常量 |
表达式,即使不涉及运行时检查 操作数约束 仅限于多态类类型的 glvalues |
CWG 1581 | C++11 | 常量求值所需的函数 不需要定义或实例化 |
需要 |
CWG 1694 | C++11 | 将临时值绑定到静态存储 持续时间引用是常量表达式 |
它不是 常量表达式 |
CWG 1952 | C++11 | 需要诊断标准库未定义行为 |
未指定是否 诊断它们 |
CWG 2126 | C++11 | 常量初始化的 const 限定字面量类型的 生命周期扩展的临时对象不可在常量表达式中使用 |
可用 |
CWG 2167 | C++11 | 求值中局部的非成员引用 使求值成为非 constexpr |
允许非成员 引用 |
CWG 2299 | C++14 | 不清楚<cstdarg>中的宏 是否可在常量求值中使用 |
禁止 va_arg ,va_start 未指定 |
CWG 2400 | C++11 | 在不可在常量表达式中使用的对象上调用 constexpr 虚函数,并且其生命周期开始于包含调用的表达式之外,这可能是常量表达式 不是 |
它不是 常量表达式 |
CWG 2418 | C++11 | 未指定哪些不是变量的对象或引用 可在常量表达式中使用 |
已指定 |
CWG 2490 | C++20 | (伪)析构函数调用缺少 在常量求值中的限制 |
已添加限制 |
CWG 2558 | C++11 | 不确定值可能是常量表达式 | 不是常量表达式 |
CWG 2763 | C++11 | 不需要在常量求值期间检测到违反 [[noreturn]] 的情况 |
需要 |
需要 | C++11 | CWG 2851 转换后的常量表达式不允许 |
浮点转换 允许非窄化 |
浮点转换
[编辑] 另请参阅 | constexpr 说明符(C++11) |
is_literal_type |
(C++11)(在 C++17 中弃用)(在 C++20 中移除) 检查类型是否为字面量类型 |
(类模板)
|