constexpr
说明符 (自 C++11 起)
constexpr
- 指定变量或函数的值可以出现在 常量表达式 中
内容 |
[编辑] 解释
constexpr
说明符声明在编译时可以评估函数或变量的值。 然后,这些变量和函数可以在仅允许编译时 常量表达式 的地方使用(前提是给出了适当的函数参数)。
在对象声明 或非静态成员函数(C++14 之前) 中使用的 constexpr
说明符意味着 const
。 在函数 或 静态 数据成员(自 C++17 起) 声明中使用的 constexpr
说明符意味着 inline
。 如果函数或函数模板的任何声明都包含 constexpr
说明符,则每个声明都必须包含该说明符。
[编辑] constexpr 变量
constexpr
变量必须满足以下要求
- 其类型必须是 LiteralType
- 它必须立即初始化
- 其初始化的 完整表达式,包括所有隐式转换、构造函数调用等,必须是 常量表达式
如果一个 constexpr 变量不是 翻译单元局部 的,它不应该被初始化为引用一个在常量表达式中可用的翻译单元局部实体,也不应该有一个子对象引用这样一个实体。这种初始化在 模块接口单元(在其私有模块片段之外,如果有的话)或模块分区中是不允许的,并且在任何其他上下文中都是不推荐的。 |
(自 C++20 起) |
[编辑] constexpr 函数
一个 constexpr 函数 必须满足以下要求
(直到 C++20) | |
|
(自 C++20 起) |
- 对于构造函数和析构函数(自 C++20 起),该类必须没有虚基类
|
(直到 C++23) |
(直到 C++14) | |||
|
(自 C++14 起) (直到 C++23) |
constexpr 构造函数一个 constexpr 构造函数,其函数体不是 = delete; 必须满足以下额外要求
constexpr 析构函数
|
(直到 C++23) |
对于 constexpr 函数模板和类模板的 constexpr 成员函数,至少一个特化必须满足上述要求。其他特化仍然被认为是 constexpr,即使对这样一个函数的调用不能出现在常量表达式中。
如果没有模板特化在被视为非模板函数时会满足 constexpr 函数的要求,则该模板格式错误,不需要诊断。 |
(直到 C++23) |
一个 constexpr 函数如果未标记 consteval,以非常量方式使用立即函数,并且是
|
(自 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 函数中是允许的,在常量表达式中仍然不允许抛出异常或执行汇编。 如果一个变量具有常量销毁,则不需要生成机器码来调用它的析构函数,即使它的析构函数不是平凡的。 一个非 lambda、非特殊成员和非模板化的 constexpr 函数不能隐式地成为一个立即函数。用户需要显式地将其标记为 consteval 以使这样一个预期的函数定义格式正确。 |
(自 C++20 起) |
功能测试宏 | 值 | Std | 功能 |
---|---|---|---|
__cpp_constexpr |
200704L | (C++11) | constexpr |
201304L | (C++14) | 放宽的 constexpr,非 const constexpr 方法 | |
201603L | (C++17) | Constexpr lambda | |
201907L | (C++20) | 平凡的 默认初始化 和 asm-声明 在 constexpr 函数中 | |
202002L | (C++20) | 在常量计算中改变联合体的活动成员 | |
202110L | (C++23) | 非 文字 变量、标签和 goto 语句在 constexpr 函数中 | |
202207L | (C++23) | 放宽一些 constexpr 限制 | |
202211L | (C++23) | 允许 static constexpr 变量在 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++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
CWG 1712 | C++14 | 要求 constexpr 变量模板的所有 声明都包含 constexpr 说明符 (它现在是冗余的,因为不可能有多个 带有 constexpr 说明符的变量模板声明) |
不再需要 |
CWG 1911 | C++11 | 不允许对非文字类型使用 constexpr 构造函数 | 允许在常量初始化中使用 |
CWG 2004 | C++11 | 带有可变成员的联合体的复制/移动 在常量表达式中是允许的 |
可变变体会取消资格 隐式复制/移动 |
CWG 2163 | C++14 | 在 constexpr 函数中允许使用标签 即使 gotos 被禁止 |
标签也被禁止 |
CWG 2268 | C++11 | 带有可变成员的联合体的复制/移动被
由CWG 问题 2004 的决议禁止。 |
如果对象是在 常量表达式中创建的,则允许。 |
[编辑] 另请参阅
常量表达式 | 定义一个可以在编译时求值的表达式。 |
consteval 说明符(C++20) |
指定函数是立即函数,即对函数的每次调用都必须在常量求值中进行。 |
constinit 说明符(C++20) |
断言变量具有静态初始化,即零初始化 和常量初始化。 |
C 文档 针对 constexpr
|