Let's design Cpp2's _requires-expression_ as a pit of success! #588
Replies: 4 comments 30 replies
-
So if I understand correctly, you are saying that a requires expression should not be inside another requires expression, right? Because, requires clauses inside requires expressions are useful. Also, I think cpp20 already disallows that. Also, maybe we could have different keywords for requires expressions and requires clauses because they are confusing currently. |
Beta Was this translation helpful? Give feedback.
-
I'll give some code examples template<class T>
concept HasAButNotB = requires (T t) {
t.a();
!requires { t.b(); };
}; The following example shows why I think "it" is banned in cpp20, this will error out template<class T>
concept HasAButNotB = requires (T t) {
t.a();
requires { t.b(); };
}; The following is the correct way to express our requirement template<class T>
concept HasAButNotB = requires (T t) {
t.a();
requires !requires { t.b(); };
}; Also, I think you're right about banning requires inside requires upto some extent. requires expressions should not be allowed (probably aren't in cpp20 but have to check that) but requires clauses are still useful as demonstrated by above example. This also supports my point about having high distinction between requires clauses and requires expressions maybe even upto having different keywords for them. And cppfront definitely needs to simplify this design space largely so users don't fall into pitfalls like above. |
Beta Was this translation helpful? Give feedback.
-
Here's the CppCon talk's pitfalls for requires-expressions.
|
Beta Was this translation helpful? Give feedback.
-
Here's my design, based on "no gratuitous difference from Cpp1". // requires-expression:
// 'requires' parameter-declaration-list? requirement-body
//
// requirement:
// type-id ';' // Cpp1 type-requirement
// expression ';' // Cpp1 simple-requirement
// compound-requirement
// nested-requirement Semantic rule: A (possibly negated) nested requires-expression may only appear as a term of a nested-requirement. // compound-requirement:
// { expression } 'throws'? is-type-constraint? ';' Maybe not really Semantic rule: Not on compound-requirement, but on a expression requirement.
// nested-requirement:
// '!'? 'requires' logical-or-expression ';' When If proposing these to Cpp1, |
Beta Was this translation helpful? Give feedback.
-
Let's design Cpp2's requires-expression as a pit of success!
As it turns out, with Cpp1's requires-expression, slight changes in a requirement's syntax can still result in a valid requirement.
This results in seemingly working
concept
declarations that actually have unintended effects.For examples of pitfalls, I suggest you (re)read the article https://quuxplusone.github.io/blog/2021/06/09/another-concepts-chest-mimic/ and (re)watch the linked CppCon talk.
With Cpp2, we have the opportunity to design a requires-expression for Cpp2 that is a pitfall of success.
Please, have one top-level comment per (re)design.
Feel free to have top-level comments about other concerns.
A few comments
Grammars
The sky is the limit; the grammar is free real state (as Herb's willing to hand it out).
Do not think that Cpp2's requires-expression has to be a single top-level grammar.
For example, each requirement could be usable on its own, without being within a requires-expression
(so it could be an alternative of grammars, rather than the requirement-body being a sequence of requirements).
Integration
Consider the greater picture of Cpp2; there might be tradeoffs to be had.
For example, in an is-as-expression, where the rhs argument can be a type or expression,
you can disambiguate an expression from a type by adding parentheses around it
(Herb has stated his intention to do the same for template arguments at #531 (comment)).
If we were to translate Cpp1's requirements as-is, we could use the same formulation.
For example, instead of translating Cpp1 requirements
𝘌; typename 𝘛;
to Cpp2's terms
they could be translated to
Pitfalls
Squint at what the sources of the pitfalls of Cpp1's requires-expression might be.
Besides slight changes in the syntax resulting in another valid meaning, there are also meaningless combinations.
Let's consider the first source of pitfalls, slight changes in syntax.
If we were to translate Cpp1's compound-requirement as-is, we might end up with Cpp1
being the equivalent of Cpp2's
Unfortunately, this still runs into the pitfall that a user might instead write:
That is a simple-requirement, if we similarly translated that from Cpp1.
It changes the meaning; currently, an
is
operation always valid (or bugged) (which I argue against with #492).Herb has also stated his intention to support terse concept syntax like
<T: type is std::regular>
at #575 (comment)(I'm supposing we will be able to omit
type
, and similarly declare NTTPs like<V: _ is std::regular>
).So this also ties back to integration.
The other source of pitfalls is meaningless combinations.
Because a requires-expression can test arbitrary expression, it can also test requires-expressions.
It's a meaningless combination because the SFINAE-level validity of all constructs is already being checked within a requires-expression.
So adding a nested
requires
doesn't add value, and not rejecting that is a source of pitfalls.This source is enhanced by the fact that a nested-requirement starts with
requires
in Cpp1.Beta Was this translation helpful? Give feedback.
All reactions