The following code fails to compile with Clang 13 and MSVC v19.29 VS16.11 but successfully compiles with GCC 11.2.
template <typename...>
concept always_true = true;
template <typename T>
concept refable = always_true<T&>;
static_assert(refable<void>);
Clang 13:
<source>:9:1: error: static_assert failed
static_assert(refable<void>);
^ ~~~~~~~~~~~~~
<source>:9:15: note: because 'void' does not satisfy 'refable'
static_assert(refable<void>);
^
<source>:7:32: note: because substituted constraint expression is ill-formed: cannot form a reference to 'void'
concept refable = always_true<T&>;
^
MSVC v19.29 VS16.11 :
<source>(9): error C2607: static assertion failed
Godbolt
Is GCC wrong here? I expected refable<void>
to evaluate to false since it forms an invalid type(void&
) in the immediate context.
static_assert(refable<void>);
As per [temp.names]/8, refable<void>
is a concept-id:
A concept-id is a simple-template-id where the template-name is a concept-name. A concept-id is a prvalue of type
bool
, and does not name a template specialization. A concept-id evaluates totrue
if the concept's normalized constraint-expression is satisfied ([temp.constr.constr]) by the specified template arguments andfalse
otherwise.
The concept's normalization is thus not dependent on the template arguments in a given concept-id that names the concept, but something that is performed independently; however the use of a concept-id may be the trigger for performing normalization of a concept, [temp.constr.normal]/2:
[Note 1: Normalization of constraint-expressions is performed when determining the associated constraints ([temp.constr.constr]) of a declaration and when evaluating the value of an id-expression that names a concept specialization ([expr.prim.id]). — end note]
Constraint normalization is covered by [temp.constr.normal]/1, where particularly /1.4 is governing the OP's example:
The normal form of an expression E is a constraint that is defined as follows:
[...]
/1.4 The normal form of a concept-id
C<A1, A2, ..., An>
is the normal form of the constraint-expression ofC
, after substitutingA1, A2, ..., An
forC
's respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.[Example 1:
template<typename T> concept A = T::value || true; template<typename U> concept B = A<U*>; template<typename V> concept C = B<V&>;
Normalization of
B
's constraint-expression is valid and results inT::value
(with the mappingT↦U*
)∨ true
(with an empty mapping), despite the expressionT::value
being ill-formed for a pointer typeT
. Normalization ofC
's constraint-expression results in the program being ill-formed, because it would form the invalid typeV&*
in the parameter mapping. — end example]
The key question here is whether OP's example runs into the
substituting [...] in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.
or not.
The normalization of refable
's constraint-expression, however, results in true
(with an empty mapping), with emphasis on the empty mapping in this normalized constraint. Thus, the normal form of OP's refable<void>
concept-id, would be substituting void
into the parameter mapping of the normalized form of refable
, but this only contains an empty parameter mapping. Thus the program is not ill-formed, NDR under [temp.constr.normal]/1.4, and the parameter mapping will never fail as there is nothing to substitute in the single empty mapping.
The question whether
refable<void>
results in a satisfied (true
) constraint or not (false
) comes down to how to interpret [temp.constr.atomic]/3:
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. [...]
This one is a bit tricky to read unambiguously (particularly the emphasized part), but as the parameter mapping is empty, there can be no substitution failure regarding the parameter mapping, and I interpret the emphasized part as substitution of template arguments into the normalized constraint-expression, which contains no template parameters, meaning there is no substitution failure. This would argue for GCC actually being correct to accept the program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With