Namespaces
Variants

Constant expressions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

定义可在编译时求值的 表达式

此类表达式可用作常量模板参数、数组大小,以及其他需要常量表达式的场景,例如

int n = 1;
std::array<int, n> a1;  // 错误:“n”不是常量表达式
const int cn = 2;
std::array<int, cn> a2; // 正确:“cn”是常量表达式

目录

定义

属于以下任一常量表达式类别的表达式即为 常量表达式

C++98 常量表达式类别

整型常量表达式 (C++98)

在以下位置,C++要求表达式必须求值为整型或枚举常量:

满足以下所有条件的表达式即为 整型常量表达式 :

  • 仅涉及以下实体:
  • 算术类型的字面量
  • 枚举项
  • 满足以下所有条件的变量或静态数据成员:
  • 具有const限定符
  • 不具有volatile限定符
  • 为整型或枚举类型
  • 使用常量表达式初始化
  • 不使用任何浮点数字面量,除非被 显式转换 为整型或枚举类型
  • 不应用任何向非整型和非枚举类型的转换
  • 除在 sizeof 的操作数中外,不使用以下任何实体:
  • 函数
  • 类对象
  • 指针
  • 引用
  • 赋值运算符
  • 自增运算符
  • 自减运算符
  • 函数调用运算符
  • 逗号运算符

其他常量表达式类别

其他表达式仅在 常量初始化 的上下文中被视为常量表达式。此类常量表达式必须是以下表达式之一:

  • 求值为 空指针值 的表达式
  • 求值为空成员指针值的表达式
  • 算术常量表达式
  • 地址常量表达式
  • 引用常量表达式
  • 完整对象类型的地址常量表达式加上或减去整型常量表达式
  • 成员指针常量表达式

算术常量表达式 是满足整型常量表达式要求的表达式,但存在以下例外:

  • 浮点数字面量无需显式转换即可使用
  • 可应用向浮点类型的转换

地址常量表达式 是满足以下所有条件的指针类型表达式:

  • 显式使用取址运算符
  • 隐式使用指针类型的常量模板形参
  • 使用数组或函数类型的表达式
  • 表达式不调用任何函数
  • 表达式使用显式指针转换(除 dynamic_cast 外)和以下运算符但不访问结果对象:
  • 下标运算符
  • 解引用运算符
  • 取址运算符
  • 成员访问运算符
  • 若使用下标运算符,其操作数之一必须是整型常量表达式

引用常量表达式 是满足以下所有条件的引用类型表达式:

  • 引用指向具有静态存储期的对象、引用类型的常量模板形参或函数。引用不得指向非POD类类型的成员或基类
  • 表达式不调用任何函数
  • 表达式使用显式引用转换(除 dynamic_cast 外)和以下运算符但不访问结果对象:
  • 下标运算符
  • 解引用运算符
  • 取址运算符
  • 成员访问运算符
  • 若使用下标运算符,其操作数之一必须是整型常量表达式

成员指针常量表达式 是通过对限定标识符应用取址运算符(可选地前置显式成员指针转换)创建的成员指针类型表达式。

(C++11 前)

以下表达式统称为 常量表达式 :

(C++11 起)
(C++14 前)

以下实体是 常量表达式的允许结果 :

常量表达式 是指向常量表达式允许结果的左值 核心常量表达式 ,或值满足以下约束的纯右值核心常量表达式:

  • 若值为类类型对象,其每个引用类型的非静态数据成员必须指向常量表达式允许结果
  • 若值为 标量类型 对象,不得具有 不确定值
  • 若值为 指针类型 ,必须是以下值之一:
  • 具有静态存储期的对象地址
  • 具有静态存储期的对象末尾后地址
  • 非立即 (C++20 起) 函数地址
  • 空指针值
  • 若值为成员函数指针类型,不得指向立即函数
(C++20 起)
  • 若值为类或数组类型对象,每个子对象的值均需满足这些约束
(C++14 起)
(C++26 前)

常量表达式 是指向对象或非 立即函数 的左值 核心常量表达式 ,或值满足以下约束的纯右值核心常量表达式:

(C++26 起)

在判断表达式是否为常量表达式时,假定 复制消除 不会被执行。

