24

The concept equality_­comparable_with<T, U> is intended to declare that objects of type T and U can be compared equal to each other, and if they are, then this has the expected meaning. That's fine.

However, this concept also requires common_reference_t<T&, U&> to exist. The primary impetus for common_reference and its attendant functionality seems to be to enable proxy iterators, to have a place to represent the relationship between reference and value_type for such iterators.

That's great, but... what does that have to do with testing if a T and a U can be compared equal to one another? Why does the standard require that T and U have a common reference relationship just to allow you to compare them equal?

This creates oddball situations where it's very difficult to have two types which don't reasonably have a common-reference relationship that are logically comparable. For example, vector<int> and pmr::vector<int> logically ought to be comparable. But they can't be because there's no reasonable common-reference between the two otherwise unrelated types.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

1 Answers1

7

This goes all the way back to the Palo Alto report, §3.3 and D.2.

For the cross-type concepts to be mathematically sound, you need to define what the cross-type comparison means. For equality_comparable_with, t == u generally means that t and u are equal, but what does it even mean for two values of different types to be equal? The design says that cross-type equality is defined by mapping them to the common (reference) type (this conversion is required to preserve the value).

Where the strong axioms of equality_comparable_with is not desirable, the standard uses the exposition-only concept weakly-equality-comparable-with, and that concept doesn't require a common reference. It is, however, a "semantic abomination" (in the words of Casey Carter) and is exposition-only for that reason: it allows having t == u and t2 == u but t != t2 (this is actually necessary for sentinels).

Justin
  • 24,288
  • 12
  • 92
  • 142
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 6
    That's interesting, but that doesn't explain why the "mathematically sound" solution involves this "common reference" type instead of... well, *anything else*. That is, why does it involve doing this implicit reference conversion business instead of something that would allow for logically mathematically sound concepts that cannot permit conversions (like `container` and `container` being comparable if `T` is comparable to `U`)? Is it just that `common_reference` was available, or is there more to it than that? – Nicol Bolas Apr 13 '20 at 06:00
  • It lacks the idea that the conversion function to the common_reference type is an epimorphism. – Oliv Apr 15 '20 at 13:03
  • Does `equality_comparable_with` without the common-reference requirements actually allow `t == u` and `t2 == u` but `t != t2`, or does it just mean that `t == t2` doesn't compile? – Justin Apr 04 '21 at 06:38
  • @Justin The part that disallows `t != t2` [is the part that uses the common reference](https://timsong-cpp.github.io/cppwp/concept.equalitycomparable#4.sentence-1). – T.C. Apr 04 '21 at 13:20
  • @T.C. I see; I misread "`t == u` and `t2 == u` but `t != t2`" as the transitive property, but now I understand what you mean. I can see that iterator+sentinel equality is not really equality, but it is instead testing membership of an equivalence class ( `{x : x is not at the end}` and `{x : x is at the end}` ). I suppose it would be like working with integers, then saying `0 == 2 mod 2`, which is true when we re-type as Z_2, but it's not actually an `==` for Z. – Justin Apr 04 '21 at 17:09