constexpr 说明符(C++11 起)

来自cppreference.com
< cpp‎ | language

解释

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

声明对象或非静态成员函数 (C++14 前)时使用 constexpr 说明符则同时蕴含 const。声明函数静态成员变量 (C++17 起)时使用 constexpr 说明符则同时蕴含 inline。如果一个函数或函数模板的某个声明拥有 constexpr 说明符,那么它的所有声明都必须含有该说明符。

constexpr 变量

constexpr 变量必须满足下列要求:

  • 它必须拥有常量析构,即:
  • 它不是类类型或它的(可能多维的)数组,或
  • 它是类类型或它的(可能多维的)数组,且该类类型拥有 constexpr 析构函数;对于作用仅为销毁该对象的虚设表达式 e,如果该对象与它的非 mutable 子对象(但不含它的 mutable 子对象)的生存期始于 e 内,那么 e 是核心常量表达式。
(C++20 起)

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

(C++20 起)

constexpr 函数

constexpr 函数必须满足下列要求:

(C++20 前)
(C++20 起)
  • 对于构造函数与析构函数 (C++20 起),该类必须无虚基类
(C++23 前)


  • 函数体必须被弃置或预置,或只含有下列内容:
(C++14 前)

  • 函数体必须含:
  • goto 语句
  • 拥有除 casedefault 之外的带有标签的语句
(C++20 前)
  • 非字面类型的变量定义
  • 静态或线程存储期变量的定义
(是 =default;=delete; 的函数体均不含任何上述内容。)
(C++14 起)
(C++23 前)


constexpr 构造函数

函数体不是 =delete;constexpr 构造函数必须满足下列额外要求:

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

constexpr 析构函数

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

(C++20 前)

函数体非 =delete;constexpr 析构函数必须满足下列额外要求:

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

对于 constexpr 函数模板和类模板的 constexpr 函数成员,必须至少有一个特化满足上述要求。其他特化仍被认为是 constexpr 的,尽管常量表达式中不能出现这种函数的调用。如果该模板的所有特化在视为非模板函数时都不能满足 constexpr 的要求,那么该模板非良构,不要求诊断。 (C++23 前)

注解

因为 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::unique_ptr 的默认构造函数是 constexpr,允许常量初始化

引用变量可声明为 constexpr(它的初始化器必须是引用常量表达式):

static constexpr int const& x = 42; // 到 const int 对象的 constexpr 引用
                                    // (该对象拥有静态存储期,因为静态引用延长了生存期)

尽管在 constexpr 函数中允许 try 块与内联汇编,但是常量表达式中仍然不允许抛出异常或执行汇编。

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

(C++20 起)

关键词

constexpr

示例

计算阶乘的 C++11 constexpr 函数的定义,及扩展字符串字面量的字面类型:

#include <iostream>
#include <stdexcept>
 
// C++11 constexpr 函数使用递归而非迭代
// (C++14 constexpr 函数可使用局部变量和循环)
constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}
 
// 字面类
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; // 隐式转换到常量字符串
}

输出:

4! = 24
8! = 40320
"Hello, world!" 里小写字母的个数是 9

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1712 C++14 一个 constexpr 变量模板的所有声明都需要包含 constexpr
说明符(此要求是多余的,因为一个 constexpr 变量模板
不会有多于一条包含 constexpr 说明符的声明)
不再需要
CWG 1911 C++11 非字面类型不允许拥有 constexpr 构造函数 在常量初始化中允许
CWG 2004 C++11 在常量表达式中允许复制/移动有 mutable 成员的联合体 mutable 变体现在无法被隐式复制/移动
CWG 2163 C++14 constexpr 函数中禁止 goto,但允许标号 标号也被禁止
CWG 2268 C++11 cwg 2004 曾禁止了复制/移动有 mutable 成员的联合体 在该对象在常量表达式中创建的情况下允许

参阅