Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC disagrees with Clang and MSVC when concept that's always true is used to implement a concept

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.

like image 712
whatishappened Avatar asked Nov 03 '21 10:11

whatishappened


1 Answers

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 to true if the concept's normalized constraint-expression is satisfied ([temp.constr.constr]) by the specified template arguments and false 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 of C, after substituting A1, A2, ..., An for C'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 in T​::​value (with the mapping T↦U*) ∨ true (with an empty mapping), despite the expression T​::​value being ill-formed for a pointer type T. Normalization of C's constraint-expression results in the program being ill-formed, because it would form the invalid type V&* 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.

like image 82
dfrib Avatar answered Oct 19 '22 23:10

dfrib