constexpr
说明符 (C++11 起)
目录 |
[编辑] 解释
constexpr 说明符声明实体的值可以在编译时求值。然后,这些实体可以在只允许编译时常量表达式的地方使用(前提是给出了适当的函数参数)。
用于对象声明或非静态成员函数(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++20 前) |
|
(直至 C++23) |
|
(C++20 起) |
|
(C++14 前) | ||
|
(C++14 起) (直至 C++23) |
除了实例化 constexpr 函数外,非模板 constexpr 函数必须是 constexpr-suitable 的。
对于非构造函数 constexpr 函数,如果既未默认也未模板化,如果不存在任何参数值使得函数调用可以是核心常量表达式的求值子表达式,则程序格式错误,不需要诊断。 对于模板化的 constexpr 函数,如果没有函数/类模板的特化在被视为非模板化函数时能使模板化函数 constexpr-suitable,则程序格式错误,不需要诊断。 |
(直至 C++23) |
在给定上下文中调用 constexpr 函数会产生与在相同上下文中调用等效的非 constexpr 函数相同的结果,除了以下情况:
[编辑] constexpr 构造函数
除了 constexpr 函数的要求外,构造函数还需要满足以下所有条件才能是 constexpr-suitable:
|
(直至 C++23) |
- 该类没有任何虚基类。
对于既未默认也未模板化的 constexpr 构造函数,如果不存在任何参数值使得函数调用可以是某个受常量表达式约束的对象的初始化完整表达式的求值子表达式,则程序格式错误,不需要诊断。 |
(直至 C++23) |
[编辑] constexpr 析构函数
析构函数不能是 constexpr,但平凡析构函数可以在常量表达式中隐式调用。 |
(C++20 前) | ||
除了 constexpr 函数的要求外,析构函数还需要满足以下所有条件才能是 constexpr-suitable:
|
(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 |
(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 } |
(C++23 起) |
对于非字面类型类,允许使用 constexpr 构造函数。例如,std::shared_ptr 的默认构造函数是 constexpr,允许常量初始化。
引用变量可以声明为 constexpr(其初始化器必须是引用常量表达式)。
static constexpr int const& x = 42; // constexpr reference to a const int object // (the object has static storage duration // due to life extension by a static reference)
尽管 try 块和内联汇编在 constexpr 函数中是允许的,但在常量表达式中仍然禁止抛出未捕获的(C++26 起)异常或执行汇编。 如果变量具有常量销毁,则无需生成机器码来调用其析构函数,即使其析构函数不是平凡的。 非 lambda、非特殊成员和非模板化的 constexpr 函数不能隐式地成为即时函数。用户需要显式地将其标记为 consteval 才能使此类预期函数定义格式正确。 |
(C++20 起) |
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_constexpr |
200704L |
(C++11) | constexpr |
201304L |
(C++14) | 放宽的 constexpr,非 const constexpr 方法 | |
201603L |
(C++17) | Constexpr lambda | |
201907L |
(C++20) | constexpr 函数中的平凡默认初始化和asm-declaration | |
202002L |
(C++20) | 常量求值中联合体活跃成员的改变 | |
202110L |
(C++23) | constexpr 函数中的非字面量变量、标签和goto 语句 | |
202207L |
(C++23) | 放宽一些 constexpr 限制 | |
202211L |
(C++23) | 允许在 constexpr 函数中使用 static constexpr 变量 | |
202306L |
(C++26) | 从 void* 的 constexpr 转换:走向 constexpr 类型擦除 | |
__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 functions use recursion rather than iteration constexpr int factorial(int n) { return n <= 1 ? 1 : (n * factorial(n - 1)); } // C++14 constexpr functions may use local variables and loops #if __cplusplus >= 201402L constexpr int factorial_cxx14(int n) { int res = 1; while (n > 1) res *= n--; return res; } #endif // C++14 // A literal class 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 functions signal errors by throwing exceptions // in C++11, they must do so from the conditional operator ?: 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 functions had to put everything in a single return statement // (C++14 does not have that requirement) 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); } // An output function that requires a compile-time constant, for testing template<int n> struct constN { constN() { std::cout << n << '\n'; } }; int main() { std::cout << "4! = "; constN<factorial(4)> out1; // computed at compile time volatile int k = 8; // disallow optimization using volatile std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time std::cout << "The number of lowercase letters in \"Hello, world!\" is "; constN<countlower("Hello, world!")> out2; // implicitly converted to conststr constexpr int a[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; constexpr int length_a = sizeof a / sizeof(int); // std::size(a) in C++17, // std::ssize(a) in C++20 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 | 带有 constexpr 构造函数的类,其函数体 是 = default 或 = delete 可以有虚基类 |
这样的类既不能 有虚基类 |
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
|