什么时候需要用括号括起 requires 子句表达式? (一语双关)

When is a requires clause expression required to be parenthesised? (pun by accident)

这给出了一个错误:

template <class T, T A, T B>
    requires A > B             // <-- error
class X{};

error: parentheses are required around this expression in a requires clause

requires A < B

         ~~^~~

         (    )

几乎所有的运算符都会报这个错误(requires A > B, requires A == B, requires A & B, requires !A)

但是 &&|| 似乎有效:

template <class T, T A, T B>
    requires A && B             // <-- ok
class X{};

在 godbolt 上使用 gcc 主干和 clang 主干(2020 年 5 月)进行测试。两个编译器给出相同的结果。

是的,&&|| 在这里被特殊对待,因为约束知道合取和析取。

§ 13.5.1 Constraints [temp.constr.constr]

  1. A constraint is a sequence of logical operations and operands that specifies requirements on template arguments. The operands of a logical operation are constraints. There are three different kinds of constraints:

    (1.1) — conjunctions (13.5.1.1),
    (1.2) — disjunctions (13.5.1.1), and
    (1.3) — atomic constraints (13.5.1.2).

它们需要是为了通过约束定义部分排序。

13.5.4 Partial ordering by constraints [temp.constr.order]

  1. [Note: [...] This partial ordering is used to determine

    • (2.1) the best viable candidate of non-template functions (12.4.3),
    • (2.2) the address of a non-template function (12.5),
    • (2.3) the matching of template template arguments (13.4.3),
    • (2.4) the partial ordering of class template specializations (13.7.5.2), and
    • (2.5) the partial ordering of function templates (13.7.6.2).

— end note]

使这段代码起作用的原因:

template <class It>
concept bidirectional_iterator = requires /*...*/;

template <class It>
concept randomaccess_iterator = bidirectional_iterator<It> && requires /*...*/;

template <bidirectional_iterator It>
void sort(It begin, It end);            // #1

template <randomaccess_iterator It>
void sort(It begin, It end);            // #2
std::list<int> l{};
sort(l.begin(), l.end()); // #A  -> calls #1

std::vector<int> v{};
sort(v.begin(), v.end()); // #B  -> calls #2

但是对于调用 #B,即使两个 sort 都可行,因为两个约束(randomaccess_iteratorbidirectional_iterator 都满足),sort #2(带有 randomaccess_iterator) 的一个被正确选择,因为它 sort #1(带有 bidirectional_iterator 的那个) 更受约束 因为 randomaccess_iterator 包含bidirectional_iterator:

Requires 子句使用特殊的表达式语法来避免certain pathological parsing ambiguities. As noted on cppreference,允许的形式是

  • 一个primary expression
  • 用运算符 &&
  • 连接的一系列主表达式
  • 一系列上述表达式与运算符 ||
  • 连接