constexpr 说明符 (C++11 起)

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

目录

[编辑] 解释

constexpr 说明符声明可以在编译时对实体求值。这些实体(给定了合适的函数实参的情况下)即可用于需要编译期常量表达式的地方。

对象或非静态成员函数(C++14 前)声明中的 constexpr 说明符蕴含 const

函数静态数据成员(C++17 起)首个声明中的 constexpr 说明符蕴含 inline。如果函数或函数模板的一个声明拥有 constexpr 说明符,那么它的所有声明都必须含有该说明符。

[编辑] constexpr 变量

如果以下条件都满足,那么变量或变量模板(C++14 起)可声明为 constexpr

(C++26 前)
(C++26 起)

  • 它具有常量析构,即满足以下条件之一:
  • 它不是类类型或它的(可能多维的)数组。
  • 它是带有 constexpr 析构函数的类类型或它的(可能多维的)数组,并且对于作用仅为销毁该对象的虚设表达式 e,如果该对象与它的非 mutable 子对象(但不含它的 mutable 子对象)的生存期始于 e 内,那么 e核心常量表达式

如果 constexpr 变量不是翻译单元局部的,那么它不应被初始化为指代可用于常量表达式的翻译单元局部实体,也不能有指代这种实体的子对象。这种初始化在模块接口单元(在它的私有模块片段外,如果存在)或模块分区中被禁止,并在任何其他语境中被弃用。

(C++20 起)

[编辑] constexpr 函数

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

满足以下全部条件时,函数适于 constexpr

  • 如果它是构造函数或析构函数(C++20 起),那么它的类没有任何虚基类
  • 它不是函数。
(C++20 前)
  • 它的返回类型(如果存在)是字面类型
  • 它的每个形参类型都是字面类型。
(C++23 前)
(C++20 起)
  • 它的函数体是 = default= delete 或者只包围下列内容的复合语句:
(C++14 前)
  • 它的函数体是 = default= delete 或者包围下列内容的复合语句:
  • 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,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 块与内联汇编,但是常量表达式中仍然不允许抛出未捕获(C++26 起)异常或执行汇编。

如果变量拥有常量析构,那么无需为调用它的析构函数而生成机器码,即使它的析构函数不平凡。

非 lambda、非特殊成员且非模板化的 constexpr 函数不能隐式变为立即函数。用户需要将之显式标为 consteval 以使这样的函数定义良构。

(C++20 起)
功能特性测试宏 标准 功能特性
__cpp_constexpr 200704L (C++11) constexpr
201304L (C++14) 放宽 constexprconstconstexpr 方法
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 函数中允许 staticconstexpr 变量
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 函数使用递归而非迭代
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 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 在常量表达式中允许复制/移动有 mutable 成员的联合体 mutable 变体现在无法被隐式复制/移动
CWG 2022 C++98 等价的 constexpr 和非 constexpr 函数是否
产生相等结果可能会取决于是否进行复制消除
假设始终会进行复制消除
CWG 2163 C++14 constexpr 函数中禁止 goto 语句,但允许标号 标号也被禁止
CWG 2268 C++11 CWG 问题 2004 禁止了复制/移动有 mutable 成员的联合体 在该对象在常量表达式中创建的情况下允许
CWG 2278 C++98 CWG 问题 2022 的解决方案无法实现 假设始终不会进行复制消除
CWG 2531 C++11 当非内联变量以 constexpr 重新声明后变为内联 不会变为内联
  1. 此要求是多余的,因为每个变量模板最多只有一条带有 constexpr 说明符的声明。

[编辑] 参阅

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