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.