Date: Tue, 15 Jun 2021 00:18:00 -0700
Hi Barry,
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&
>
That is an excellent summary of what I'm proposing. I suppose the only
thing I would add explicitly is, "CR const& is to prevent 'convertible to
CR' from requiring copyability/movability in the case of T = CR".
Thanks,
Justin
On Sat, Jun 12, 2021 at 12:37 PM Barry Revzin <barry.revzin_at_[hidden]>
wrote:
> 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.
>
> Barry
>
> 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
>>
>
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&
>
That is an excellent summary of what I'm proposing. I suppose the only
thing I would add explicitly is, "CR const& is to prevent 'convertible to
CR' from requiring copyability/movability in the case of T = CR".
Thanks,
Justin
On Sat, Jun 12, 2021 at 12:37 PM Barry Revzin <barry.revzin_at_[hidden]>
wrote:
> 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.
>
> Barry
>
> 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-15 02:18:19