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)
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.
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
I think cppreference's concepts page explains this. I can explain with "math" so to say, why this must be like this:
If you want to define a concept, you do this:
template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression
If you want to declare a function that uses that concept, you do this:
template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }
Now if you don't want to define the concept separately, I guess all you have to do is some substitution. Take this part requires (T x) { x + x; };
and replace the Addable<T>
part, and you'll get:
template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }
which is what you're asking about.
I found a comment from Andrew Sutton (one of the Concepts authors, who implemented it in gcc) to be quite helpful in this regard, so I thought I'd just quote it here in its near-entirety:
Not so long ago requires-expressions (the phrase introduced by the second requires) was not allowed in constraint-expressions (the phrase introduced by the first requires). It could only appear in concept definitions. In fact, this is exactly what is proposed in the section of that paper where that claim appears.
However, in 2016, there was a proposal to relax that restriction [Editor's note: P0266]. Note the strikethrough of paragraph 4 in section 4 of the paper. And thus was born requires requires.
To tell the truth, I had never actually implemented that restriction in GCC, so it had always been possible. I think that Walter may have discovered that and found it useful, leading to that paper.
Lest anybody think that I wasn't sensitive to writing requires twice, I did spend some time trying to determine if that could be simplified. Short answer: no.
The problem is that there are two grammatical constructs that need to introduced after a template parameter list: very commonly a constraint expression (like
P && Q
) and occasionally syntactic requirements (likerequires (T a) { ... }
). That's called a requires-expression.The first requires introduces the constraint. The second requires introduces the requires-expression. That's just the way the grammar composes. I don't find it confusing at all.
I tried, at one point, to collapse these to a single requires. Unfortunately, that leads to some seriously difficult parsing problems. You can't easily tell, for example if a
(
after the requires denotes a nested subexpression or a parameter-list. I don't believe that there is a perfect disambiguation of those syntaxes (see the rationale for uniform initialization syntax; this problem is there too).So you make a choice: make requires introduce an expression (as it does now) or make it introduce a parameterized list of requirements.
I chose the current approach because most of the time (as in nearly 100% of the time), I want something other than a requires-expression. And in the exceedingly rare case I did want a requires-expression for ad hoc constraints, I really don't mind writing the word twice. It's a an obvious indicator that I haven't developed a sufficiently sound abstraction for the template. (Because if I had, it would have a name.)
I could have chosen to make the requires introduce a requires-expression. That's actually worse, because practically all of your constraints would start to look like this:
template<typename T> requires { requires Eq<T>; } void f(T a, T b);
Here, the 2nd requires is called a nested-requirement; it evaluates its expression (other code in the block of the requires-expression is not evaluated). I think this is way worse than the status quo. Now, you get to write requires twice everywhere.
I could also have used more keywords. This is a problem in its own right---and it's not just bike shedding. There might be a way to "redistribute" keywords to avoid the duplication, but I haven't given that serious thought. But that doesn't really change the essence of the problem.
Because you are saying that a thing A has a requirement B, and the requirement B has a requirement C.
The thing A requires B which in turn requires C.
The "requires" clause itself requires something.
You have thing A (requiring B (requiring C)).
Meh. :)
来源:https://stackoverflow.com/questions/54200988/why-do-we-require-requires-requires