命名空间
变体
操作

constexpr 说明符 (自 C++11 起)

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句 (循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (C++17* 之前已弃用)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储时效说明符
初始化
表达式
替代表示
文字
布尔值 - 整数 - 浮点数
字符 - 字符串 - nullptr (C++11)
用户定义的 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
特定于类的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 

内容

[编辑] 解释

constexpr 说明符声明在编译时可以评估函数或变量的值。 然后,这些变量和函数可以在仅允许编译时 常量表达式 的地方使用(前提是给出了适当的函数参数)。

在对象声明 或非静态成员函数(C++14 之前) 中使用的 constexpr 说明符意味着 const。 在函数 静态 数据成员(自 C++17 起) 声明中使用的 constexpr 说明符意味着 inline。 如果函数或函数模板的任何声明都包含 constexpr 说明符,则每个声明都必须包含该说明符。

[编辑] constexpr 变量

constexpr 变量必须满足以下要求

  • 它必须具有常量析构,即:
  • 它不是类类型,也不是(可能是多维的)类类型的数组,或者
  • 如果它是类类型或(可能的多维)类类型的数组,该类类型具有一个 constexpr 析构函数,并且对于一个假设的表达式 e,其唯一作用是销毁对象,e 将是一个核心常量表达式,如果对象的生存期及其非可变子对象(但不是其可变子对象)被认为是在 e 内开始的。

如果一个 constexpr 变量不是 翻译单元局部 的,它不应该被初始化为引用一个在常量表达式中可用的翻译单元局部实体,也不应该有一个子对象引用这样一个实体。这种初始化在 模块接口单元(在其私有模块片段之外,如果有的话)或模块分区中是不允许的,并且在任何其他上下文中都是不推荐的。

(自 C++20 起)

[编辑] constexpr 函数

一个 constexpr 函数 必须满足以下要求

(直到 C++20)
(自 C++20 起)
  • 对于构造函数和析构函数(自 C++20 起),该类必须没有虚基类
  • 它的返回值(如果有)和它的每个参数必须是 LiteralType
  • 至少存在一组参数值,使得函数的调用可以是 核心常量表达式 的一个已计算的子表达式(对于构造函数,在 常量初始化 中使用就足够了)。对于违反此要点,不需要诊断。
(直到 C++23)
  • 函数体必须是删除的或默认的,或者仅包含以下内容:
(直到 C++14)
  • 函数体必须 包含::
  • 一个 goto 语句
  • 带有 标签 的语句,除了 casedefault
(直到 C++20)
  • 一个非文字类型的变量的定义
  • 一个静态或线程 存储期 的变量的定义
(一个函数体是 = default;= delete; 不包含以上任何内容。)
(自 C++14 起)
(直到 C++23)

constexpr 构造函数

一个 constexpr 构造函数,其函数体不是 = delete; 必须满足以下额外要求

  • 对于 类或结构体 的构造函数,每个基类子对象和每个 非变体 非静态数据成员必须被初始化。如果该类是 联合类,对于它的每个非空匿名联合成员,必须恰好初始化一个变体成员
  • 对于非空 联合体 的构造函数,必须恰好初始化一个非静态数据成员
(直到 C++20)
  • 每个用于初始化非静态数据成员和基类的构造函数必须是一个 constexpr 构造函数。

constexpr 析构函数

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

(直到 C++20)

一个 constexpr 析构函数,其函数体不是 = delete; 必须满足以下额外要求

  • 每个用于销毁非静态数据成员和基类的析构函数必须是一个 constexpr 析构函数。
(自 C++20 起)
(直到 C++23)

对于 constexpr 函数模板和类模板的 constexpr 成员函数,至少一个特化必须满足上述要求。其他特化仍然被认为是 constexpr,即使对这样一个函数的调用不能出现在常量表达式中。

如果没有模板特化在被视为非模板函数时会满足 constexpr 函数的要求,则该模板格式错误,不需要诊断。

(直到 C++23)

一个 constexpr 函数如果未标记 consteval,以非常量方式使用立即函数,并且是

  • 一个 lambda 的调用运算符,或者
  • 一个默认的特殊成员函数,或者
  • 一个标记为 constexpr 的模板实体的特化。
(自 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
(直到 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) 放宽的 constexprconst 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 函数中用于动态存储期的运算

[编辑] 关键字

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