常量表达式

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

目录

[编辑] 核心常量表达式

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

  1. this 指针,除了在作为表达式一部分求值的 constexpr 函数或 constexpr 构造函数中
  2. 调用不声明为 constexpr 的函数(或构造函数)的函数调用表达式
    constexpr int n = std::numeric_limits<int>::max(); // OK : max() 是 constexpr
    constexpr int m = std::time(NULL); // 错误: std::time() 非 constexpr
  3. 对声明为 constexpr 但不定义的函数的函数调用
  4. constexpr 函数/构造函数模板实例化的函数调用,其中该实例化无法满足 constexpr 函数/构造函数的要求。
  5. 会超出实现定义限制的表达式
  6. 求值导致任何形式的核心语言未定义行为(包含有符号整数溢出、除以零、数组边界外的指针算术等)的表达式。是否检测标准库未定义行为是未指定的。
    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]; // UB ,但是否检测未指定
  7. (C++17 前) Lambda 表达式
  8. 左值到右值隐式转换,除非该左值……
    1. 拥有整数或枚举类型,并指代完整非 volatile 的 const 对象,该对象以常量表达式初始化
      int main() {
          const std::size_t tabsize = 50;
          int tab[tabsize]; // OK : tabsize 是常量表达式
       
          std::size_t n = 50;
          const std::size_t sz = n;
          int tab2[sz]; // 错误: sz 不是常量表达式
                        // 因为 sz 不以常量表达式初始化
      }
    2. 是指代字符串字面量元素的非 volatile 泛左值
    3. 拥有字面类型,并指代以 constexpr 定义的非 volatile 对象,或指代其非 mutable 子对象
    4. 拥有字面类型,并指代生存期始于此表达式的求值内的非 volatile 对象
  9. 应用到 union 或其子对象不活跃成员的左值到右值隐式转换或修改(即使它与活跃成员共享公共起始序列)
  10. 对活跃成员(若存在)为 mutable 的 union 调用隐式定义的复制/移动构造函数或复制/移动赋值运算符,除非该 union 对象的生存期始于此表达式的求值内
  11. (C++17 起)会更改 union 活跃成员的赋值表达式,或重载的赋值运算符的调用
  12. 指代引用类型变量或数据成员的 id-expression ,除非该引用以常量表达式初始化,或其生存期始于此表达式的求值内
  13. cv void* 转换到任何指向对象指针类型
  14. dynamic_cast
  15. reinterpret_cast
  16. 伪析构函数调用
  17. (C++14 前)自增或自减运算符
  18. (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) 之内
  19. 应用到多态类型泛左值的 typeid 表达式
  20. new 表达式或 delete 表达式
  21. (C++20 起)比较不指向同一完整对象,或其子对象的指针的三路比较
  22. 结果未指定时的相等或关系运算符
  23. (C++14 前)赋值或复合赋值运算符
  24. throw 表达式
  25. 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 起)

注意:只是核心常量表达式,这点无任何直接语义含义:表达式必须为下列子集之一,以用于确定的语境中:

[编辑] 整型常量表达式

整数常量表达式是隐式转换成纯右值的整数或无作用域枚举类型表达式,其中被转换的表达式是核心常量表达式。若将类类型表达式用在期待整数常量表达式处,则将该表达式按语境隐式转换成整数或无作用域枚举类型。

只有整数常量表达式能用做 new 表达式中,第一维以外维度的数组边界 (C++14 前)位域长度、底层类型不固定时的枚举初始化器、空指针常量 (C++14 前)和对齐。

[编辑] 经转换的常量表达式

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

  • constexpr 用户定义转换(故类能用于期待整数类型处)
  • 左值到右值转换
  • 整数类型提升
  • 非窄化整数转换
  • 数组到指针转换
  • 函数到指针转换
  • 函数指针转换(指向 noexcept 函数的指针到指向函数的指针)
  • 限定转换
  • 源自 std::nullptr_t 的空指针转换
  • 源自 std::nullptr_t 的空成员指针转换
(C++17 起)
  • 而若发生任何引用绑定,则它是直接绑定(非构造临时对象者)
只有经转换的常量表达式能用做 case 表达式、固定底层类型时的枚举项初始化器 new 表达式中,第一维以外维度的数组边界 (C++14 起)整数与枚举 (C++17 前)非类型模板实参

按语境转换的 bool 类型常量表达式按语境转换到 bool 的表达式,其中转换后的表达式是常量表达式,且转换序列只含上述转换。这种表达式能用于 noexcept 规定static_assert 声明

常量表达式

常量表达式是

  • 指代下列者的泛左值核心常量表达式
  • 拥有静态存储期且非临时的对象,或
  • 拥有静态存储期的临时对象,但其值满足后述纯右值的制约,或
  • 函数
  • 值满足下列制约的纯右值核心常量表达式
  • 若值是类类型对象,则其每个引用类型的非静态数据成员指代满足上述对泛左值制约的实体
  • 若值有指针类型,则它保有
  • 拥有静态存储期的对象地址
  • 拥有静态存储期的对象的结尾后一位置地址
  • 函数地址
  • 空指针值
  • 若值拥有类或数组类型,则每个子对象满足这些对值的制约
(C++14 起)

字面常量表达式

字面常量表达式是非指针字面类型(在语境所要求的转换后)的纯右值核心常量表达式。数组或类类型的字面常量表达式要求每个子对象以常量表达式初始化。

引用常量表达式

引用常量表达式是指代拥有静态存储期的对象或函数的左值核心常量表达式。

地址常量表达式

地址常量表达式std::nullptr_t 类型或指针类型(在语境所要求的转换后)的纯右值核心常量表达式,它指向拥有静态存储期的对象、拥有静态存储期的数组结尾后一位置、函数,或为空指针。

(C++14 前)
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 是纯右值常量表达式
}

[编辑] 注意

不容许实现声明库函数为 constexpr ,除非标准说该函数为 constexpr

常量表达式中复制消除是强制的。

(C++14 起)

[编辑] 缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 2167 C++14 求值中局部的非成员引用会令求值为非 constexpr 允许非成员引用
CWG 1313 C++11 容许未定义行为,且禁止所有指针减法 同数组内的指针减法 OK ,禁止 UB
CWG 1952 C++11 要求诊断标准库未定义行为 未指定是否诊断库 UB

[编辑] 参阅