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);
之所以有效,是因为这种到 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 - 这是一个保留标识符。两个连续的下划线属于任意使用的实现。对手头的问题并不重要,但请注意。
为什么有效?
#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);
之所以有效,是因为这种到 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 - 这是一个保留标识符。两个连续的下划线属于任意使用的实现。对手头的问题并不重要,但请注意。