Namespaces
Variants

constexpr specifier (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

目录

说明

constexpr 说明符声明可以在编译时对实体的值进行计算。随后这类实体便可在仅允许使用编译期 常量表达式 的场合使用(前提是提供了适当的函数参数)。

在对象声明 或非静态成员函数 (until C++14) 中使用的 constexpr 说明符隐含 const 语义。

在函数 静态 数据成员 (C++17起) 的首次声明中使用的 constexpr 说明符隐含 inline 。若函数或函数模板的任意声明含有 constexpr 说明符,则所有声明都必须包含该说明符。

constexpr 变量

若满足以下所有条件,变量 或变量模板 (C++14 起) 可被声明为 constexpr

(直至 C++26)
(自 C++26 起)

  • 具有常量销毁特性,需满足以下任一条件:
  • 该对象不是类类型或其(可能多维)数组。
  • 该对象是具有 constexpr 析构函数的类类型或其(可能多维)数组,且对于仅作用为销毁该对象的假设表达式 e ,若将该对象及其非可变子对象(但不含可变子对象)的生命周期视为始于 e ,则 e 应为 核心常量表达式

constexpr 变量非 翻译单元局部 ,则其不应被初始化为引用可在常量表达式中使用的翻译单元局部实体,也不应包含引用此类实体的子对象。此类初始化在 模块接口单元 (不在其 私有模块片段 内)或模块分区中被禁止,在其他上下文中已被弃用。

(C++20 起)

constexpr 函数

函数或函数模板可被声明为 constexpr

一个函数是 constexpr-suitable 的,当且仅当满足以下所有条件:

  • 若它是构造函数 或析构函数 (C++20 起) ,则其类没有任何 虚基类
(C++20 前)
  • 其返回类型(若存在)为 字面类型
  • 其各参数类型均为字面类型。
(C++23 前)
(C++20 起)
  • 其函数体为 = default = delete ,或仅包含下列内容的复合语句 (封闭)
(C++14 前)
  • 其函数体为 = default = delete ,或 复合语句 (C++20 前) 包含 下列内容:
(C++20 前)
  • 非字面类型的变量定义
  • 静态或线程 存储期 的变量定义
(C++14 起)
(C++23 前)

除了实例化的 constexpr 函数外,非模板化的 constexpr 函数必须满足 constexpr 适用条件。

对于既非默认实现又非模板化的非构造函数 constexpr 函数,若不存在任何实参值使得该函数的调用能成为 核心常量表达式 的求值子表达式,则程序非良构,不要求诊断。

对于模板化的 constexpr 函数,若将该模板函数视为非模板函数时,函数/类模板的所有特化均无法使其成为常量表达式适用函数,则程序非良构,不要求诊断。

(C++23 前)

在给定上下文中调用 constexpr 函数产生的结果,与在相同上下文中调用等效非 constexpr 函数的结果在所有方面均相同,但存在以下例外情况:

constexpr 构造函数

除了对 constexpr 函数的要求之外,构造函数还需要满足以下所有条件才能适用于 constexpr:

  • 其函数体为 = delete 或满足以下附加要求:
  • 若该类为具有变体成员的 联合体 ,则仅初始化其中一个变体成员。
  • 若该类为 类联合类型 (但非联合体),则对于其每个包含变体成员的匿名联合体成员,仅初始化其中一个变体成员。
  • 每个非变体非静态数据成员及基类子对象均被初始化。
(C++20 前)
  • 若该构造函数为 委托构造函数 ,则目标构造函数必须是 constexpr 构造函数。
  • 若该构造函数为非委托构造函数,则所有用于初始化非静态数据成员和基类子对象的构造函数必须是 constexpr 构造函数。
(C++23 前)

对于既非默认生成又非模板化的 constexpr 构造函数,若不存在任何实参值使得该函数的调用能成为某个受 常量表达式 约束对象的初始化全表达式中的被求值子表达式,则程序非良构,不要求诊断。

(直至 C++23)

constexpr 析构函数

析构函数不能声明为 constexpr ,但 平凡析构函数 可以在常量表达式中隐式调用。

(C++20 前)

除了 constexpr 函数的基本要求外,析构函数还需满足以下所有条件才能适用于常量表达式:

  • 对于类类型的每个子对象或其(可能多维的)数组,该类型必须具有 constexpr 析构函数。
(C++23 前)
  • 该类不能拥有任何虚基类。
(C++20 起)

注释

由于 noexcept 运算符对于常量表达式总是返回 true ,因此可用于检查 constexpr 函数的特定调用是否采用常量表达式分支:

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 函数中的动态存储期操作

关键词

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 重声明
会变为内联
该变量不会
变为内联
  1. 这是冗余的,因为使用 constexpr 说明符的变量模板不能有多个声明。

参见

常量表达式 定义可在编译时求值的 表达式
consteval 说明符 (C++20) 指定函数为 立即函数 ,即每次对该函数的调用都必须在常量求值中执行
constinit 说明符 (C++20) 断言变量具有静态初始化,即 零初始化 常量初始化
C 文档 关于 constexpr