常量表达式

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue、右值 rvalue、亡值 xvalue)
求值顺序(序列点)
常量表达式
潜在求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=ba+=ba-=ba*=ba/=ba%=ba&=ba|=ba^=ba<<=ba>>=b
自增与自减++a--aa++a--
算术运算符+a-aa+ba-ba*ba/ba%b~aa&ba|ba^ba<<ba>>b
逻辑运算符a||ba&&b!a
比较运算符a==ba!=ba<ba>ba<=ba>=ba<=>b(C++20)
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
折叠表达式(C++17)
运算符的代用表示
优先级和结合性
运算符重载
默认比较(C++20)
类型转换
隐式转换
一般算术转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 

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

这种表达式能用做非类型模板实参、数组大小,并用于其他要求常量表达式的语境,例如

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

核心常量表达式

核心(core)常量表达式 是求值过程中不会 对下列任一者求值的表达式:

  1. this 指针,除了在 constexpr 函数之中,作为该表达式的一部分求值
  2. (C++23 起) 经过静态或线程存储期变量声明的控制流
  3. 调用未声明为 constexpr 的函数(或构造函数)
    constexpr int n = std::numeric_limits<int>::max(); // OK:max() 是 constexpr
    constexpr int m = std::time(NULL); // 错误:std::time() 非 constexpr
  4. 调用已声明但未定义的 constexpr 函数
  5. 调用实例化无法满足 constexpr 函数/构造函数 要求的 constexpr 函数/构造函数模板
  6. 调用 constexpr 虚函数,除了所用的对象可用于常量表达式或它的生命期始于此表达式内
  7. 会超出实现定义限制的表达式
  8. 导致任何形式的核心语言未定义行为(包含有符号整数溢出、除以零、数组边界外的指针算术等)的操作。未指明是否检测标准库的未定义行为
    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]; // 行为未定义,但是否检测未指定
  9. (C++17 前) lambda 表达式
  10. 左值到右值隐式转换,除非应用于
    1. 指代可用于常量表达式的对象的非 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 不可用于常量表达式
                        // 因为它的初始化器不是常量初始化器
      }
    2. 或拥有字面类型,并指代生命期始于此表达式的求值之内非 volatile 泛左值
  11. union 的不活跃成员或它们的子对象实施的左值到右值隐式转换或修改操作(即使它与活跃成员共享公共起始序列也是如此)
  12. (C++20 起) 应用到拥有不确定值的对象的左值到右值隐式转换
  13. 对活跃成员(如果存在)是 mutable 的 union 调用隐式定义的复制/移动构造函数或复制/移动赋值运算符,除非该 union 对象的生命周期始于此表达式的求值
  14. (C++17 起) (C++20 前)会更改 union 的活跃成员的赋值表达式,或对重载的赋值运算符的调用
  15. 指代引用类型的变量或数据成员的 标识表达式,除非
  16. 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 起)
  17. void 指针 转换到 任何对象类型的指针 (C++26 前) 对象类型的指针 T*,除了这个指针指向一个与 T 相似的对象 (C++26 起)
  18. (C++20 前) dynamic_cast
  19. reinterpret_cast
  20. (C++14 前) 自增或自减运算符
  21. (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) 之内开始
  22. (C++20 前) 对于生命期不在此表达式的求值内开始的对象的析构函数调用或伪析构函数调用
  23. (C++20 前) 应用到多态类型泛左值的 typeid 表达式
  24. new 表达式,除非选择的分配函数是全局可替换分配函数,且它分配的存储会在此表达式的求值内解分配 (C++20 起)
  25. delete 表达式,除非它在此表达式的求值内分配的存储区域解分配 (C++20 起)
  26. (C++20 起) 调用 std::allocator<T>::allocate ,除非它分配的存储在此表达式的求值内解分配
  27. (C++20 起) 调用 std::allocator<T>::deallocate ,除非它在此表达式的求值内分配的存储区域解分配
  28. (C++20 起) await 表达式yield 表达式
  29. (C++20 起) 结果未指定的三路比较或相等或关系运算符
  30. (C++14 前) 赋值或复合赋值运算符
  31. throw 表达式
  32. (C++20 起) 汇编声明
  33. (C++14 起)va_arg 的调用,是否能求值宏 va_start 是未指定的
  34. (C++23 起) goto 语句
  35. (C++20 起) 会抛出异常的 dynamic_casttypeid 表达式

注意:核心常量表达式 本身并无任何直接的语义含义:表达式必须是常量表达式的子集之一(见后述),才可以在特定语境中使用。

常量表达式

常量表达式(constant expression)

  • 指代下列之一的左值 (C++14 前)泛左值 (C++14 起)核心常量表达式
  • 拥有静态存储期且非临时的对象,或
  • 拥有静态存储期的临时对象,但它的值满足下文对纯右值的约束,或
