常量表达式
定义一个可在编译时求值的表达式。
此类表达式可用作非类型模板实参、数组大小以及其他需要常量表达式的上下文中,例如:
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++。
[编辑] 字面类型
以下类型统称为字面类型
- 它具有平凡析构函数(直到 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
,并且 placement 实参满足以下所有条件
- 它指向
- 类型类似于
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-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 使用发生在对闭包的函数调用中,则它不引用 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-unknown。
- 对于这样的引用,它不是可用常量表达式(自 C++20 起),该引用被视为绑定到未指定类型的被引用类型的对象,该对象的生命周期以及所有子对象的生命周期都包含整个常量求值,并且其动态类型为 constexpr-unknown。
[编辑] 整型常量表达式
整型常量表达式 是指一个整型或无作用域枚举类型的表达式,它被隐式转换为纯右值 (prvalue),且转换后的表达式是一个核心常量表达式。
如果在一个期望整型常量表达式出现的地方使用了一个类类型的表达式,该表达式会 语境隐式转换 为整型或无作用域枚举类型。
[编辑] 转换后的常量表达式
类型 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 从点 对象或引用 x 在点
|
(自 C++26 起) | ||||||||
常量初始化的实体
可在常量表达式中使用一个变量是 潜在常量,如果它是 constexpr 变量,或者它具有引用类型或非易失性 const 限定的整型或枚举类型。 一个常量初始化的潜在常量变量 var 在点
显式常量求值的表达式以下表达式(包括到目标类型的转换)是 显式常量求值 的:
一个求值是否发生在显式常量求值语境中,可以通过 std::is_constant_evaluated 和 |
(自 C++20 起) |
[编辑] 常量求值所需的函数和变量
以下表达式或转换是 潜在常量求值 的:
- 显式常量求值的表达式
- 潜在求值的表达式
- 花括号括起来的初始化列表 的直接子表达式(常量求值可能用于确定 转换是否是窄化转换)
- 发生在 模板实体 内的取地址表达式(常量求值可能用于确定此类表达式是否是 值依赖的)
- 上述之一的子表达式,且不是嵌套的 未求值操作数 的子表达式
一个函数是 常量求值所需的,如果它是一个 constexpr 函数,并且被一个潜在常量求值的表达式 命名。
一个变量是 常量求值所需的,如果它要么是一个 constexpr 变量,要么是具有非易失性 const 限定的整型类型或引用类型,并且表示它的 标识符表达式 是潜在常量求值的。
如果一个函数 或变量(C++14 起) 是常量求值所需的,则会触发默认函数的定义和 函数模板 特化或 变量模板 特化(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 placement 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 glvalue 可以在常量表达式中使用 | 禁止 |
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 表达式的操作数是 多态类类型,即使不涉及运行时检查,也不是核心常量表达式 表达式,即使不涉及运行时检查,也不是核心常量表达式 |
操作数约束 仅限于多态类类型的 多态类类型的 glvalue |
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 限定字面量类型的常量初始化且生命周期延长的临时对象在常量表达式中不可用 const 限定字面量类型的常量初始化且生命周期延长的临时对象在常量表达式中不可用 |
可用 |
CWG 2129 | C++11 | 整数字面量不是常量表达式 | 它们是 |
CWG 2167 | C++11 | 局部于求值的非成员引用 使得求值变为非 constexpr |
允许非成员 允许非成员引用 |
CWG 2278 | C++98 | CWG issue 2022 的解决方案不可实现 | 假设总是执行复制省略 永不执行 |
CWG 2299 | C++14 | <cstdarg> 中的宏是否 可以在常量求值中使用尚不明确 |
va_arg 被禁止, va_start 未指定 |
CWG 2400 | C++11 | 在常量表达式中不可用的对象上调用 constexpr 虚函数,且该对象的生命周期在包含调用的表达式之外开始,这可能是一个常量表达式 在常量表达式中不可用的对象上调用 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 glvalue |
可以应用此类 转换 |
CWG 2909 | C++20 | 没有初始化器的变量只有在其 默认初始化导致执行某些初始化的情况下才能被常量初始化 默认初始化导致执行某些初始化的情况下才能被常量初始化 |
只有在其类型是 只有在其类型是 const 默认可初始化的时才能被常量初始化 |
CWG 2924 | C++11 C++23 |
违反 [[noreturn]] (C++11) 或 违反 [[noreturn]] (C++11) 或 [[assume]] (C++23) 约束的表达式是否是核心常量表达式尚不明确 |
它是 它是实现定义的 |
P2280R4 | C++11 | 求值包含标识符表达式 或 *this 的表达式,且该表达式引用生命周期 在此求值之外开始的对象或引用,则不是常量表达式 |
它可能是一个 常量表达式 |
[编辑] 参见
constexpr 说明符(C++11) |
指定变量或函数的值可以在编译时计算 |
(C++11)(C++17 中已弃用)(C++20 中已移除) |
检查类型是否为字面量类型 (类模板) |
C 文档 中关于 常量表达式 的内容
|