Why do we require requires requires?

One of the corners of C++20 concepts is that there are certain situations in which you have to write requires requires. For instance, this example from [expr.prim.req]/3:

A requires-expression can also be used in a requires-clause ([temp]) as a way of writing ad hoc constraints on template arguments such as the one below:

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

The first requires introduces the requires-clause, and the second introduces the requires-expression.

What is the technical reason behind needing that second requires keyword? Why can't we just allow writing:

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(Note: please don't answer that the grammar requires it)


Solution 1:

It is because the grammar requires it. It does.

A requires constraint does not have to use a requires expression. It can use any more-or-less arbitrary boolean constant expression. Therefore, requires (foo) must be a legitimate requires constraint.

A requires expression (that thing that tests whether certain things follow certain constraints) is a distinct construct; it's just introduced by the same keyword. requires (foo f) would be the beginning of a valid requires expression.

What you want is that if you use requires in a place that accepts constraints, you should be able to make a "constraint+expression" out of the requires clause.

So here's the question: if you put requires (foo) into a place that is appropriate for a requires constraint... how far does the parser have to go before it can realize that this is a requires constraint rather than a constraint+expression the way you want it to be?

Consider this:

void bar() requires (foo)
{
  //stuff
}

If foo is a type, then (foo) is a parameter list of a requires expression, and everything in the {} is not the body of the function but the body of that requires expression. Otherwise, foo is an expression in a requires clause.

Well, you could say that the compiler should just figure out what foo is first. But C++ really doesn't like it when the basic act of parsing a sequence of tokens requires that the compiler figure out what those identifiers mean before it can make sense of the tokens. Yes, C++ is context-sensitive, so this does happen. But the committee prefers to avoid it where possible.

So yes, it's grammar.

Solution 2:

The situation is exactly analogous to noexcept(noexcept(...)). Sure, this sounds more like a bad thing than a good thing, but let me explain. :) We'll start with what you already know:

C++11 has "noexcept-clauses" and "noexcept-expressions." They do different things.

  • A noexcept-clause says, "This function should be noexcept when... (some condition)." It goes on a function declaration, takes a boolean parameter, and causes a behavioral change in the declared function.

  • A noexcept-expression says, "Compiler, please tell me whether (some expression) is noexcept." It is itself a boolean expression. It has no "side effects" on the behavior of the program — it's just asking the compiler for the answer to a yes/no question. "Is this expression noexcept?"

We can nest a noexcept-expression inside a noexcept-clause, but we typically consider it bad style to do so.

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

It's considered better style to encapsulate the noexcept-expression in a type-trait.

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

The C++2a Working Draft has "requires-clauses" and "requires-expressions." They do different things.

  • A requires-clause says, "This function should participate in overload resolution when... (some condition)." It goes on a function declaration, takes a boolean parameter, and causes a behavioral change in the declared function.

  • A requires-expression says, "Compiler, please tell me whether (some set of expressions) is well-formed." It is itself a boolean expression. It has no "side effects" on the behavior of the program — it's just asking the compiler for the answer to a yes/no question. "Is this expression well-formed?"

We can nest a requires-expression inside a requires-clause, but we typically consider it bad style to do so.

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

It's considered better style to encapsulate the requires-expression in a type-trait...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

...or in a (C++2a Working Draft) concept.

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2