C++98 关于常量表达式的定义完全折叠在隐藏框内。以下描述适用于 C++11 及后续 C++ 版本。

字面类型

以下类型统称为 字面类型 :

(自 C++17 起)
  • 满足以下条件之一的 聚合 联合类型:
  • 没有 变体成员
  • 至少有一个非易失性字面类型的变体成员
  • 非联合聚合类型,且其每个 匿名联合 成员满足以下条件之一:
  • 没有变体成员
  • 至少有一个非易失性字面类型的变体成员
  • 具有至少一个非拷贝/移动构造函数的 constexpr 构造函数(模板)

只有字面类型的对象才能在常量表达式中创建。

核心常量表达式

一个 核心常量表达式 是指其求值 不会 涉及以下任一语言结构的表达式:

语言结构 版本 文献
除作为表达式一部分被求值的 constexpr 函数 中,或出现在隐式或显式类成员访问表达式中的情况外, this 指针 N2235
控制流经过具有静态或线程 存储期 不可用于常量表达式 块作用域变量 声明 (自 C++23 起) P2242R3
  1. 调用未声明为 constexpr 的函数(或构造函数)的函数调用表达式
    constexpr int n = std::numeric_limits<int>::max(); // 正确:max() 是 constexpr
    constexpr int m = std::time(nullptr); // 错误:std::time() 不是 constexpr
  2. 调用已声明但未定义的 constexpr 函数的函数调用
  3. 调用 constexpr 函数/构造函数模板实例化,但该实例化不满足 constexpr 函数/构造函数 要求
  4. 调用 constexpr 虚函数,且该调用作用于动态类型为 constexpr-未知 的对象
  5. 超出实现定义限制的表达式
  6. 求值会导致任何形式的核心语言 未定义 或错误 (C++26 起) 行为的表达式,除非是由 标准属性 引入的潜在未定义行为
    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]; // 未定义行为,但未指定是否会被检测
  7. (C++17 前) lambda 表达式
  8. 左值到右值的 隐式转换 ,除非应用于...
    1. 类型为(可能 cv 限定的) std::nullptr_t 的泛左值
    2. 非易失性字面类型的泛左值,且其指代的对象 可在常量表达式中使用
      int main()
      {
          const std::size_t tabsize = 50;
          int tab[tabsize]; // 正确:tabsize 是常量表达式
                            // 因为 tabsize 可在常量表达式中使用
                            // 因为它具有 const 限定的整型类型,且
                            // 其初始化器是常量初始化器
          std::size_t n = 50;
          const std::size_t sz = n;
          int tab2[sz]; // 错误:sz 不是常量表达式
                        // 因为 sz 不可在常量表达式中使用
                        // 因为其初始化器不是常量初始化器
      }
    3. 非易失性字面类型的泛左值,且其引用的非易失性对象的生存期始于该表达式的求值过程中
  9. 应用于 联合体 或其子对象的非活跃成员的左值到右值 隐式转换 或修改(即使其与活跃成员共享公共初始序列)
  10. 值不确定 的对象进行左值到右值隐式转换
  11. 调用联合体的隐式复制/移动构造函数/赋值运算符,且该联合体的活跃成员(如果有)为 mutable,且其生存期始于该表达式求值之外
  12. (C++20 前) 会改变联合体活跃成员的赋值表达式
  13. 指向 void 的指针 到指向对象类型 T* 的转换 ,除非该指针持有空指针值或指向类型与 T 相似 的对象 (C++26 起)
  14. dynamic_cast ,且其操作数是引用动态类型为 constexpr-未知 的对象的泛左值 (C++20 起)
  15. reinterpret_cast
  16. (C++20 前) 伪析构函数调用
  17. (C++14 前) 自增或自减运算符
  18. (C++14 起) 修改对象,除非该对象具有非易失性字面类型且其生存期始于该表达式的求值过程中
    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) 内部
  19. (C++20 起) 对生存期未始于该表达式求值过程中的对象进行析构函数调用或伪析构函数调用
  20. 应用于多态类型的泛左值的 typeid 表达式 ,且该泛左值引用动态类型为 constexpr-未知 的对象 (C++20 起)
  21. new 表达式 ,除非满足以下条件之一: (C++20 起)
    • 选择的 分配函数 是可替换的全局分配函数,且分配的存储在该表达式的求值过程中被释放。
    (C++20 起)
    • 选择的分配函数是具有分配类型 T 的非分配形式,且 placement 参数满足以下所有条件:
    • 它指向:
    • 类型与 T 相似的对象,如果 T 不是数组类型,或
    • 类型与 T 相似的对象的首元素,如果 T 是数组类型。
    • 它指向的存储的生存期始于该表达式的求值过程中。
    • </ul

