命名空间
变体
操作

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

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

内容

[编辑] 解释

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

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

在函数static 数据成员(自 C++17 起) 的首次声明中使用的 constexpr 说明符隐含 inline。 如果函数或函数模板的任何声明具有 constexpr 说明符,则每个声明都必须包含该说明符。

[编辑] constexpr 变量

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

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

  • 它具有常量析构,这意味着需要满足以下条件之一
  • 它不是类类型,也不是其(可能是多维)数组。
  • 它是具有 constexpr 析构函数的类类型或其(可能是多维)数组,并且对于一个假设的表达式 e,其唯一效果是销毁对象,如果对象的生命周期及其非可变子对象(但不是其可变子对象)被认为在 e 内开始,则 e 将是核心常量表达式

如果 constexpr 变量不是翻译单元局部,则不应将其初始化为引用可在常量表达式中使用的翻译单元局部实体,也不应具有引用此类实体的子对象。 在模块接口单元(在其私有模块片段之外,如果有)或模块分区中,不允许进行此类初始化,并且在任何其他上下文中都不推荐使用。

(自 C++20 起)

[编辑] constexpr 函数

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

如果满足以下所有条件,则函数是constexpr 适用的

  • 如果是构造函数或析构函数(自 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 适用的。

对于既非默认也非模板的非构造函数 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 适用的

  • 对于类类型或其(可能是多维)数组的每个子对象,该类类型都具有 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)

即使 constexpr 函数中允许 try 块和内联汇编,但在常量表达式中仍然不允许抛出未捕获的(自 C++26 起)异常或执行汇编。

如果变量具有常量析构,则无需生成机器代码即可为其调用析构函数,即使其析构函数不是平凡的。

非 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) constexpr 函数中的非字面变量、标签和 goto 语句
202207L (C++23) 放宽一些 constexpr 限制
202211L (C++23) 允许 constexpr 函数中的 static constexpr 变量
202306L (C++26) constexpr 从 void* 的转型:迈向 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 1358 C++11 模板 constexpr 函数也需要
至少有一个有效的实参值
不需要
CWG 1359 C++11 constexpr 联合体构造函数
必须初始化所有数据成员
为非空联合体
初始化恰好一个数据成员
CWG 1366 C++11 具有函数体为
= default= deleteconstexpr 构造函数的类可能具有虚基类
此类不能
具有虚基类
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 即使禁止使用 goto 语句,也允许在 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