命名空间
变体
操作

常量表达式

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 

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

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

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

目录

[编辑] 核心常量表达式

核心常量表达式是指其求值不会求值以下任何一项的任何表达式

  1. this 指针,除非是在作为表达式一部分进行求值的 constexpr 函数
  2. 穿过具有静态或线程局部存储期的变量声明的控制流,并且在常量表达式中不可用
  3. 调用未声明为 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
  4. 对已声明但未定义的 constexpr 函数的函数调用
  5. 对 constexpr 函数/构造函数模板实例化的函数调用,其中实例化未能满足 constexpr 函数/构造函数 的要求。
  6. 对 constexpr 虚函数的函数调用,在常量表达式中不可用且其生命周期开始于此表达式之外的对象上调用。
  7. 会超出实现定义限制的表达式
  8. 其求值会导致任何形式的核心语言未定义或错误(自 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
  9. (直到 C++17) lambda 表达式
  10. 左值到右值的隐式转换,除非应用于非易失性字面量类型左值,该左值...
    1. 指定可在常量表达式中使用的对象,
      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
      }
    2. 引用其生命周期开始于此表达式求值期间的非易失性对象
  11. 应用于联合或其子对象的非活动成员的左值到右值的隐式转换或修改(即使它与活动成员共享一个共同的初始序列)
  12. 其值不确定的对象进行左值到右值的隐式转换
  13. 对生命周期开始于此表达式求值之外的联合(如果存在)的隐式复制/移动构造函数/赋值的调用,其活动成员是可变的
  14. (直到 C++20) 会更改联合的活动成员的赋值表达式
  15. 引用变量或引用类型数据成员的 id 表达式,除非该引用可在常量表达式中使用或其生命周期开始于此表达式的求值期间
  16. 指向 void 的指针转换为指向对象类型 T* 的指针,除非该指针持有空指针值或指向其类型与 T 相似 的对象(自 C++26 起)
  17. (直到 C++20) dynamic_cast
  18. reinterpret_cast
  19. (直到 C++20) 伪析构函数调用
  20. (直到 C++14) 自增或自减运算符
  21. (自 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)
  22. (自 C++20 起) 对其生命周期未开始于此表达式求值期间的对象的析构函数调用或伪析构函数调用
  23. (直到 C++20) 应用于多态类型左值的 typeid 表达式
  24. new 表达式,除非满足以下条件之一:(自 C++20 起)
    • 选定的分配函数是可替换的全局分配函数,并且分配的存储在此表达式的求值期间被释放。
    (自 C++20 起)
    • 选定的分配函数是非分配形式,其分配类型为 T,并且放置参数满足以下所有条件
    • 如果 T 不是数组类型,则为 T 类型的对象,或
    • 如果 T 是数组类型,则为 T 类型对象的第一个元素。
    • 它指向其持续时间开始于此表达式求值期间的存储。
    (自 C++26 起)
  25. delete 表达式,除非它释放了在此表达式求值期间分配的存储区域(自 C++20 起)
  26. (自 C++20 起) 协程:await 表达式yield 表达式
  27. (自 C++20 起) 当结果未指定时进行三路比较
  28. 结果未指定的相等或关系运算符
  29. (直到 C++14) 赋值或复合赋值运算符
  30. throw 表达式
  31. 一个 asm-声明
  32. va_arg 宏的调用
  33. 一个 goto 语句
  34. 一个会抛出异常的 dynamic_casttypeid 表达式
  35. 在 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 是否为核心常量表达式是未指定的

  • 对先前使用 [[noreturn]] 属性声明的函数的调用,并且该调用返回到其调用者。
(自 C++11 起)
  • 一个假设 [[assume(expr)]]; 使得如果在出现假设的位置计算 expr,结果不是 true,而 E 没有被取消作为核心常量表达式的资格(即,假设的 expr 计算将计算前一项列表中的任何一项)。
(自 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++20 起)
(自 C++17 起)

[编辑] 可在常量表达式中使用

在上面的列表中,如果一个变量满足以下条件,则它在点 P可在常量表达式中使用

  • 该变量是
  • 引用类型或
  • const 限定的整数或枚举类型
  • 并且可以从 P 访问到该变量的定义
  • 并且,如果 P 与变量定义不在同一个翻译单元中(即,定义是 导入的),则该变量不会被初始化为指向、引用或具有一个(可能是递归的)子对象,该子对象指向或引用一个可在常量表达式中使用的 翻译单元局部实体
(自 C++20 起)

如果一个对象或引用满足以下条件,则它可在常量表达式中使用

  • 一个可在常量表达式中使用的变量,或者
(自 C++20 起)
  • 一个 字符串字面量 对象,或者
  • 上述任何一个的非可变子对象或引用成员,或者
  • 一个非易失性 const 限定的字面类型的临时对象,其生命周期被 延长 到一个可在常量表达式中使用的变量的生命周期。
const std::size_t sz = 10; // sz is usable in constant expressions

[编辑] 显式常量求值表达式

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

(自 C++17 起)
(自 C++20 起)
  • 引用类型或 const 限定的整数或枚举类型的变量的初始化器,但前提是初始化器是常量表达式
  • 静态和线程局部变量的初始化器,但前提是初始化器的所有子表达式(包括构造函数调用和隐式转换)都是常量表达式(即,如果初始化器是 常量初始化器

求值是否发生在显式常量求值上下文中可以通过 std::is_constant_evaluatedif consteval(自 C++23 起) 检测。

为了测试最后两个条件,编译器可能会首先对初始化器执行试探性常量求值。在这种情况下,不建议依赖结果。

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 newnew[]

[编辑] 缺陷报告

以下行为更改缺陷报告已追溯应用于先前发布的 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)
(C++11)(在 C++17 中弃用)(在 C++20 中移除)
检查类型是否为字面量类型
(类模板) [编辑]
C 文档 常量表达式