static_assert 计算非常量表达式

static_assert evaluates non constant expression

为什么有效?

#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);

https://godbolt.org/z/GYTdE3M9q

之所以有效,是因为这种到 auto 运算符的转换是 constexpr。

template<auto x> struct constant {
    constexpr explicit operator auto() const { return x; }
};

它由 static_assert 调用,即使标记为显式。看起来将表达式放在 static_assert 中类似于对 bool 进行显式转换。 'if' 和 if (registryv2<0>::x)

一样

static_assert evaluates non constant expression

不,它肯定不会。常量求值有一组严格的条件,必须遵守才能成功。

初学者:

[dcl.dcl]

6 In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool.

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

[expr.const]

4 A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only

  • user-defined conversions,
  • [...]

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

list of requirements for constant evaluation is rather long 所以我不会检查它来验证是否支持每一个项目符号。但我会证明我们可以绊倒其中之一。

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

这个变化immediately makes the godbolt code sample ill-formed。为什么?因为我们在 bool 上进行左值到右值的转换(访问的标准术语)是不允许的。

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • an lvalue-to-rvalue conversion unless it is applied to

    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or

    • a non-volatile glvalue that refers to a subobject of a string literal, or

    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or

    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

检查例外情况,none 适用。所以现在 registryv2<0>::x 不是 bool.

类型的上下文转换常量表达式

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


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