[concept.same] was changed as part of LWG issue 3182 (before the concept Same
was renamed to is_same
as per P1754R1) [emphasis mine]:
3182. Specification of Same could be clearer
- Section: 18.4.2 [concept.same]
- Status: WP
- [...]
Discussion:
The specification of the Same concept in 18.4.2 [concept.same]:
template<class T, class U>
concept Same = is_same_v<T, U>;
Same<T, U>
subsumes Same<U, T>
and vice versa.
seems contradictory. From the concept definition alone, it is not the
case that Same<T, U>
subsumes Same<U, T>
nor vice versa. Paragraph
1 is trying to tell us that there's some magic that provides the
stated subsumption relationship, but to a casual reader it appears
to be a mis-annotated note. We should either add a note to explain
what's actually happening here, or define the concept in such a way
that it naturally provides the specified subsumption relationship.
Given that there's a straightforward library implementation of the symmetric subsumption idiom, the latter option seems preferable.
[...]
Proposed resolution:
This wording is relative to N4791.
Change 18.4.2 [concept.same] as follows:
template<class T, class U>
concept same-impl = // exposition only
is_same_v<T, U>;
template<class T, class U>
concept Same = is_same_v<T, U>same-impl<T, U> && same-impl<U, T>;
- [Note:
Same<T, U>
subsumes Same<U, T>
and vice versa. — end note]
I will start addressing the second question of the OP (as the answer to the first question will follow from it):
OP: The second is why same_as
checks if T
is the same as U
and U
the same as T
? Isn't it redundant?
As per the last part emphasized above:
[...] Given that there's a straightforward library implementation of the symmetric subsumption idiom, the latter option seems preferable.
the resolution to CWG 3182 was to redefine the library spec to use two symmetric constraints specifically to fulfill the subsumption relationsship between the two ("the symmetric subsumption idiom", if you will) in a (semantically) natural way.
As a tangent (but relevant to answer OP's first question), this can be important for partial ordering by constraints, as per [temp.constr.order], particularly [temp.constr.order]/1 and [temp.constr.order]/3
/1 A constraint P
subsumes a constraint Q
if and only if, [...] [ Example: Let A and B be atomic constraints. The constraint A ∧ B
subsumes A
, but A
does not subsume A ∧ B
. The constraint A
subsumes A ∨ B
, but A ∨ B
does not subsume A
. Also note that every constraint subsumes itself. — end example ]
/3 A declaration D1
is at least as constrained as a declaration D2
if
- (3.1)
D1
and D2
are both constrained declarations and D1
's associated constraints subsume those of D2
; or
- (3.2) D2 has no associated constraints.
Such that in the following example:
#include <iostream>
template <typename T> concept C1 = true;
template <typename T> concept C2 = true;
template <typename T> requires C1<T> && C2<T> // #1
void f() { std::cout << "C1 && C2"; }
template <typename T> requires C1<T> // #2
void f() { std::cout << "C1"; }
a call to, say, f<int>()
, is not ambiguous (#1
will be called) as the constraints at #1
, C1<T> && C2<T>
, subsumes the constraint at #2
, C1<T>
, but not vice versa.
We could, however, go down the rabbit hole of [temp.constr.order] and [temp.constr.atomic] to show that even in the older implementation of same_as
:
// old impl.; was named Same back then
template<typename T, typename U>
concept same_as = is_same_v<T, U>;
same_as<T, U>
would still subsume same_as<U, T>
and vice versa; this is not entirely trivial, however.
Thus, instead of choosing the option of "add a note to explain what's actually happening here" to resolve LWG 3182, [concept.same] instead changed the library implementation to be defined in a form that had a clearer semantic meaning to the "casual reader":
// A and B are concepts
concept same_as = A ^ B
As per the (tangential) part above, we may also note that same_as
subsumes both the concepts A
and B
in isolation, whereas A
and B
in isolation does not subsume same_as
.
OP: The first question is why a SameHelper
concept is nedded?
As per temp.constr.order]/1, only concepts can be subsumed. Thus, for the older implementation of the concept, where the is_same
transformation trait (which is not a concept) was used directly, the trait itself did not fall under the subsumption rules. Meaning an implementation as follows:
template< class T, class U >
concept same_as = std::is_same_v<T, U> && std::is_same_v<U, T>
would truly contain a redundant r.h.s. for &&
, as type traits cannot subsume type traits. When LWG 3182 was resolved, and an intention was to semantically show the subsumption relationship as per above, an intermediate concept was added to place emphasis on subsumption.
SameHelper<T, U>
might be true doesn't meanSameHelper<U, T>
might be. – Eousis_same<T, U>::value == true
if and only ifis_same<U, T>::value == true
." This implies that this double check isn't necessary – Clutch