constexpr 指定符(C++11 起)

来自cppreference.com
< cpp‎ | language

目录

[编辑] 解释

constexpr 指定符声明可以在编译时求得函数或变量的值。然后这些变量和函数(若给定了合适的函数参数)可用于仅允许编译时常量表达式之处。用于对象的 constexpr 指定符隐含 const 。用于函数的 constexpr 指定符static 成员变量 (C++17 起)隐含 inline

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

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

  • 它必须非
  • 其返回类型必须是字面类型 (LiteralType)
  • 其每个参数都必须是字面类型 (LiteralType)
  • 至少存在一组参数值,令函数的一个调用可以为已求值的核心常量表达式的子表达式(对于构造函数,在常量初始化器中使用就够了 (C++14 起))。不要求对此行为的诊断。
  • 函数体必须是被删除或被默认化,或只含有下列内容:
(C++14 前)
  • 函数体必须是被删除或被默认化,或含有下列内容外的任何语句
  • 若函数是默认化的复制/移动赋值,则其所属的类不可拥有 mutable 变体成员
(C++14 起)

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

  • 构造函数体必须被删除或被默认化或只含有下列内容:
  • 每个基类和每个非 static 成员都必须被初始化,要么在构造函数初始化列表中,要么通过成员花括号或等号初始化器。另外,其所涉及的每个构造函数都必须是 constexpr 构造函数,且每个花括号或等号初始化器都必须是常量表达式。
(C++14 前)
  • 构造函数体必须被删除或被默认化或满足下列限制:
  • 构造函数体的复合语句必须满足 constexpr 函数体的限制
  • 对于 class 或 struct 的构造函数,每个子对象和每个非变体非 static 数据成员必须被初始化。若类是类联合体类,对于其每个非空匿名联合体成员,必须恰好有一个变体成员被初始化
  • 对于非空 union 的构造函数,恰好有一个非 static 数据成员被初始化
  • 每个被选作初始化非 static 成员和基类的构造函数必须是 constexpr 构造函数。
  • 若构造函数是默认化的复制/移动构造函数,则其所属的类不可拥有 mutable 变体成员
(C++14 起)

对于 constexpr 函数模板和类模板的 constexpr 函数成员,至少一个实例须满足上述要求。

[编辑] 注意

因为 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() 是常量表达式

constexpr 构造函数允许用于非字面类型的类。例如, std::unique_ptr 的默认构造函数是 constexpr ,允许常量初始化

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

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

[编辑] 关键词

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 << "the number of lowercase letters in \"Hello, world!\" is ";
    constN<countlower("Hello, world!")> out2; // 隐式转换到 conststr
}

输出:

4! = 24
8! = 40320
the number of lowercase letters in "Hello, world!" is 9

[编辑] 缺陷报告

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

DR 应用于 出版时的行为 正确行为
CWG 1911 C++14 不允许对于非字面类型的 constexpr 构造函数 在常量初始化中允许
CWG 2004 C++14 在常量表达式中允许赋值给有 mutable 成员的联合体 去除 mutable 变体隐式复制/移动的资格
CWG 2163 C++14 尽管 goto 在 constexpr 函数中被禁止,标号却得到允许 标号也被禁止

[编辑] 参阅