constexpr
specifier
(since C++11)
目录 |
说明
constexpr 说明符声明可以在编译时对实体的值进行计算。随后这类实体便可在仅允许使用编译期 常量表达式 的场合使用(前提是提供了适当的函数参数)。
在对象声明 或非静态成员函数 (until C++14) 中使用的 constexpr 说明符隐含 const 语义。
在函数 或 静态 数据成员 (C++17起) 的首次声明中使用的 constexpr 说明符隐含 inline 。若函数或函数模板的任意声明含有 constexpr 说明符,则所有声明都必须包含该说明符。
constexpr 变量
若满足以下所有条件,变量 或变量模板 (C++14 起) 可被声明为 constexpr :
| (直至 C++26) | |
|
(自 C++26 起) |
|
若 constexpr 变量非 翻译单元局部 ,则其不应被初始化为引用可在常量表达式中使用的翻译单元局部实体,也不应包含引用此类实体的子对象。此类初始化在 模块接口单元 (不在其 私有模块片段 内)或模块分区中被禁止,在其他上下文中已被弃用。 |
(C++20 起) |
constexpr 函数
函数或函数模板可被声明为 constexpr 。
一个函数是 constexpr-suitable 的,当且仅当满足以下所有条件:
|
(C++20 前) |
|
(C++23 前) |
|
(C++20 起) |
|
(C++14 前) | ||
|
(C++14 起)
(C++23 前) |
除了实例化的 constexpr 函数外,非模板化的 constexpr 函数必须满足 constexpr 适用条件。
|
对于既非默认实现又非模板化的非构造函数 constexpr 函数,若不存在任何实参值使得该函数的调用能成为 核心常量表达式 的求值子表达式,则程序非良构,不要求诊断。 对于模板化的 constexpr 函数,若将该模板函数视为非模板函数时,函数/类模板的所有特化均无法使其成为常量表达式适用函数,则程序非良构,不要求诊断。 |
(C++23 前) |
在给定上下文中调用 constexpr 函数产生的结果,与在相同上下文中调用等效非 constexpr 函数的结果在所有方面均相同,但存在以下例外情况:
constexpr 构造函数
除了对 constexpr 函数的要求之外,构造函数还需要满足以下所有条件才能适用于 constexpr:
|
(C++23 前) |
- 该类没有任何 virtual base class 。
|
对于既非默认生成又非模板化的 constexpr 构造函数,若不存在任何实参值使得该函数的调用能成为某个受 常量表达式 约束对象的初始化全表达式中的被求值子表达式,则程序非良构,不要求诊断。 |
(直至 C++23) |
constexpr 析构函数
|
析构函数不能声明为 constexpr ,但 平凡析构函数 可以在常量表达式中隐式调用。 |
(C++20 前) | ||
|
除了 constexpr 函数的基本要求外,析构函数还需满足以下所有条件才能适用于常量表达式:
|
(C++20 起) |
注释
|
由于
constexpr int f(); constexpr bool b1 = noexcept(f()); // false, undefined constexpr function constexpr int f() { return 0; } constexpr bool b2 = noexcept(f()); // true, f() is a constant expression |
(until C++17) |
|
可以编写一个constexpr函数,其调用永远无法满足核心常量表达式的要求: void f(int& i) // not a constexpr function { i = 0; } constexpr void g(int& i) // well-formed since C++23 { f(i); // unconditionally calls f, cannot be a constant expression } |
(since C++23) |
对于非字面类型的类,允许使用常量表达式构造函数。例如, std::shared_ptr 的默认构造函数是 constexpr 的,这使得 常量初始化 成为可能。
引用变量可以声明为 constexpr(它们的初始化器必须是 引用常量表达式 ):
static constexpr int const& x = 42; // 指向常量整型对象的 constexpr 引用 // (该对象具有静态存储期 // 因静态引用引发的生命周期延长)
|
尽管在 constexpr 函数中允许使用 try 块和内联汇编,但在常量表达式中仍然不允许抛出 未被捕获的异常 (since C++26) 或执行汇编指令。 若变量具有常量析构,则无需生成机器代码来调用其析构函数,即使该析构函数非平凡。 非 lambda、非特殊成员且非模板化的 constexpr 函数不能隐式成为立即函数。用户需要显式标记其为 consteval 才能使此类预期函数定义合法。 |
(since C++20) |
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_constexpr
|
200704L
|
(C++11) | constexpr |
201304L
|
(C++14) | 放宽的 constexpr , 非 const 的 constexpr 方法 | |
201603L
|
(C++17) | 常量表达式lambda | |
201907L
|
(C++20) | constexpr 函数中的平凡 默认初始化 和 汇编声明 | |
202002L
|
(C++20) | 常量求值中更改联合体的活跃成员 | |
202110L
|
(C++23) | constexpr函数中的非 字面类型 变量、标签和 goto 语句 | |
202207L
|
(C++23) | 放宽部分 constexpr 限制 | |
202211L
|
(C++23) | 允许在 constexpr 函数中使用 static constexpr 变量 | |
202306L
|
(C++26) | 常量表达式从 void * 转换:迈向常量表达式类型擦除 | |
__cpp_constexpr_in_decltype
|
201711L
|
(C++11)
(DR) |
当 常量求值需要时 生成函数和变量定义 |
__cpp_constexpr_dynamic_alloc
|
201907L
|
(C++20) | constexpr 函数中的动态存储期操作 |
关键词
示例
定义用于计算阶乘的 C++11/14 constexpr 函数;定义扩展字符串字面量的字面量类型:
#include <iostream> #include <stdexcept> // C++11 constexpr 函数使用递归而非迭代 constexpr int factorial(int n) { return n <= 1 ? 1 : (n * factorial(n - 1)); } // C++14 constexpr 函数可使用局部变量和循环 #if __cplusplus >= 201402L constexpr int factorial_cxx14(int n) { int res = 1; while (n > 1) res *= n--; return res; } #endif // C++14 // 字面量类 class conststr { const char* p; std::size_t sz; public: template<std::size_t N> constexpr conststr(const char(&a)[N]): p(a), sz(N - 1) {} // constexpr 函数通过抛出异常来报告错误 // 在 C++11 中,必须通过条件运算符 ?: 实现 constexpr char operator[](std::size_t n) const { return n < sz ? p[n] : throw std::out_of_range(""); } constexpr std::size_t size() const { return sz; } }; // C++11 constexpr 函数必须将所有内容放在单个 return 语句中 //(C++14 无此要求) constexpr std::size_t countlower(conststr s, std::size_t n = 0, std::size_t c = 0) { return n == s.size() ? c : 'a' <= s[n] && s[n] <= 'z' ? countlower(s, n + 1, c + 1) : countlower(s, n + 1, c); } // 用于测试的需编译时常量的输出函数 template<int n> struct constN { constN() { std::cout << n << '\n'; } }; int main() { std::cout << "4! = "; constN<factorial(4)> out1; // 编译时计算 volatile int k = 8; // 使用 volatile 禁止优化 std::cout << k << "! = " << factorial(k) << '\n'; // 运行时计算 std::cout << "The number of lowercase letters in \"Hello, world!\" is "; constN<countlower("Hello, world!")> out2; // 隐式转换为 conststr constexpr int a[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; constexpr int length_a = sizeof a / sizeof(int); // C++17 中的 std::size(a), // C++20 中的 std::ssize(a) std::cout << "Array of length " << length_a << " has elements: "; for (int i = 0; i < length_a; ++i) std::cout << a[i] << ' '; std::cout << '\n'; }
输出:
4! = 24 8! = 40320 The number of lowercase letters in "Hello, world!" is 9 Array of length 12 has elements: 0 1 2 3 4 5 6 7 8 0 0 0
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用版本 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 1358 | C++11 |
模板化的
constexpr
函数还需要
至少有一个有效的参数值 |
无需此要求 |
| CWG 1359 | C++11 |
constexpr
联合体构造函数
必须初始化所有数据成员 |
对非空联合体仅初始化
一个数据成员 |
| CWG 1366 | C++11 |
函数体为
=
default
或
=
delete
的
constexpr 构造函数类可包含虚基类 |
此类不能包含
虚基类 |
| CWG 1595 | C++11 |
constexpr
委托构造函数要求
所有相关构造函数均为 constexpr |
仅要求目标构造函数
为 constexpr |
| CWG 1712 | C++14 |
constexpr
变量模板要求其所有声明
都包含 constexpr 说明符 [1] |
不再要求 |
| CWG 1911 | C++11 | 不允许非字面类型的 constexpr 构造函数 | 在常量初始化中允许 |
| CWG 2004 | C++11 |
允许在常量表达式中复制/移动
包含可变成员的联合体 |
可变成员使隐式
复制/移动不符合条件 |
| CWG 2022 | C++98 |
等价的
constexpr
与非
constexpr
函数是否产生相等结果可能 取决于是否执行复制消除 |
假定在常量表达式中
始终执行复制消除 |
| CWG 2163 | C++14 |
允许在
constexpr
函数中使用标签
尽管禁止 goto 语句 |
同时禁止标签 |
| CWG 2268 | C++11 |
根据
CWG 2004
的决议
禁止复制/移动含可变成员的联合体 |
若对象在常量表达式内
创建则允许 |
| CWG 2278 | C++98 | CWG 2022 的决议不可实现 |
假定在常量表达式中
从不执行复制消除 |
| CWG 2531 | C++11 |
非内联变量若用
constexpr
重声明
会变为内联 |
该变量不会
变为内联 |
- ↑ 这是冗余的,因为使用 constexpr 说明符的变量模板不能有多个声明。
参见
| 常量表达式 | 定义可在编译时求值的 表达式 |
consteval
说明符
(C++20)
|
指定函数为 立即函数 ,即每次对该函数的调用都必须在常量求值中执行 |
constinit
说明符
(C++20)
|
断言变量具有静态初始化,即 零初始化 和 常量初始化 |
|
C 文档
关于
constexpr
|
|