常量表达式
定义一个可在编译时求值的表达式。
这种表达式可以用作非类型模板实参、数组大小,以及其他要求常量表达式的语境,例如:
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
目录 |
[编辑] 定义
属于下列常量表达式类别之一的表达式是常量表达式。
|
(C++11 前) | ||
下列表达式统称为常量表达式
|
(C++11 起) (C++14 前) | ||
下列实体是常量表达式的允许结果 常量表达式是引用一个作为常量表达式允许结果的实体的泛左值核心常量表达式,或其值满足下列约束的纯右值核心常量表达式
|
(C++14 起) (直到 C++26) | ||
(C++26 起) |
在确定表达式是否为常量表达式时,假定不执行复制消除。
C++98 中常量表达式的定义完全位于折叠框内。以下描述适用于 C++11 及更高版本的 C++。
[编辑] 字面类型
下列类型统称为字面类型 (literal types)
- 它有一个平凡析构函数(C++20 前)constexpr 析构函数(C++20 起)。
- 其所有非静态、非变体数据成员和基类都是非 volatile 的字面类型。
- 它是下列类型之一
(C++17 起) |
只有字面类型的对象才能在常量表达式内创建。
[编辑] 核心常量表达式
核心常量表达式是其求值不会对下列任何语言构造进行求值的任何表达式
语言构造 | 版本 | 提案 |
---|---|---|
this 指针,除非在作为表达式一部分求值的 constexpr 函数中,或出现在隐式或显式类成员访问表达式中 |
N2235 | |
一个经过具有静态或线程存储期且不可在常量表达式中使用的块变量声明的控制流 | (C++23 起) | P2242R3 |
本节不完整 原因:将下面原始 HTML 有序列表中的内容转移到上面的维基表格中,同时添加引入相应项到标准的提案/CWG 问题。小示例不保留,它们可以组合成此页面底部的大示例。 |
- 调用未声明为 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 虚函数的函数调用,该调用作用于其动态类型为 constexpr 未知的对象上
- 将超出实现定义限制的表达式
- 其求值导致任何形式的核心语言未定义或谬误(C++26 起)行为的表达式,除了由标准属性引入的任何潜在未定义行为。
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 表达式
- 左值到右值隐式转换,除非应用于……
- 类型为(可能 cv 限定的)std::nullptr_t 的泛左值
- 指定可在常量表达式中使用的对象的非 volatile 字面类型泛左值
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 }
- 引用其生存期在此表达式求值内开始的非 volatile 对象的非 volatile 字面类型泛左值
- 应用于联合体的非活跃成员或其子对象的左值到右值隐式转换或修改(即使它与活跃成员共享一个共同的初始序列)
- 对其值不确定的对象的左值到右值隐式转换
- 对联合体的隐式复制/移动构造函数/赋值的调用,该联合体的活跃成员是可变的(如果有),且其生存期在此表达式求值外开始
- (C++20 前) 将改变联合体活跃成员的赋值表达式
- 从指向 void 的指针到指向对象类型的指针
T*
的转换,除非该指针持有空指针值或指向其类型与T
相似的对象(C++26 起) -
dynamic_cast
,其操作数是引用动态类型为 constexpr 未知的对象的泛左值(C++20 起) -
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); // 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 起) 对生存期未在此表达式求值内开始的对象的析构函数调用或伪析构函数调用
- 应用于多态类型泛左值的
typeid
表达式,且该泛左值引用动态类型为 constexpr 未知的对象(C++20 起) - 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)); // OK since C++26 static_assert(always_throw()); // Error: uncaught exception
- asm-声明
- 对 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: 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-使用发生在对闭包的函数调用中,它不引用 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 是否为核心常量表达式是实现定义的。
即使表达式 E 不对上述任何内容进行求值,如果对 E 的求值会求值下列任何一项,则 E 是否为核心常量表达式是未指定的
为了确定表达式是否为核心常量表达式,如果 T
是字面类型,则忽略对 std::allocator<T> 成员函数体的求值。
为了确定表达式是否为核心常量表达式,对联合体的平凡复制/移动构造函数或复制/移动赋值运算符的调用,被认为是在复制/移动联合体的活跃成员(如果有)。
为了确定表达式是否为核心常量表达式,对命名结构化绑定 bd 的标识符表达式的求值具有以下语义
|
(C++26 起) |
在将表达式作为核心常量表达式求值期间,所有引用其生存期在此表达式求值外开始的对象或引用的标识符表达式和 *this 的使用,都被视为引用该对象或引用的特定实例,该实例及其所有子对象(包括所有联合体成员)的生存期包含整个常量求值过程。
- 对于这样一个不可在常量表达式中使用的(C++20 起)对象,该对象的动态类型是 constexpr-未知的。
- 对于这样一个不可在常量表达式中使用的(C++20 起)引用,该引用被视为绑定到一个未指定的被引用类型的对象,该对象的生存期及其所有子对象的生存期包含整个常量求值过程,并且其动态类型是 constexpr-未知的。
[编辑] 整型常量表达式
整型常量表达式 (integral constant expression) 是一个整型或无作用域枚举类型的表达式,它被隐式转换为一个纯右值,其中转换后的表达式是核心常量表达式。
如果一个类类型的表达式被用在需要整型常量表达式的地方,该表达式将被按语境隐式转换为整型或无作用域枚举类型。
[编辑] 经转换的常量表达式
类型为 T
的经转换的常量表达式 (converted constant expression) 是一个隐式转换为类型 T
的表达式,其中转换后的表达式是常量表达式,并且隐式转换序列只包含
(C++17 起) |
以下语境需要经转换的常量表达式
(C++14 起) | |
(C++26 起) |
类型为 bool 的语境转换的常量表达式(contextually converted constant expression of type bool)是一个表达式,它语境转换为 bool,其中被转换的表达式是一个常量表达式,且转换序列只包含上述转换。
下列语境要求一个类型为 bool 的语境转换的常量表达式
(直至 C++23) | |
(C++17 起) (直至 C++23) | |
(C++20 起) |
构成实体一个对象 obj 的构成值(constituent values)定义如下 一个对象 obj 的构成引用(constituent references)包括以下引用
一个变量 var 的构成值和构成引用定义如下
对于变量 var 的任何构成引用 ref,如果 ref 绑定到一个临时对象或其子对象,且该临时对象的生存期被延长至 ref 的生存期,那么该临时对象的构成值和引用也递归地成为 var 的构成值和引用。 可用 constexpr 表示的实体具有静态存储期的对象在程序的任何点都是可用 constexpr 引用(constexpr-referenceable)的。 具有自动存储期的对象 obj 从点 一个对象或引用 x 在点
|
(C++26 起) | ||||||||
常量初始化的实体
可用于常量表达式一个变量是潜在常量(potentially-constant)的,如果它是一个 constexpr 变量,或者它具有引用类型或非 volatile 的 const 限定的整型或枚举类型。 一个经过常量初始化的潜在常量变量 var 在点
明显常量求值的表达式下列表达式(包括到目标类型的转换)是明显常量求值(manifestly constant-evaluated)的
一个求值是否发生在明显常量求值的语境中,可以通过 std::is_constant_evaluated 和 |
(C++20 起) |
[编辑] 常量求值所需的函数和变量
下列表达式或转换是潜在常量求值(potentially constant evaluated)的
- 明显常量求值的表达式
- 潜在求值的表达式
- 花括号环绕的初始值列表的直接子表达式(可能需要常量求值来确定转换是否为窄化转换)
- 出现在模板化实体中的取地址表达式(可能需要常量求值来确定这样的表达式是否是值依赖的)
- 上述之一的子表达式,且该子表达式本身不是嵌套的未求值操作数的子表达式
一个函数是常量求值所需(needed for constant evaluation)的,如果它是一个 constexpr 函数且被一个潜在常量求值的表达式指名。
一个变量是常量求值所需的,如果它是一个 constexpr 变量,或者具有非 volatile 的 const 限定整型类型或引用类型,并且指代它的标识符表达式是潜在常量求值的。
如果一个函数或变量(C++14 起)是常量求值所需的,则会触发一个预置函数的定义和函数模板特化或变量模板特化(C++14 起)的实例化。
[编辑] 常量子表达式
常量子表达式(constant subexpression)是这样一个表达式,其作为表达式 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 | 算术常量表达式不能 涉及变量和静态数据成员 |
推导指引可以有尾随的requires子句 |
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 文档中有关常量表达式的内容
|