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 构造函数函数体不是 =delete; 的 constexpr 构造函数必须满足下列额外要求:
constexpr 析构函数
|
(C++23 前) |
对于 constexpr 函数模板和类模板的 constexpr 成员函数,必须至少有一个特化满足上述要求。其他特化仍被认为是 constexpr 的,尽管常量表达式中不能出现这种函数的调用。
如果该模板的所有特化在视为非模板函数时都不能满足 constexpr 的要求,那么该模板非良构,不要求诊断。 |
(C++23 前) |
constexpr 函数,如果未被标为 consteval,以非常量方式使用了立即函数,并且是
则它隐式变为立即函数。 |
(C++20 起) |
注解
因为 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 函数: void f(int& i) // 不是 constexpr 函数 { i = 0; } constexpr void g(int& i) // C++23 起良构 { f(i); // 无条件调用 f,不可能是常量表达式 } |
(C++23 起) |
constexpr 构造函数允许用于非字面类型的类。例如,std::shared_ptr 的默认构造函数是 constexpr,允许进行常量初始化。
引用变量可声明为 constexpr(它的初始化式必须是引用常量表达式):
static constexpr int const& x = 42; // 到 const int 对象的 constexpr 引用 // (该对象拥有静态存储期,因为静态引用延长了生存期)
尽管在 constexpr 函数中允许 try 块与内联汇编,但是常量表达式中仍然不允许抛出异常或执行汇编。 如果变量拥有常量析构,那么无需为调用它的析构函数而生成机器码,即使它的析构函数不平凡。 非 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 函数中的平凡默认初始化和汇编声明 | |
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 函数使用递归而非迭代 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 << "\"Hello, world!\" 里小写字母的个数是 "; 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 << "长度为 " << length_a << " 的数组中各元素为: "; for (int i = 0; i < length_a; ++i) std::cout << a[i] << ' '; std::cout << '\n'; }
输出:
4! = 24 8! = 40320 "Hello, world!" 里小写字母的个数是 9 长度为 12 的数组中各元素为: 0 1 2 3 4 5 6 7 8 0 0 0
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1712 | C++14 | constexpr 变量模板的所有声明都需要包含 constexpr 说明符 (此要求是多余的,因为 constexpr 变量模板的声明不会有多于一条的 constexpr 说明符) |
不再需要 |
CWG 1911 | C++11 | 非字面类型不允许拥有 constexpr 构造函数 | 在常量初始化中允许 |
CWG 2004 | C++11 | 在常量表达式中允许复制/移动有 mutable 成员的联合体 | mutable 变体现在无法被隐式复制/移动 |
CWG 2163 | C++14 | constexpr 函数中禁止 goto,但允许标号 | 标号也被禁止 |
CWG 2268 | C++11 | cwg 2004 曾禁止了复制/移动有 mutable 成员的联合体 | 在该对象在常量表达式中创建的情况下允许 |
参阅
常量表达式 | 定义可在编译时求值的表达式 |
consteval 说明符(C++20)
|
指定函数为立即函数,即对该函数的每次调用必须在常量求值中进行 |
constinit 说明符(C++20)
|
断言变量拥有静态初始化,即零初始化与常量初始化 |