c++c++20requires-clause

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


This gives an error:

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

         ~~^~~

         (    )

Almost all operators give this error (requires A > B, requires A == B, requires A & B, requires !A)

However && and || seem to work:

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

Testes with gcc trunk and clang trunk (on May 2020) on godbolt. Both compilers give the same results.


Solution

  • Yes, && and || are treated special here because constraints are aware of conjunctions and disjunctions.

    § 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).

    They need to be in order to define a partial ordering by constraints.

    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]

    Which makes this code work:

    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
    
    

    But for call #B even if both sorts are viable as both constraints (randomaccess_iterator and bidirectional_iterator are satisfied) the sort #2 (the one with the randomaccess_iterator) is correctly chosen because it is more constrained than sort #1 (the one with bidirectional_iterator) because randomaccess_iterator subsumes bidirectional_iterator:

    See How is the best constrained function template selected with concepts?