The last time I used C++ concepts with GCC and the fconcepts flag the following snippet used to work
template <typename T, typename U>
concept equality_comparable = requires(T a, U b) {
{ a == b } -> bool;
{ a != b } -> bool;
};
Apparently this is no longer the case and a return-type-requirement after a compound requirement can now only contain type constraints. If I'm not mistaken this basically means using another concept to satisfy the return-type-requirement.
So the perfectly readable and (for C++ standards) short snippet becomes
template <typename From, typename To>
concept convertible_to = std::is_convertible_v<From, To>;
template <typename T, typename U>
concept equality_comparable = requires(T a, U b) {
{ a == b } -> convertible_to<bool>;
{ a != b } -> convertible_to<bool>;
};
Of course this isn't even a full implementation, but let's ignore that for now. Could someone maybe explain to me why the committee decided to change that? Personally I find that "implicitly used template parameter" in the convertible_to concept extremely irritating and confusing.
Well, what does this actually mean:
Does it mean
a == b
must have type exactlybool
, or does it mean if you decay the type you getbool
(i.e.const bool
orbool&
are ok), or does it mean convertible tobool
(i.e.std::true_type
is ok)? I don't think it's at all clear from the syntax - and any one of these three could be meaningfully desired by a particular concept (as P1452 points out, at the time, the ratio ofSame<T>
toConvertibleTo<T>
in concepts was 40-14).The paper also goes on to point out that in the Concepts TS, where
-> Type
existed, we also had the ability to write something likevector<Concept>
... or-> vector<Concept>
as a requirement. That's a type, but would behave very difficultly with thedecltype(())
semantics we adopted in P1084.Basically I don't think the "perfectly readable" snippet actually is - there are multiple potential meanings for that syntax, all of which can be the desired meaning depending on context. And the most commonly used one at the time (
same_as<bool>
) isn't even the one we want here (convertible_to<bool>
).It's novel in C++, but I personally find it reads quite nicely in these cases. Seeing:
Just reads exactly as the requirement:
a == b
needs to be a valid expression that's convertible tobool
. For unary concepts, it makes the usage quite nice since you can use them in place of the somewhat meaninglesstypename
/class
keyword:Which isn't that different from other languages. Like, in Rust for instance:
There the "implicitly used template parameter" is so implicit that it's not even part of the trait declaration, it's implicit there too:
So even with a longer-form syntax, you'd write
where I: Iterator
whereas in C++ you'd still writerequires range<R>
.This isn't strictly related to the original question, but I just find it interesting to add for some other color.