C++ Logo


Advanced search

Re: std::equality_comparable_with: relaxing the common reference requirements

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Sat, 12 Jun 2021 14:37:00 -0500
Hey Justin,

I find this pretty compelling. So in short, equality_comparable_with<T, U>
(among other things) both requires that a common reference exists (I'll
call this type CR) but also that T and U are both convertible to CR, and
what you're proposing here is to keep all the requirements the same, still
require that CR exists as a type, but instead only require that T and U are
both convertible to CR const&?

To me, that seems like it still completely satisfies the design intent of
the model (we still have semantic meaning for the quality between T and U)
while simply allowing non-copyable types to work. And unique_ptr<T> ==
nullptr_t does very much seem semantically sound to me. But I am not
somebody that has ever really understood common reference, so I'm CCing the
three people that do.


On Fri, Jun 11, 2021 at 12:58 AM Justin Bassett via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Hello,
> *Summary:* std::equality_comparable_with should be extended to support
> more types that have a heterogeneous equality, specifically types which are
> move-only or are otherwise burned by one particular aspect of the common
> reference requirements.
> *Background: *std::equality_comparable_with<T, U> checks that const T&
> and const U& are both convertible to their std::equality_comparable common
> reference. This requirement is because we want
> std::equality_comparable_with to specify equality rather than any number of
> things that are not equality (e.g. iterator/sentinel "equality", DSLs).
> However, this requirement is too strict, and I believe we can do better.
> https://stackoverflow.com/q/66937947/1896169 is relevant and why I've
> been thinking about this; it shows that
> std::equality_comparable_with<std::unique_ptr<T>, std::nullptr_t> is false,
> even though the heterogenous operator== is in fact an actual equality.
> Why we have the common-reference requirement: cross-type equality isn't
> mathematically rigorously defined (see https://wg21.link/n3351 pages
> 15-16). When we write operator==(T, U) as an equality operator, what we are
> actually saying is that there is some common super-type of T and U under
> which this operator== is an equality. This is what
> std::equality_comparable_with attempts to encode. We don't actually need to
> convert to this common supertype to do t == u; we just need the
> heterogeneous operator== to be equivalent to the supertype's operator==.
> *Proposal: *I believe the common *reference *requirement is too strict.
> Mathematically, a "reference" is meaningless. All we actually need is a
> common "supertype." This is important, because sometimes
> std::common_reference<const T&, const U&> is a non-reference V, such as
> with the unique_ptr<T>-nullptr_t case, where it is unique_ptr<T>. By
> specifying equality_comparable_with to require a common *reference*, we
> end up requiring that unique_ptr<T> is copyable (const unique_ptr<T>& must
> be convertible to unique_ptr<T>). In this case and many other similar cases
> with a heterogeneous operator== that means equality, we can capture this
> equality with equality_comparable_with if change the convertible_to<const
> T&, common_reference_t<const T&, const U&>> to allow const T& to be the
> same type as common_reference_t<const T&, const U&> after stripping cvref.
> This does mean that it will become very difficult to express an equals()
> function in terms of the supertype operator==, but the point of the
> supertype operator== requirement is that it exists, not that it will be
> used.
> In the code form that I believe captures this (quoting my answer to that
> stackoverflow question):
>> template <class T, class U>concept equality_comparable_with =
>> __WeaklyEqualityComparableWith<T, U> &&
>> std::equality_comparable<T> &&
>> std::equality_comparable<U> &&
>> std::equality_comparable<
>> std::common_reference_t<
>> const std::remove_reference_t<T>&,
>> const std::remove_reference_t<U>&>> &&
>> __CommonSupertypeWith< // not necessarily a reference anymore
>> const std::remove_reference_t<T>&,
>> const std::remove_reference_t<U>&>;
>> template <class T, class U>concept __CommonSupertypeWith =
>> std::same_as<std::common_reference_t<T, U>, std::common_reference_t<U, T>> &&
>> std::convertible_to<T, const std::common_reference_t<T, U>&> &&
>> std::convertible_to<U, const std::common_reference_t<T, U>&>;
>> *"Diff":*
> Changes this: std::common_reference_with<const remove_reference_t<T>&,
> const remove_reference_t<U>&>
> To this: __CommonSupertypeWith<const remove_reference_t<T>&, const
> remove_reference_t<U>&>
> Which __CommonSupertypeWith is the same as common_reference_with, except
> we specify the convertibility to the common_reference_t as convertibility
> to const (common-reference)& instead of (common-reference).
> *Other thoughts:*
> I have other thoughts regarding equality_comparable_with, namely that I
> think it should be possible to opt-in to equality_comparable_with without
> hijacking common_reference_t for your type, which is problematic because
> common_reference_t is not exclusive to equality_comparable_with. That is, I
> think there should be an explicit customization point to declare "MyType's
> heterogeneous operator== is actually equality and isn't just using the '=='
> syntax." However, I don't feel as strongly about this.
> Thanks,
> Justin Bassett
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2021-06-12 14:37:16