static_assert 计算非常量表达式

Own*_*gic 5 c++ language-lawyer c++17

为什么它有效?

#include <cstdio>

template<auto x> struct constant {
  constexpr operator auto() { return x; }
};
constant<true> true_;

static constexpr const bool true__ = true;

template<auto tag> struct registryv2 {
  // not constexpr
  static auto push() {
    fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);

    //*
    return true_; // compiles
    /*/
    return true__; // read of non-const variable 'x' is not allowed in a constant expression
    //*/
  }
  // not constexpr either
  static inline auto x = push();
};

static_assert(registryv2<0>::x);
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/GYTdE3M9q

Sto*_*ica 2

static_assert 计算非常量表达式

不,它肯定不是。持续评估有一套严格的条件,必须遵守才能成功。

对于初学者:

[dcl.dcl]

6在 static_assert 声明中,常量表达式应为根据上下文转换的 bool 类型常量表达式。

“根据上下文转换”是“我们将考虑显式转换运算符”的标准行话。它可能变得违反直觉的地方是定义“转换的常量表达式”时。

[表达式.const]

4 T 类型的转换常量表达式是隐式转换为 T 类型的表达式,其中转换后的表达式是常量表达式,并且隐式转换序列仅包含

  • 用户定义的转换,
  • [...]

要点在于该段的第一句。转换后的表达式必须是常量表达式。但表达式不一定是!只要转换序列仅限于段落中的列表并且是有效的常量评估本身,我们就清楚了。在您的示例中,表达式registryv2<0>::x具有 type constant<true>,它可以bool通过用户定义的转换运算符根据上下文转换为 a 。而且,转换运算符满足 constexpr 函数和常量求值的所有要求。

持续评估的要求清单相当长,因此我不会仔细检查以验证每一条要点是否得到支持。但我将证明我们可以绊倒其中一个。

template<auto x> struct constant {
  bool const x_ = x;
  constexpr explicit operator auto() const { return x_; }
};
Run Code Online (Sandbox Code Playgroud)

此更改立即使 godbolt 代码示例格式错误。为什么?因为我们在 a 上进行左值到右值的转换(访问的标准术语),bool而这是不允许的。

表达式 e 是核心常量表达式,除非对 e 的求值遵循抽象机的规则,将求值以下表达式之一:

  • 左值到右值的转换,除非它应用于

    • 整型或枚举类型的非易失性泛左值,引用具有前面初始化的完整非易失性 const 对象,并使用常量表达式进行初始化,或者

    • 引用字符串文字的子对象的非易失性泛左值,或者

    • 一个非易失性泛左值,它引用用 constexpr 定义的非易失性对象,或者引用此类对象的非可变子对象,或者

    • 文字类型的非易失性泛左值,指的是其生命周期开始于 e 求值期间的非易失性对象;

回顾一下例外情况,没有一个适用。所以 nowregistryv2<0>::x不是一个上下文转换的 type 常量表达式bool

这也解释了为什么true__1被禁止。同样的问题,访问被禁止的对象。


1 - 这是一个保留的标识符。两个连续的下划线属于任何用途的实现。对于当前的问题并不重要,但请注意。