首页 > 解决方案 > static_assert 计算非常量表达式

问题描述

为什么它起作用?

#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

标签: c++c++17language-lawyer

解决方案


static_assert 计算非常量表达式

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

对于初学者:

[dcl.dcl]

6在 static_assert-declaration 中,常量表达式应为 bool 类型的上下文转换的常量表达式。

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

[expr.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_; }
};

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

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

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

    • 一个整数或枚举类型的非易失性左值,它引用一个完整的非易失性 const 对象,该对象具有前面的初始化,用常量表达式初始化,或

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

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

    • 文字类型的非易失性左值,它引用一个非易失性对象,其生命周期在 e 的评估中开始;

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

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


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


推荐阅读