(C++14 起)
  • 它的值满足下列约束的纯右值核心常量表达式
  • 如果它的值是类类型对象,那么它的每个引用类型的非静态数据成员均指代满足上述对左值 (C++14 前)泛左值 (C++14 起)约束的实体
  • 如果它的值具有指针类型,那么它保有
  • 拥有静态存储期的对象的地址
  • 拥有静态存储期的对象的末尾后一位置的地址
  • 立即 (C++20 起)函数的地址
  • 空指针值
  • 如果它的值具有成员函数指针类型,那么它不代表立即函数
(C++20 起)
  • 如果它的值具有类或数组类型,那么每个子对象均满足这些对值的约束
void test()
{
    static const int a = std::random_device{}();
    constexpr const int& ra = a; // OK:a 是泛左值常量表达式
    constexpr int ia = a; // 错误:a 不是纯右值常量表达式
 
    const int b = 42;
    constexpr const int& rb = b; // 错误:b 不是泛左值常量表达式
    constexpr int ib = b; // OK:b 是纯右值常量表达式
}

整数常量表达式

整数常量表达式 是隐式转换成纯右值的整数类型或无作用域枚举类型的表达式,其中被转换的表达式是核心常量表达式。如果期待整数常量表达式的地方使用类类型表达式,那么该表达式将被按语境隐式转换成整数类型或无作用域枚举类型。

下列语境要求整数常量表达式

(C++14 前)

经转换的常量表达式

T 类型的经转换的常量表达式 是隐式转换到 T 类型的表达式,其中被转换后表达式是常量表达式,且隐式转换序列只含有:

  • constexpr 用户定义转换(故类能用于期待整数类型的地方)
  • 左值到右值转换
  • 整数提升
  • 非窄化整数转换
  • 数组到指针转换
  • 函数到指针转换
  • 函数指针转换(指向 noexcept 函数的指针到指向函数的指针)
  • 限定性转换
  • 源自 std::nullptr_t 的空指针转换
  • 源自 std::nullptr_t 的空成员指针转换
(C++17 起)
  • 而如果发生任何引用绑定,那么它是直接绑定(而不是构造临时对象)

下列语境要求经转换的常量表达式

(C++14 起)

按语境转换的 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 起) 模板形参对象
  • 字符串字面量对象
  • 以上任一者的非 mutable 子对象或引用成员
  • 具有无 volatile 限定但有 const 限定的类型,并且生存期被延续到与某个可用于常量表达式的变量相同的临时对象
const std::size_t sz = 10; // sz 可用于常量表达式

明显常量求值的表达式

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

  • 语法上要求常量表达式处,包括
(C++20 起)
(C++17 起)
(C++20 起)
  • 可用于常量表达式的变量的初始化器,包括
  • constexpr 变量的初始化器
  • 引用和 const 限定的整数或枚举类型的变量的初始化器,如果该初始化器是常量表达式
  • 静态及线程局域变量的初始化器,如果该初始化器的所有子表达式(含构造函数调用和隐式转换)都是常量表达式(即该初始化器是常量初始化器

注意最后两种情况的语境也接受非常量表达式。

能用 std::is_constant_evaluatedif consteval (C++23 起) 检测求值是否在明显常量求值语境中出现。

为测试最后两类条件,编译器可能及对初始化器的试探性地常量求值。不建议依赖此时的结果。

(C++20 起)

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

下列表达式或转换会潜在常量求值

  • 明显常量求值的表达式
  • 潜在求值的表达式
  • 花括号初始化器列表的立即子表达式(可能需要常量求值确定转换是否为窄化
  • 模板化实体内出现的取址(一元 &)表达式(可能需要常量求值确定这种表达式是否为值待决
  • 上述之一的子表达式,除了嵌套的不求值操作数的子表达式

如果函数是 constexpr 函数且被潜在常量求值的表达式指名,那么它需要用于常量求值

如果变量是 constexpr 变量或非 volatile 的 const 限定的整数类型或引用类型的变量,且指代它的标识表达式被潜在常量求值,那么它需要用于常量求值

预置的函数的定义及函数模板特化变量模板特化 (C++14 起)的实例化会被触发,如果该函数或变量 (C++14 起)需要用于常量求值。

注解

实现不能将库函数声明为 constexpr,除非标准指定该函数为 constexpr

常量表达式中不容许具名返回值优化(NRVO),而返回值优化(RVO)是强制要求的。

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
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 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 常量求值中的(伪)析构函数调用缺少限制 添加了限制

参阅

constexpr 说明符(C++11) 指定变量或函数的值能在编译时计算
(C++11)(C++17 中弃用)(C++20 中移除)
检查类型是否为字面类型
(类模板)