常量表达式
定义能在编译时求值的表达式。
这种表达式能用作常量模板实参、数组大小,并用于其他要求常量表达式的语境,例如
int n = 1; std::array<int, n> a1; // 错误:“n” 不是常量表达式 const int cn = 2; std::array<int, cn> a2; // OK:“cn” 是常量表达式
目录 |
[编辑] 定义
属于下列常量表达式类别之一的表达式是常量表达式。
|
(C++11 前) | ||
以下表达式统称为常量表达式:
|
(C++11 起) (C++14 前) | ||
以下实体是允许的常量表达式结果: 常量表达式 要么是指代某个允许的常量表达式结果的泛左值核心常量表达式,要么是其值满足以下约束的纯右值核心常量表达式:
|
(C++14 起) (C++26 前) | ||
(C++26 起) |
当确定表达式是否为常量表达式时始终会假设不进行复制消除。
C++98 的常量表达式定义全部在上面的折叠盒中。以下描述适用于 C++11 以及后续 C++ 版本。
[编辑] 字面类型
字面类型 是下列之一:
- 它拥有平凡析构函数(C++20 前) constexpr 析构函数(C++20 起),
- 它的所有非静态非可变数据成员和基类都具有非 volatile 字面类型。
- 它是以下类型之一:
(C++17 起) |
常量表达式中只能创建具有字面类型的对象。
[编辑] 核心常量表达式
核心常量表达式 是求值过程中不会对下列任一语言构造求值的表达式:
语言构造 | 版本 | 文档 |
---|---|---|
this 指针,除了在作为该表达式的一部分而求值的 constexpr 函数之中,或者在隐式或显式的类成员访问表达式之中出现
|
N2235 | |
经过具有静态或线程存储期且不可用于常量表达式的块变量的声明的控制流 | (C++23 起) | P2242R3 |
本节未完成 原因:将内容从下方原始 HTML 有序号列表转移到上方表格,并且加上相应文档。将所有迷你示例转移到最后,整合为大型示例。 |
- 调用未声明为 constexpr 的函数(或构造函数)的函数调用表达式
constexpr int n = std::numeric_limits<int>::max(); // OK:max() 是 constexpr constexpr int m = std::time(NULL); // 错误:std::time() 非 constexpr
- 调用已声明但未定义的 constexpr 函数
- 调用实例化无法满足constexpr 函数/构造函数要求的 constexpr 函数/构造函数模板
- 调用 constexpr 虚函数,所用对象的动态类型是 constexpr 未知的
- 会超出实现定义的极限的表达式
- 导致任何形式的核心语言未定义行为或错误行为(C++26 起)的操作,不包括由标准属性引入的潜在未定义行为
constexpr double d1 = 2.0 / 1.0; // OK 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]; // OK constexpr std::bitset<2> a; constexpr bool b = a[2]; // 行为未定义,但未指定是否检测
- (C++17 前) lambda 表达式
- 左值到右值隐式转换,除非应用于:
- (可有 cv 限定的)std::nullptr_t 类型的泛左值
- 指代可用于常量表达式的对象的非 volatile 字面类型的泛左值
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 不可用于常量表达式 // 因为它的初始化器不是常量初始化器 }
- 指代生命期始于此表达式的求值之内的非 volatile 对象的非 volatile 字面类型的泛左值
- 对联合体的不活跃成员或它们的子对象实施的左值到右值隐式转换或修改操作(即使它与活跃成员共享公共起始序列也是如此)
- (C++20 起) 应用到拥有不确定值的对象的左值到右值隐式转换
- 对活跃成员(如果存在)是 mutable 的联合体调用隐式定义的复制/移动构造函数或复制/移动赋值运算符,除非该联合体对象的生存期始于此表达式的求值之内
- (C++20 前) 会更改联合体的活跃成员的赋值表达式
- 从 void 指针转换到对象指针类型
T*
,除非这个指针持有空指针值或者指向一个与类型T
相似的对象(C++26 起) - 操作数所指代对象的动态类型为 constexpr 未知的(C++20 起)
dynamic_cast
reinterpret_cast
- (C++20 前) 伪析构函数调用
- (C++14 前) 自增或自减运算符
-
(C++14 起) 修改对象,除非该对象拥有非 volatile 字面类型,且它的生存期始于此表达式的求值之内
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); // OK:不要求 x 以核心常量表达式初始化 return x; } constexpr int y = h(1); // OK:以值 2 初始化 y // h(1) 是核心常量表达式 // 因为 k 的生存期始在表达式 h(1) 之内开始
- (C++20 起) 对于生存期不在此表达式的求值内开始的对象的析构函数调用或伪析构函数调用
- 应用到多态类型泛左值,且该泛左值所指代对象的动态类型为 constexpr 未知(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 前) 赋值或复合赋值运算符
- (C++26 前) throw 表达式
- (C++26 起) 异常对象的构造,除非异常对象和所有由 std::current_exception 或 std::rethrow_exception 的调用所隐式创建的副本都在此表达式的求值内部销毁
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)); // OK static_assert(!is_ok(-1)); // C++26 起 OK static_assert(always_throw()); // 错误:未捕获异常
- 汇编声明
- 宏 va_arg 的调用
goto
语句- 会抛出异常的
dynamic_cast
或typeid
表达式或 new 表达式(C++26 起),且其中异常类型的定义不可达(C++26 起) - lambda 表达式中,提�� this 或提及于该 lambda 之外定义的变量,如果它是一次 ODR 使用
void g() { const int n = 0; constexpr int j = *&n; // OK:lambda 表达式之外 [=] { constexpr int i = n; // OK:'n' 未被 ODR 使用且未在此处被俘获。 constexpr int j = *&n;// 非良构:'&n' ODR 使用了 'n'。 }; }
注意,如果 ODR 使用在对闭包的函数调用中发生,那么它不涉指 this 或外围变量,因为它所访问的是该闭包的数据成员
// OK:'v' 与 'm' 被 ODR 使用,但没有在嵌套于 lambda 内的常量表达式中出现 auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; // 在常量表达式求值中,创建对自动对象的俘获是 OK 的。 static_assert(bind(monad(2))(monad)() == monad(2)());
(C++17 起)
[编辑] 额外要求
即使表达式 E 不会求值以上任何一项,但是如果求值它会导致运行时未定义行为,那么由实现定义 E 是否还是核心常量表达式。
即使表达式 E 不会求值以上任何一项,但是如果求值它会导致求值以下任何一项,那么未指定 E 是否还是核心常量表达式:
为确定表达式是否为核心常量表达式,在 T
是字面类型时会忽略对 std::allocator<T> 的成员函数体的求值。
为确定表达式是否为核心常量表达式,对联合体的平凡的复制/移动构造函数和复制/移动赋值运算符的调用会被视为复制/移动该联合体的活跃成员,如果存在。
为确定表达式是否为核心常量表达式,对命名了结构化绑定 bd 的标识表达式的求值具有以下语义:
|
(C++26 起) |
在表达式作为核心常量表达式的求值过程中,所有标识表达式和 *this 的使用,如果它指代的是在该表达式求值之外已经开始生存期的对象或引用,那么都被当做指代这个对象或引用的特定实例,且它和它的所有子对象(包括所有联合体成员)的生存期包含整个常量求值。
- 对于不可用于常量表达式的这种对象,对象的动态类型即是 constexpr 未知的。
- 对于不可用于常量表达式的这种引用,将引用当做绑定到某个未指明的被引用类型的对象,它和它的所有子对象的生存期包含整个常量求值,且它的动态类型是 constexpr 未知的。
[编辑] 整数常量表达式
整数常量表达式 是隐式转换成纯右值的整数类型或无作用域枚举类型的表达式,其中被转换的表达式是核心常量表达式。
如果期待整数常量表达式的地方使用类类型表达式,那么该表达式将被按语境隐式转换成整数类型或无作用域枚举类型。
[编辑] 经转换的常量表达式
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 的成分值和成分引用。 可 constexpr 表示的实体具有静态存储期的对象在程序的任何一点都可 constexpr 引用。 对于具有自动存储期的对象 obj,如果变量 var 的最小外围作用域和点 如果满足以下所有条件,那么对象或引用 x 在点
|
(C++26 起) | ||||||||
以常量初始化的实体
可用于常量表达式constexpr 变量和具有引用类型或有 const 限定且无 volatile 限定的整数或枚举类型的变量是潜在常量的。 对于以常量初始化的潜在常量的变量 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) | 从 void* 进行 constexpr 转型:走向 constexpr 类型擦除 |
202406L |
(C++26) | constexpr 的布置 new 和 new[] | |
__cpp_constexpr_exceptions |
202411L |
(C++26) | constexpr 异常 |
[编辑] 示例
本节未完成 原因:暂无示例 |
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 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 | 容许未定义行为;且禁止所有指针减法 | 禁止未定义行为;允许同数组内的指针减法 |
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 | 求值中局部的非成员引用会令求值为非 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 文档
|