额外要求

即使表达式 E 未评估上述任何情况,若对 E 的求值会导致 运行时未定义行为 ,则 E 是否为核心常量表达式由实现定义。

即使表达式 E 未计算上述任何内容,若计算 E 将计算以下任何操作,则 E 是否为核心常量表达式是未指定的:

为判断表达式是否为核心常量表达式,若 T 为字面类型,则忽略 std:: allocator < T > 成员函数体的求值过程。

为判断表达式是否为核心常量表达式,对 联合体 的平凡复制/移动构造函数或复制/移动赋值运算符的调用,其求值过程被视为复制/移动该联合体的活跃成员(若存在)。

为确定表达式是否为核心常量表达式,对命名 结构化绑定 bd 的标识符表达式求值时遵循以下语义:

  • bd 是引用被绑定到虚构引用 ref 的对象的左值,其行为如同指名了 ref
  • 否则,若 bd 命名数组元素,其行为等价于对 e [ i ] 求值,其中 e 是从结构化绑定声明初始化器初始化的变量名, i bd 所引用元素的下标。
  • 否则,若 bd 命名类成员,其行为等价于对 e. m 求值,其中 e 是从结构化绑定声明初始化器初始化的变量名, m bd 所引用成员的名称。
(since C++26)

在将表达式作为核心常量表达式求值期间,所有引用生命周期始于该表达式求值之外的对象的标识符表达式及 * this 的使用,均被视为引用该对象或引用的特定实例,该实例及其所有子对象(包括所有联合体成员)的生命周期需涵盖整个常量求值过程。

  • 对于此类 不可在常量表达式中使用的 (C++20 起) 对象,该对象的动态类型为 constexpr-unknown
  • 对于此类 不可在常量表达式中使用的 (C++20 起) 引用,该引用被视为绑定到被引用类型的一个未指定对象,该对象及其所有子对象的生存期涵盖整个常量求值过程,且其动态类型为 constexpr-unknown。

整型常量表达式

整型常量表达式 是指整型或无作用域枚举类型的表达式,该表达式被隐式转换为纯右值,且转换后的表达式为核心常量表达式。

当需要整型常量表达式的地方使用了类类型的表达式时,该表达式会被 上下文隐式转换 为整型或无作用域枚举类型。

转换后的常量表达式

类型 T 转换后常量表达式 是指一个被 隐式转换 为类型 T 的表达式,其中被转换的表达式是常量表达式,且隐式转换序列仅包含:

(C++17 起)

如果发生任何 引用绑定 ,它只能是 直接绑定

以下上下文需要转换后的常量表达式:

(C++14 起)
(C++26 起)

一个 上下文转换为 bool 类型的常量表达式 是指: 被上下文转换为 bool 的表达式,其中被转换的表达式是常量表达式,且转换序列仅包含上述转换规则。

以下上下文需要类型为 bool 的上下文转换常量表达式:

(直至 C++23)
(自 C++17 起)
(直至 C++23)
(自 C++20 起)


组成实体

对象 obj 组成值 定义如下:

对象 obj 组成引用 包括以下引用:

  • obj 的任何具有引用类型的直接成员
  • obj 的任何直接子对象(不包括非活跃联合成员)的组成引用

变量 var 组成值 组成引用 定义如下:

  • var 声明一个对象,则组成值和引用为该对象的组成值和引用。
  • var 声明一个引用,则组成引用为该引用。

对于变量 var 的任何组成引用 ref ,若 ref 绑定到临时对象或其子对象,且该临时对象的生存期被延长至 ref 的生存期,则该临时对象的组成值和引用也是 var 的组成值和引用(递归地)。

常量表达式可表示实体

具有静态存储期的对象在程序的任何点都是 常量表达式可引用 的。

