How to implement the generalized form of std::same_as (i.e. for more than two type parameters) that is agnostic to parameter order?
From cppreference.com Constraint_normalization
The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity mapping. This includes all fold expressions, even those folding over the && or || operators.
So
template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);
is "atomic".
So indeed are_same<U, T>
and are_same<T, U>
are not equivalent.
I don't see how to implement it :-(
The problem is, with this concept:
template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);
Is that the normalized form of this concept is... exactly that. We can't "unfold" this (there's nothing to do), and the current rules don't normalize through "parts" of a concept.
In other words, what you need for this to work is for your concept to normalize into:
... && (same-as-impl<T, U> && same-as-impl<U, T>)
into:
... && (is_same_v<T, U> && is_same_v<U, T>)
And consider one fold-expression &&
constraint to subsume another fold-expression constraint &&
if its underlying constraint subsumes the other's underlying constraint. If we had that rule, that would make your example work.
It may be possible to add this in the future - but the concern around the subsumption rules is that we do not want to require compilers to go all out and implement a full SAT solver to check constraint subsumption. This one doesn't seem like it makes it that much more complicated (we'd really just add the &&
and ||
rules through fold-expressions), but I really have no idea.
Note however that even if we had this kind of fold-expression subsumption, are_same<T, U>
would still not subsume std::same_as<T, U>
. It would only subsume are_same<U, T>
. I am not sure if this would even be possible.