Why does it working?
#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
CodePudding user response:
static_assert evaluates non constant expression
Nope, it most certainly does not. Constant evaluation has a strict set of conditions to is must obey in order to succeed.
For starters:
[dcl.dcl]
6 In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool.
"contextually converted" is standard lingo for "we'll consider explicit conversion operators". The place where it may become counter-intuitive is when "converted constant expression" is defined.
[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,
- [...]
The fine point is in the first sentence of the paragraph. The converted expression must be a constant expression. But the source expression doesn't have to be! So long as the conversion sequence is limited to the list in the paragraph and is valid constant evaluation itself, we are in the clear. In your example, the expression registryv2<0>::x
has type constant<true>
, it can be contextually converted to a bool
via the user defined conversion operator. And well, the conversion operator satisfiers all the requirements of a constexpr function and constant evaluation.
The list of requirements for constant evaluation is rather long so I won't go over it to verify every bullet is upheld. But I will demonstrate that we can trip one of them.
template<auto x> struct constant {
bool const x_ = x;
constexpr explicit operator auto() const { return x_; }
};
This change ::x);
templatebool
when it is not permitted.
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;
Going over the exceptions, none apply. So now registryv2<0>::x
is not a contextually converted constant expression of type bool
.
This also explain why true__
1 is verboten. Same issue, access to an object that is disallowed.
1 - That's a reserved identifier. Two consecutive underscores belong to the implementation for any use. Not critical to the issue at hand, but take note.
CodePudding user response:
It works because this conversion to auto operator is constexpr.
template<auto x> struct constant {
constexpr explicit operator auto() const { return x; }
};
It is called by the static_assert, even if marked explicit. It looks like putting an expession within a static_assert is akin to making an explicit cast to bool. The same is true for 'if' as in if (registryv2<0>::x)