具有自动存储期的对象 obj 在点 P 常量表达式可引用 的,如果包围变量 var 的最小 作用域 与包围 P 的最小作用域是同一个函数参数作用域,且该作用域不与 requires 表达式 的参数列表关联,其中 var 是对应于 obj 的完整对象的变量,或 obj 的生存期被 延长 至其生存期的变量。

对象或引用 x 在点 P 常量表达式可表示 的,如果满足以下所有条件:

  • 对于 x 的每个指向对象 obj 的组成值, obj P 是常量表达式可引用的。
  • 对于 x 的每个指向对象 obj 之后的组成值, obj P 是常量表达式可引用的。
  • 对于 x 的每个引用对象 obj 的组成引用, obj P 是常量表达式可引用的。
(C++26 起)

常量初始化实体

变量或临时对象 obj 常量初始化 的,如果满足以下所有条件:

  • 它要么有初始化器,要么其类型是 常量默认可构造 的。
  • 其初始化的 完整表达式 在要求常量表达式的上下文中是常量表达式,但若 obj 是对象,则该完整表达式也可以调用 obj 及其子对象的 constexpr 构造函数 ,即使这些对象是非字面类类型。
(C++26 前)

变量 var 常量可初始化 的,如果满足以下所有条件:

  • 其初始化的 完整表达式 在要求常量表达式的上下文中是常量表达式,其中所有 契约断言 使用“忽略”求值语义。
  • var 的初始化声明之后,由 var 声明的对象或引用是常量表达式可表示的。
  • 若由 var 声明的对象或引用 x 具有静态或线程存储期,则 x 在紧接 var 初始化声明之后、立即作用域为命名空间作用域的最远点是常量表达式可表示的。

常量可初始化变量是 常量初始化 的,如果它要么有初始化器,要么其类型是 常量默认可构造 的。

(C++26 起)

可用于常量表达式

变量是 潜在常量 的,如果它是 constexpr 变量 ,或者它具有引用类型或非 volatile const 限定的整型或枚举类型。

常量初始化的潜在常量变量 var 在点 P 可用于常量表达式 的,如果 var 的初始化声明 D 可从 P 到达,且满足以下任一条件:

  • var constexpr 变量。
  • var 未初始化为 翻译单元局部 值。
  • P D 在同一翻译单元中。

对象或引用在点 P 可用于常量表达式 的,如果它是以下实体之一:

  • P 可用于常量表达式的变量
  • 生存期被延长至在 P 可用于常量表达式的变量的非 volatile const 限定字面类型的临时对象
  • 模板参数对象
  • 字符串字面量 对象
  • 以上任何实体的非可变子对象
  • 以上任何实体的引用成员
(C++26 前)

对象或引用在点 P 潜在可用于常量表达式 的,如果它是以下实体之一:

  • P 可用于常量表达式的变量
  • 生存期被延长至在 P 可用于常量表达式的变量的非 volatile const 限定字面类型的临时对象
  • 模板参数对象
  • 字符串字面量 对象
  • 以上任何实体的非可变子对象
  • 以上任何实体的引用成员

对象或引用在点 P 可用于常量表达式 的,如果它是在 P 潜在可用于常量表达式的对象或引用,且在 P 是常量表达式可表示的。

(C++26 起)

显式常量求值表达式

以下表达式(包括到目标类型的转换)是 显式常量求值 的:

常量求值所需的函数与变量

以下表达式或转换属于 潜在常量求值

一个函数在以下情况下 需要用于常量求值 :如果它是 constexpr 函数,且被可能进行常量求值的表达式 命名

一个变量在以下情况下 需要用于常量求值 :如果它是constexpr变量,或者是非volatile const限定的整型类型或引用类型,并且表示该变量的 标识符表达式 可能被常量求值。

默认函数的定义以及 函数模板 特化 变量模板 特化 (since C++14) 的实例化会在函数 或变量 (since 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++标准。

缺陷报告 适用版本 发布时行为 正确行为
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 允许未定义行为;
禁止所有指针减法
禁止UB;允许
同数组指针减法
CWG 1405 C++11 对于可在常量表达式中使用的对象,
其mutable子对象也可使用
不可使用
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 文档 关于 常量表达式