解释

constexpr 说明符声明可以在编译时求得函数或变量的值。然后这些变量和函数(若给定了合适的函数实参)即可用于仅允许编译时常量表达式之处。用于对象或非静态成员函数 (C++14 前)声明的 constexpr 说明符蕴含 const。用于函数声明的 constexpr 说明符或 static 成员变量 (C++17 起)蕴含 inline。若函数或函数模板的任何声明拥有 constexpr 说明符,则每个声明必须都含有该说明符。

constexpr 变量必须满足下列要求:



-
- 它必须拥有常量析构,即:
-
- 它既非类类型亦非其(可能多维的)数组,或、
- 它是类类型或其(可能多维的)数组,该类类型拥有 constexpr 析构函数,而对于仅有的作用为销毁该对象的虚设表达式 e ,假如认为该对象与其非 mutable 子对象(但非其 mutable 子对象)的生存期始于 e 内,则 e 会是核心常量表达式。
(C++20 起)

constexpr 函数必须满足下列要求:



-
- 它必须非
(C++20 前)


-
- 它必须不是协程
(C++20 起)


-
- 其函数体必须不是函数 try 块
(C++20 前)


-
- 函数体必须被弃置或预置,或只含有下列内容:
-
- 空语句(仅分号)
- static_assert 声明
- 不定义类或枚举的 typedef 声明及别名声明
- using 声明
- using 指令
- 恰好一条 return 语句,若函数不是构造函数。
(C++14 前)


-
- 函数体必须含:
-
- goto 语句
- 拥有除 casedefault 之外的标号的语句




-
-
- try 块
- asm 声明
- 不进行初始化的变量定义。
(C++20 前)
      • 非字面类型的变量定义
      • 静态或线程存储期变量的定义
    • (=default; 或 =delete; 的函数体不含任何上述内容。)
(C++14 起)

函数体非 =delete; 的 constexpr 构造函数必须满足下列额外要求:



-
- 对于 class 或 struct 的构造函数,每个子对象和每个非变体非 static 数据成员必须被初始化。若类是联合体式的类,对于其每个非空匿名联合体成员,必须恰好有一个变体成员被初始化
- 对于非空 union 的构造函数,恰好有一个非静态数据成员被初始化
(C++20 前)
    • 每个被选用于初始化非静态成员和基类的构造函数必须是 constexpr 构造函数。

析构函数不能为 constexpr ,但能在常量表达式中调用平凡析构函数
(C++20 前)

函数体非 =delete; 的 constexpr 析构函数必须满足下列额外要求:


-
- 每个用于销毁非静态数据成员与基类的析构函数必须为 constexpr 析构函数。
(C++20 起)

对于 constexpr 函数模板和类模板的 constexpr 函数成员,必须至少有一个特化满足上述要求。其他特化仍被认为是 constexpr,尽管常量表达式中不能出现这种函数的调用。

注解


因为 noexcept 运算符始终对常量表达式返回 true,故它可用于检查具体特定的 constexpr 函数调用是否采用常量表达式分支:




  1. constexpr int f();
    constexpr bool b1 = noexcept(f()); // false,constexpr 函数未定义
    constexpr int f() { return 0; }
    constexpr bool b2 = noexcept(f()); // true,f() 是常量表达式



(C++17 前)

constexpr 构造函数允许用于非字面类型的类。例如,std::unique_ptr 的默认构造函数是 constexpr,允许常量初始化

引用变量可声明为 constexpr(其初始化器必须是引用常量表达式):

  1. static constexpr int const& x = 42; // 到 const int 对象的 constexpr 引用
  2. // (该对象拥有静态存储期,因为 static 引用续命)

尽管在 constexpr 函数中允许 try 块与内联汇编,常量表达式中仍然不允许抛异常或执行汇编。

若 constexpr 变量拥有常量析构,则无需为调用其析构函数而生成机器码。
(C++20 起)

关键词

constexpr

示例

计算阶乘的 C++11 constexpr 函数的定义,及扩展字符串字面量的字面类型:

运行此代码

  1. #include <iostream>
  2. #include <stdexcept>
  3.  
  4. // C++11 constexpr 函数使用递归而非迭代
  5. // (C++14 constexpr 函数可使用局部变量和循环)
  6. constexpr int factorial(int n)
  7. {
  8. return n <= 1? 1 : (n * factorial(n - 1));
  9. }
  10.  
  11. // 字面类
  12. class conststr {
  13. const char* p;
  14. std::size_t sz;
  15. public:
  16. template<std::size_t N>
  17. constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {}
  18.  
  19. // constexpr 函数通过抛异常来提示错误
  20. // C++11 中,它们必须用条件运算符 ?: 这么做
  21. constexpr char operator[](std::size_t n) const
  22. {
  23. return n < sz ? p[n] : throw std::out_of_range("");
  24. }
  25. constexpr std::size_t size() const { return sz; }
  26. };
  27.  
  28. // C++11 constexpr 函数必须把一切放在单条 return 语句中
  29. // (C++14 无此要求)
  30. constexpr std::size_t countlower(conststr s, std::size_t n = 0,
  31. std::size_t c = 0)
  32. {
  33. return n == s.size() ? c :
  34. 'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) :
  35. countlower(s, n + 1, c);
  36. }
  37.  
  38. // 输出要求编译时常量的函数,用于测试
  39. template<int n>
  40. struct constN
  41. {
  42. constN() { std::cout << n << '\n'; }
  43. };
  44.  
  45. int main()
  46. {
  47. std::cout << "4! = " ;
  48. constN<factorial(4)> out1; // 在编译时计算
  49.  
  50. volatile int k = 8; // 不允许使用 volatile 者优化
  51. std::cout << k << "! = " << factorial(k) << '\n'; // 运行时计算
  52.  
  53. std::cout << "the number of lowercase letters in \"Hello, world!\" is ";
  54. constN<countlower("Hello, world!")> out2; // 隐式转换到 conststr
  55. }

输出:

  1. 4! = 24
  2. 8! = 40320
  3. the number of lowercase letters in "Hello, world!" is 9

缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1911 C++14 不允许对于非字面类型的 constexpr 构造函数 在常量初始化中允许
CWG 2004 C++14 在常量表达式中允许复制/移动有 mutable 成员的联合体 去除 mutable 变体隐式复制/移动的资格
CWG 2163 C++14 尽管 goto 在 constexpr 函数中被禁止,标号却得到允许 标号也被禁止
CWG 2268 C++14 cwg 2004 曾禁止了复制/移动有 mutable 成员的联合体 若该对象在常量表达式中创建,则允许