命名空间
变体
操作

constexpr 说明符 (C++11 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

目录

[编辑] 解释

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

用于对象声明或非静态成员函数(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 前) 包含以下内容:
  • goto 语句
  • 具有除 casedefault 之外的标签的语句
(C++20 前)
(C++14 起)
(直至 C++23)

除了实例化 constexpr 函数外,非模板 constexpr 函数必须是 constexpr-suitable 的。

对于非构造函数 constexpr 函数,如果既未默认也未模板化,如果不存在任何参数值使得函数调用可以是核心常量表达式的求值子表达式,则程序格式错误,不需要诊断。

对于模板化的 constexpr 函数,如果没有函数/类模板的特化在被视为非模板化函数时能使模板化函数 constexpr-suitable,则程序格式错误,不需要诊断。

(直至 C++23)

在给定上下文中调用 constexpr 函数会产生与在相同上下文中调用等效的非 constexpr 函数相同的结果,除了以下情况:

[编辑] constexpr 构造函数

除了 constexpr 函数的要求外,构造函数还需要满足以下所有条件才能是 constexpr-suitable:

  • 其函数体是 = delete 或满足以下附加要求:
  • 如果类是带有变体成员的联合,则其中恰好有一个被初始化。
  • 如果类是类联合,但不是联合,则对于其每个具有变体成员的匿名联合成员,恰好有一个被初始化。
  • 每个非变体非静态数据成员和基类子对象都已初始化。
(C++20 前)
  • 如果构造函数是委托构造函数,则目标构造函数是 constexpr 构造函数。
  • 如果构造函数是非委托构造函数,则选择用于初始化非静态数据成员和基类子对象的每个构造函数都是 constexpr 构造函数。
(直至 C++23)

对于既未默认也未模板化的 constexpr 构造函数,如果不存在任何参数值使得函数调用可以是某个受常量表达式约束的对象的初始化完整表达式的求值子表达式,则程序格式错误,不需要诊断。

(直至 C++23)

[编辑] constexpr 析构函数

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

(C++20 前)

除了 constexpr 函数的要求外,析构函数还需要满足以下所有条件才能是 constexpr-suitable:

  • 对于类类型或(可能多维)数组的每个子对象,该类类型都具有 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
(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) 放宽的 constexprconst 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 函数中的动态存储持续时间操作

[编辑] 关键词

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

[编辑] 另请参阅

常量表达式 定义可在编译时求值的表达式
consteval 说明符(C++20) 指定函数是一个即时函数,即对函数的所有调用都必须在常量求值中[编辑]
constinit 说明符(C++20) 断言变量具有静态初始化,即零初始化常量初始化[编辑]
C 文档,关于 constexpr