当使用始终为真的概念来实现概念时,GCC 不同意 Clang 和 MSVC

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

以下代码无法使用 Clang 13 和 MSVC v19.29 VS16.11 进行编译,但可以使用 GCC 11.2 成功编译。

template <typename...>
concept always_true = true;

template <typename T>
concept refable = always_true<T&>;

static_assert(refable<void>);

叮当声 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

这里GCC错了吗?我希望 refable<void> 评估为 false,因为它在直接上下文中形成无效类型 (void&)。

static_assert(refable<void>);

根据 [temp.names]/8refable<void> 是一个 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.

因此,概念的规范化不依赖于命名概念的给定 concept-id 中的模板参数,而是独立执行的;然而,使用 concept-id 可能会触发 performing 概念规范化,[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]

[temp.constr.normal]/1 涵盖约束规范化,其中特别是 /1.4 管理 OP 的示例:

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]

这里的关键问题是OP的例子是否遇到了

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.

与否。

然而,refable 的约束表达式的规范化导致 true(具有空映射),重点是此规范化约束中的空映射。因此,OP 的 refable<void> concept-id 的正常形式会将 void 代入 refable 的规范化形式的参数映射中,但是这只包含一个空参数映射。因此程序不是病式的,NDR 在 [temp.constr.normal]/1.4 下,参数映射永远不会失败,因为在单个空映射中没有什么可以替代。

是否

的问题
refable<void>

结果是否满足 (true) 约束 (false) 归结为如何解释 [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. [...]

这个读起来有点费劲(尤其是强调的部分),但是由于参数映射是空的,所以参数映射不会有替换失败,我把强调的部分理解为模板替换规范化约束表达式中的参数,它不包含模板参数,这意味着没有替换失败。这将证明 GCC 实际上接受该程序是正确的。