On Mon, Jan 19, 2026 at 11:53 AM Jonathan Wakely <[email protected]> wrote:

> On Mon, 19 Jan 2026 at 08:44, Tomasz Kamiński <[email protected]> wrote:
> >
> > The implementation of less<> did not consider the possibility of t < u
> being
> > rewritten from overloaded operator<=>. This lead to situation when for
> t,u that:
> > * provide overload operator<=>, such that (t < u) is rewritten to (t <=>
> u) < 0,
> > * are convertible to pointers,
> > the expression std::less<>(t, u) would incorrectly result in call of
> > std::less<void*> on values converted to the pointers, instead of t < u.
> > The similar issues also occurred for greater<>, less_equal<>,
> greater_equal<>,
> > their range equivalents, and in three_way_compare for hat erogenous
> calls.
>
> I'm not sure what "hat erogenous" was meant to say :-)
>
> >
> > This patch addresses above, by also checking for free-functions and
> member
> > overloads of operator<=>, before fall backing to pointer comparison. We
> do
>
> "falling back"
>
> > not put any contains on the return type of selected operator, in
> particular
>
> "contains" -> "constraints"
>
> > in being one of the standard defined comparison categories, as the
> language
> > does not put any restriction of returned type, and if (t <=> u) is well
> > formed, (t op u) is interpreted as (t <=> u) op 0. If that later
> expression
> > is ill-formed, the expression using op also is (see included tests).
> >
> > The relational operator rewrites try both order of arguments, t < u,
> > can be rewritten into operator<=>(t, u) < 0 or 0 < operator<=>(u, t), it
> > means that we need to test both operator<=>(T, U) and operator<=>(U, T)
> > if T and U are not the same types. This is now extracted into
> > __not_overloaded_spaceship helper concept, placed in <concepts>, to
> > avoid extending set of includes.
> >
> > The compare_three_way functor defined in compare, already considers
> overloaded
> > operator<=>, however it does not consider reversed candidates, leading
> > to situation in which t <=> u results in 0 <=> operator<=>(u, t), while
> > compare_three_way{}(t, u) uses pointer comparison. This is also
> addressed by
> > using __not_overloaded_spaceship, that check both order of arguments.
>
> I would have missed checking the reversed args, and the unconventional
> return types from operator<=>.
>
> > Finally, as operator<=> is introduced in C++20, for std::less(_equal)?<>,
> > std::greater(_equal)?<>, we use provide separate __ptr_cmp implementation
> > in that mode, that relies on use of requires expression. We use a nested
> > requires clause to guarantee short-circuiting of their evaluation.
> > The operator() of aforementioned functors is reworked to use if
> constexpr,
> > in all standard modes (as we allow is as extension), eliminating the need
> > for _S_cmp function.
>
> A nice solution - thanks.
>
I based on your prototype in bugzilla:
https://gcc.gnu.org/bugzilla/attachment.cgi?id=57577,
with few modifications/improvements, so not surprised that you like it.

>
> OK for trunk with the commit message fixes mentioned above.
>
> > libstdc++-v3/ChangeLog:
> >
> >         * include/bits/ranges_cmp.h (__detail::__less_builtin_ptr_cmp):
> >         Add __not_overloaded_spaceship spaceship check.
> >         * include/bits/stl_function.h (greater<void>::operator())
> >         (less<void>::operator(), greater_equal<void>::operator())
> >         (less_equal<void>::operator()): Implement using if constexpr.
> >         (greater<void>::__S_cmp, less<void>::__S_cmp)
> >         (greater_equal<void>::__ptr_comp, less_equal<void>::S_cmp):
> >         Remove.
> >         (greater<void>::__ptr_cmp, less<void>::__ptr_cmp)
> >         (greater_equal<void>::__ptr_comp, less_equal<void>::ptr_cmp):
> Change
> >         tostatic constexpr variable. Define in terms of requires
> expressions
> >         and __not_overloaded_spaceship check.
> >         * include/std/concepts: (__detail::__not_overloaded_spaceship):
> >         Define.
> >         * libsupc++/compare: (__detail::__3way_builtin_ptr_cmp): Use
> >         __not_overloaded_spaceship concept.
> >         *
> testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc: New
> test.
> > ---
> > Tested on x86_64-linux. OK for trunk/15?
> > Should we backport it to eariel releases?
> >
> >  libstdc++-v3/include/bits/ranges_cmp.h        |   7 +-
> >  libstdc++-v3/include/bits/stl_function.h      | 183 ++++++----
> >  libstdc++-v3/include/std/concepts             |  16 +
> >  libstdc++-v3/libsupc++/compare                |   5 +-
> >  .../comparisons_pointer_spaceship.cc          | 336 ++++++++++++++++++
> >  5 files changed, 468 insertions(+), 79 deletions(-)
> >  create mode 100644
> libstdc++-v3/testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc
> >
> > diff --git a/libstdc++-v3/include/bits/ranges_cmp.h
> b/libstdc++-v3/include/bits/ranges_cmp.h
> > index c6451427beb..e2bf97ed4cd 100644
> > --- a/libstdc++-v3/include/bits/ranges_cmp.h
> > +++ b/libstdc++-v3/include/bits/ranges_cmp.h
> > @@ -71,10 +71,11 @@ namespace ranges
> >         = requires (_Tp&& __t, _Up&& __u) { { __t < __u } ->
> same_as<bool>; }
> >           && convertible_to<_Tp, const volatile void*>
> >           && convertible_to<_Up, const volatile void*>
> > -         && (! requires(_Tp&& __t, _Up&& __u)
> > +         && ! requires(_Tp&& __t, _Up&& __u)
> >               { operator<(std::forward<_Tp>(__t),
> std::forward<_Up>(__u)); }
> > -             && ! requires(_Tp&& __t, _Up&& __u)
> > -             {
> std::forward<_Tp>(__t).operator<(std::forward<_Up>(__u)); });
> > +         && ! requires(_Tp&& __t, _Up&& __u)
> > +             {
> std::forward<_Tp>(__t).operator<(std::forward<_Up>(__u)); }
> > +         && std::__detail::__not_overloaded_spaceship<_Tp, _Up>;
> >    } // namespace __detail
> >
> >    // [range.cmp] Concept-constrained comparisons
> > diff --git a/libstdc++-v3/include/bits/stl_function.h
> b/libstdc++-v3/include/bits/stl_function.h
> > index 659025c4bf9..0600de72b10 100644
> > --- a/libstdc++-v3/include/bits/stl_function.h
> > +++ b/libstdc++-v3/include/bits/stl_function.h
> > @@ -59,6 +59,9 @@
> >  #if __cplusplus > 201103L
> >  #include <bits/move.h>
> >  #endif
> > +#if __cplusplus >= 202002L
> > +#include <concepts>
> > +#endif
> >
> >  namespace std _GLIBCXX_VISIBILITY(default)
> >  {
> > @@ -525,8 +528,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         noexcept(noexcept(std::forward<_Tp>(__t) >
> std::forward<_Up>(__u)))
> >         -> decltype(std::forward<_Tp>(__t) > std::forward<_Up>(__u))
> >         {
> > -         return _S_cmp(std::forward<_Tp>(__t), std::forward<_Up>(__u),
> > -                       __ptr_cmp<_Tp, _Up>{});
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> > +         if constexpr (__ptr_cmp<_Tp, _Up>)
> > +           return greater<const volatile void*>{}(
> > +             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > +             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > +         else
> > +           return std::forward<_Tp>(__t) > std::forward<_Up>(__u);
> > +#pragma GCC diagnostic pop
> >         }
> >
> >        template<typename _Tp, typename _Up>
> > @@ -537,20 +547,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        typedef __is_transparent is_transparent;
> >
> >      private:
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr decltype(auto)
> > -       _S_cmp(_Tp&& __t, _Up&& __u, false_type)
> > -       { return std::forward<_Tp>(__t) > std::forward<_Up>(__u); }
> > -
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr bool
> > -       _S_cmp(_Tp&& __t, _Up&& __u, true_type) noexcept
> > +#if __cplusplus >= 202002L
> > +      template<typename _Tp, typename _Up>
> > +       static constexpr bool __ptr_cmp = requires
> >         {
> > -         return greater<const volatile void*>{}(
> > -             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > -             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > -       }
> > -
> > +         requires
> > +              ! requires
> > +               { operator>(std::declval<_Tp>(), std::declval<_Up>()); }
> > +           && ! requires
> > +               { std::declval<_Tp>().operator>(std::declval<_Up>()); }
> > +           && __detail::__not_overloaded_spaceship<_Tp, _Up>
> > +           && is_convertible_v<_Tp, const volatile void*>
> > +           && is_convertible_v<_Up, const volatile void*>;
> > +       };
> > +#else
> >        // True if there is no viable operator> member function.
> >        template<typename _Tp, typename _Up, typename = void>
> >         struct __not_overloaded2 : true_type { };
> > @@ -572,9 +582,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         : false_type { };
> >
> >        template<typename _Tp, typename _Up>
> > -       using __ptr_cmp = __and_<__not_overloaded<_Tp, _Up>,
> > -             is_convertible<_Tp, const volatile void*>,
> > -             is_convertible<_Up, const volatile void*>>;
> > +       static constexpr bool __ptr_cmp = __and_<
> > +         __not_overloaded<_Tp, _Up>,
> > +         is_convertible<_Tp, const volatile void*>,
> > +         is_convertible<_Up, const volatile void*>>::value;
> > +#endif
> >      };
> >
> >    /// One of the @link comparison_functors comparison functors@endlink.
> > @@ -587,8 +599,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         noexcept(noexcept(std::forward<_Tp>(__t) <
> std::forward<_Up>(__u)))
> >         -> decltype(std::forward<_Tp>(__t) < std::forward<_Up>(__u))
> >         {
> > -         return _S_cmp(std::forward<_Tp>(__t), std::forward<_Up>(__u),
> > -                       __ptr_cmp<_Tp, _Up>{});
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> > +         if constexpr (__ptr_cmp<_Tp, _Up>)
> > +           return less<const volatile void*>{}(
> > +             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > +             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > +         else
> > +           return std::forward<_Tp>(__t) < std::forward<_Up>(__u);
> > +#pragma GCC diagnostic pop
> >         }
> >
> >        template<typename _Tp, typename _Up>
> > @@ -599,20 +618,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        typedef __is_transparent is_transparent;
> >
> >      private:
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr decltype(auto)
> > -       _S_cmp(_Tp&& __t, _Up&& __u, false_type)
> > -       { return std::forward<_Tp>(__t) < std::forward<_Up>(__u); }
> > -
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr bool
> > -       _S_cmp(_Tp&& __t, _Up&& __u, true_type) noexcept
> > +#if __cplusplus >= 202002L
> > +      template<typename _Tp, typename _Up>
> > +       static constexpr bool __ptr_cmp = requires
> >         {
> > -         return less<const volatile void*>{}(
> > -             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > -             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > -       }
> > -
> > +         requires
> > +              ! requires
> > +               { operator<(std::declval<_Tp>(), std::declval<_Up>()); }
> > +           && ! requires
> > +               { std::declval<_Tp>().operator<(std::declval<_Up>()); }
> > +           && __detail::__not_overloaded_spaceship<_Tp, _Up>
> > +           && is_convertible_v<_Tp, const volatile void*>
> > +           && is_convertible_v<_Up, const volatile void*>;
> > +       };
> > +#else
> >        // True if there is no viable operator< member function.
> >        template<typename _Tp, typename _Up, typename = void>
> >         struct __not_overloaded2 : true_type { };
> > @@ -634,9 +653,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         : false_type { };
> >
> >        template<typename _Tp, typename _Up>
> > -       using __ptr_cmp = __and_<__not_overloaded<_Tp, _Up>,
> > -             is_convertible<_Tp, const volatile void*>,
> > -             is_convertible<_Up, const volatile void*>>;
> > +       static constexpr bool __ptr_cmp = __and_<
> > +         __not_overloaded<_Tp, _Up>,
> > +         is_convertible<_Tp, const volatile void*>,
> > +         is_convertible<_Up, const volatile void*>>::value;
> > +#endif
> >      };
> >
> >    /// One of the @link comparison_functors comparison functors@endlink.
> > @@ -649,8 +670,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         noexcept(noexcept(std::forward<_Tp>(__t) >=
> std::forward<_Up>(__u)))
> >         -> decltype(std::forward<_Tp>(__t) >= std::forward<_Up>(__u))
> >         {
> > -         return _S_cmp(std::forward<_Tp>(__t), std::forward<_Up>(__u),
> > -                       __ptr_cmp<_Tp, _Up>{});
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> > +         if constexpr (__ptr_cmp<_Tp, _Up>)
> > +           return greater_equal<const volatile void*>{}(
> > +             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > +             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > +         else
> > +           return std::forward<_Tp>(__t) >= std::forward<_Up>(__u);
> > +#pragma GCC diagnostic pop
> >         }
> >
> >        template<typename _Tp, typename _Up>
> > @@ -661,20 +689,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        typedef __is_transparent is_transparent;
> >
> >      private:
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr decltype(auto)
> > -       _S_cmp(_Tp&& __t, _Up&& __u, false_type)
> > -       { return std::forward<_Tp>(__t) >= std::forward<_Up>(__u); }
> > -
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr bool
> > -       _S_cmp(_Tp&& __t, _Up&& __u, true_type) noexcept
> > +#if __cplusplus >= 202002L
> > +      template<typename _Tp, typename _Up>
> > +       static constexpr bool __ptr_cmp = requires
> >         {
> > -         return greater_equal<const volatile void*>{}(
> > -             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > -             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > -       }
> > -
> > +         requires
> > +              ! requires
> > +               { operator>=(std::declval<_Tp>(), std::declval<_Up>()); }
> > +           && ! requires
> > +               { std::declval<_Tp>().operator>=(std::declval<_Up>()); }
> > +           && __detail::__not_overloaded_spaceship<_Tp, _Up>
> > +           && is_convertible_v<_Tp, const volatile void*>
> > +           && is_convertible_v<_Up, const volatile void*>;
> > +       };
> > +#else
> >        // True if there is no viable operator>= member function.
> >        template<typename _Tp, typename _Up, typename = void>
> >         struct __not_overloaded2 : true_type { };
> > @@ -696,9 +724,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         : false_type { };
> >
> >        template<typename _Tp, typename _Up>
> > -       using __ptr_cmp = __and_<__not_overloaded<_Tp, _Up>,
> > -             is_convertible<_Tp, const volatile void*>,
> > -             is_convertible<_Up, const volatile void*>>;
> > +       static constexpr bool __ptr_cmp = __and_<
> > +         __not_overloaded<_Tp, _Up>,
> > +         is_convertible<_Tp, const volatile void*>,
> > +         is_convertible<_Up, const volatile void*>>::value;
> > +#endif
> >      };
> >
> >    /// One of the @link comparison_functors comparison functors@endlink.
> > @@ -711,8 +741,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         noexcept(noexcept(std::forward<_Tp>(__t) <=
> std::forward<_Up>(__u)))
> >         -> decltype(std::forward<_Tp>(__t) <= std::forward<_Up>(__u))
> >         {
> > -         return _S_cmp(std::forward<_Tp>(__t), std::forward<_Up>(__u),
> > -                       __ptr_cmp<_Tp, _Up>{});
> > +#pragma GCC diagnostic push
> > +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> > +         if constexpr (__ptr_cmp<_Tp, _Up>)
> > +           return less_equal<const volatile void*>{}(
> > +             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > +             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > +         else
> > +           return std::forward<_Tp>(__t) <= std::forward<_Up>(__u);
> > +#pragma GCC diagnostic pop
> >         }
> >
> >        template<typename _Tp, typename _Up>
> > @@ -723,20 +760,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        typedef __is_transparent is_transparent;
> >
> >      private:
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr decltype(auto)
> > -       _S_cmp(_Tp&& __t, _Up&& __u, false_type)
> > -       { return std::forward<_Tp>(__t) <= std::forward<_Up>(__u); }
> > -
> > -      template <typename _Tp, typename _Up>
> > -       static constexpr bool
> > -       _S_cmp(_Tp&& __t, _Up&& __u, true_type) noexcept
> > +#if __cplusplus >= 202002L
> > +      template<typename _Tp, typename _Up>
> > +       static constexpr bool __ptr_cmp = requires
> >         {
> > -         return less_equal<const volatile void*>{}(
> > -             static_cast<const volatile void*>(std::forward<_Tp>(__t)),
> > -             static_cast<const volatile void*>(std::forward<_Up>(__u)));
> > -       }
> > -
> > +         requires
> > +              ! requires
> > +               { operator<=(std::declval<_Tp>(), std::declval<_Up>()); }
> > +           && ! requires
> > +               { std::declval<_Tp>().operator<=(std::declval<_Up>()); }
> > +           && __detail::__not_overloaded_spaceship<_Tp, _Up>
> > +           && is_convertible_v<_Tp, const volatile void*>
> > +           && is_convertible_v<_Up, const volatile void*>;
> > +       };
> > +#else
> >        // True if there is no viable operator<= member function.
> >        template<typename _Tp, typename _Up, typename = void>
> >         struct __not_overloaded2 : true_type { };
> > @@ -758,9 +795,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         : false_type { };
> >
> >        template<typename _Tp, typename _Up>
> > -       using __ptr_cmp = __and_<__not_overloaded<_Tp, _Up>,
> > -             is_convertible<_Tp, const volatile void*>,
> > -             is_convertible<_Up, const volatile void*>>;
> > +       static constexpr bool __ptr_cmp = __and_<
> > +         __not_overloaded<_Tp, _Up>,
> > +         is_convertible<_Tp, const volatile void*>,
> > +         is_convertible<_Up, const volatile void*>>::value;
> > +#endif
> >      };
> >  #else // < C++14
> >
> > diff --git a/libstdc++-v3/include/std/concepts
> b/libstdc++-v3/include/std/concepts
> > index 9c687b03e80..7673443f33a 100644
> > --- a/libstdc++-v3/include/std/concepts
> > +++ b/libstdc++-v3/include/std/concepts
> > @@ -405,6 +405,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    template<typename _Rel, typename _Tp, typename _Up>
> >      concept strict_weak_order = relation<_Rel, _Tp, _Up>;
> >
> > +  namespace __detail
> > +  {
> > +    // operator<=> are automatically reversed, so we need to consider
> > +    // both directions if types are different.
> > +    template<typename _Tp, typename _Up>
> > +      concept __not_overloaded_spaceship
> > +        =  ! requires(_Tp&& __t, _Up&& __u)
> > +           { operator<=>(static_cast<_Tp&&>(__t),
> static_cast<_Up&&>(__u)); }
> > +       && ! requires(_Tp&& __t, _Up&& __u)
> > +           {
> static_cast<_Tp&&>(__t).operator<=>(static_cast<_Up&&>(__u)); }
> > +       && (is_same_v<_Tp, _Up>
> > +           || (! requires(_Tp&& __t, _Up&& __u)
> > +                { operator<=>(static_cast<_Up&&>(__u),
> static_cast<_Tp&&>(__t)); }
> > +              && ! requires(_Tp&& __t, _Up&& __u)
> > +                {
> static_cast<_Up&&>(__u).operator<=>(static_cast<_Tp&&>(__t)); }));
> > +  }
> >  _GLIBCXX_END_NAMESPACE_VERSION
> >  } // namespace
> >  #endif // __cpp_lib_concepts
> > diff --git a/libstdc++-v3/libsupc++/compare
> b/libstdc++-v3/libsupc++/compare
> > index a00bbefb069..3847d0fb141 100644
> > --- a/libstdc++-v3/libsupc++/compare
> > +++ b/libstdc++-v3/libsupc++/compare
> > @@ -560,10 +560,7 @@ namespace std _GLIBCXX_VISIBILITY(default)
> >           { static_cast<_Tp&&>(__t) <=> static_cast<_Up&&>(__u); }
> >           && convertible_to<_Tp, const volatile void*>
> >           && convertible_to<_Up, const volatile void*>
> > -         && ! requires(_Tp&& __t, _Up&& __u)
> > -         { operator<=>(static_cast<_Tp&&>(__t),
> static_cast<_Up&&>(__u)); }
> > -         && ! requires(_Tp&& __t, _Up&& __u)
> > -         {
> static_cast<_Tp&&>(__t).operator<=>(static_cast<_Up&&>(__u)); };
> > +         && __not_overloaded_spaceship<_Tp, _Up>;
> >    } // namespace __detail
> >
> >    // _GLIBCXX_RESOLVE_LIB_DEFECTS
> > diff --git
> a/libstdc++-v3/testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc
> b/libstdc++-v3/testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc
> > new file mode 100644
> > index 00000000000..80fa94b5dbf
> > --- /dev/null
> > +++
> b/libstdc++-v3/testsuite/20_util/function_objects/comparisons_pointer_spaceship.cc
> > @@ -0,0 +1,336 @@
> > +// { dg-do run { target c++20 } }
> > +
> > +#include <compare>
> > +#include <cstring>
> > +#include <functional>
> > +#include <testsuite_hooks.h>
> > +
> > +constexpr const char arr[] = "efgh\0abcd\0ijkl";
> > +constexpr const char* s1 = arr;
> > +constexpr const char* s2 = arr+5;
> > +constexpr const char* s3 = arr+6;
> > +
> > +struct CStrNone
> > +{
> > +  const char* str;
> > +
> > +  constexpr
> > +  operator const char*() const
> > +  { return str; }
> > +};
> > +
> > +template<typename ResultCreator>
> > +struct CStrMem
> > +{
> > +  const char* str;
> > +
> > +  constexpr
> > +  operator const char*() const
> > +  { return str; }
> > +
> > +  auto operator<=>(CStrMem const& rhs) const
> > +  { return ResultCreator::create(std::strcmp(this->str, rhs.str)); }
> > +
> > +  auto operator<=>(const char* rhs) const
> > +  { return ResultCreator::create(std::strcmp(this->str, rhs)); }
> > +};
> > +
> > +template<typename ResultCreator>
> > +struct CStrFriend
> > +{
> > +  const char* str;
> > +
> > +  constexpr
> > +  operator const char*() const
> > +  { return str; }
> > +
> > +  friend auto operator<=>(CStrFriend lhs, CStrFriend rhs)
> > +  { return ResultCreator::create(std::strcmp(lhs.str, rhs.str)); }
> > +
> > +  friend auto operator<=>(CStrFriend lhs, const char* rhs)
> > +  { return ResultCreator::create(std::strcmp(lhs.str, rhs)); }
> > +};
> > +
> > +template<typename ResultCreator>
> > +struct CStrFree
> > +{
> > +  const char* str;
> > +
> > +  constexpr
> > +  operator const char*() const
> > +  { return str; }
> > +};
> > +
> > +template<typename RC>
> > +auto operator<=>(CStrFree<RC> lhs, CStrFree<RC> rhs)
> > +{ return RC::create(std::strcmp(lhs.str, rhs.str)); }
> > +
> > +template<typename RC>
> > +auto operator<=>(CStrFree<RC> lhs, const char* rhs)
> > +{ return RC::create(std::strcmp(lhs.str, rhs)); }
> > +
> > +template<typename ResultCreator>
> > +struct CStrMixed
> > +{
> > +  const char* str;
> > +
> > +  constexpr
> > +  operator const char*() const
> > +  { return str; }
> > +};
> > +
> > +template<typename RC>
> > +auto operator<=>(CStrMixed<RC> lhs, CStrMixed<RC> rhs)
> > +{ return RC::create(std::strcmp(lhs.str, rhs.str)); }
> > +
> > +template<typename RC>
> > +auto operator<=>(CStrMixed<RC> lhs, CStrFree<RC> rhs)
> > +{ return RC::create(std::strcmp(lhs.str, rhs.str)); }
> > +
> > +// If the type returned from shapeship does not support relational
> > +// operators, then synthesized operators are ill-formed, SFINAEable.
> > +struct ReturnVoid
> > +{
> > +  constexpr static void
> > +  create(int) { }
> > +};
> > +
> > +struct NoOperators
> > +{
> > +  constexpr static NoOperators
> > +  create(int)
> > +  { return NoOperators(); }
> > +};
> > +
> > +// std defined ordering types are expected
> > +template<typename Ord = std::strong_ordering>
> > +struct ReturnOrd
> > +{
> > +  constexpr static Ord
> > +  create(int cmp)
> > +  { return cmp <=> 0; }
> > +};
> > +
> > +// However, other types that provide required
> > +// operators are supported.
> > +struct ReturnInt
> > +{
> > +  constexpr static int
> > +  create(int cmp)
> > +  { return cmp; }
> > +};
> > +
> > +struct CustomOrd
> > +{
> > +  constexpr static CustomOrd
> > +  create(int cmp)
> > +  { return CustomOrd(cmp); }
> > +
> > +  CustomOrd() = default;
> > +
> > +  explicit constexpr
> > +  CustomOrd(int cmp)
> > +  : v(cmp) {}
> > +
> > +  friend constexpr bool
> > +  operator<(CustomOrd c, std::nullptr_t)
> > +  { return c.v < 0; }
> > +
> > +  friend constexpr bool
> > +  operator<(std::nullptr_t, CustomOrd c)
> > +  { return 0 < c.v; }
> > +
> > +  friend constexpr bool
> > +  operator>(CustomOrd c, std::nullptr_t)
> > +  { return c.v > 0; }
> > +
> > +  friend constexpr bool
> > +  operator>(std::nullptr_t, CustomOrd c)
> > +  { return 0 > c.v; }
> > +
> > +  friend constexpr bool
> > +  operator<=(CustomOrd c, std::nullptr_t)
> > +  { return c.v <= 0; }
> > +
> > +  friend constexpr bool
> > +  operator<=(std::nullptr_t, CustomOrd c)
> > +  { return 0 <= c.v; }
> > +
> > +  friend constexpr bool
> > +  operator>=(CustomOrd c, std::nullptr_t)
> > +  { return c.v >= 0; }
> > +
> > +  friend constexpr bool
> > +  operator>=(std::nullptr_t, CustomOrd c)
> > +  { return 0 >= c.v; }
> > +
> > +  friend constexpr CustomOrd
> > +  operator<=>(CustomOrd c, std::nullptr_t)
> > +  { return c; }
> > +
> > +  friend constexpr CustomOrd
> > +  operator<=>(std::nullptr_t, CustomOrd c)
> > +  { return CustomOrd(-c.v); }
> > +
> > +private:
> > +  int v = 0;
> > +};
> > +
> > +template<typename CStr1, typename CStr2>
> > +void
> > +test_relational(bool use_overloaded)
> > +{
> > +  CStr1 cs1{s1}; CStr2 cs2{s2};
> > +
> > +  if (use_overloaded)
> > +  {
> > +    // Overloaded operaetors compare content of the string,
> > +    // and cs1 > cs2;
> > +
> > +    VERIFY( !(cs1 < cs2) );
> > +    VERIFY( !std::less<>{}(cs1, cs2) );
> > +    VERIFY( !std::ranges::less{}(cs1, cs2) );
> > +
> > +    VERIFY( (cs1 > cs2) );
> > +    VERIFY( std::greater<>{}(cs1, cs2) );
> > +    VERIFY( std::ranges::greater{}(cs1, cs2) );
> > +
> > +    VERIFY( !(cs1 < cs2) );
> > +    VERIFY( !std::less_equal<>{}(cs1, cs2) );
> > +    VERIFY( !std::ranges::less_equal{}(cs1, cs2) );
> > +
> > +    VERIFY( (cs1 > cs2) );
> > +    VERIFY( std::greater_equal<>{}(cs1, cs2) );
> > +    VERIFY( std::ranges::greater_equal{}(cs1, cs2) );
> > +  }
> > +  else
> > +  {
> > +    // Without overloaded operators, we comapre pointers,
> > +    // and cs1 < cs2;
> > +
> > +    VERIFY( (cs1 < cs2) );
> > +    VERIFY( std::less<>{}(cs1, cs2) );
> > +    VERIFY( std::ranges::less{}(cs1, cs2) );
> > +
> > +    VERIFY( !(cs1 > cs2) );
> > +    VERIFY( !std::greater<>{}(cs1, cs2) );
> > +    VERIFY( !std::ranges::greater{}(cs1, cs2) );
> > +
> > +    VERIFY( (cs1 < cs2) );
> > +    VERIFY( std::less_equal<>{}(cs1, cs2) );
> > +    VERIFY( std::ranges::less_equal{}(cs1, cs2) );
> > +
> > +    VERIFY( !(cs1 > cs2) );
> > +    VERIFY( !std::greater_equal<>{}(cs1, cs2) );
> > +    VERIFY( !std::ranges::greater_equal{}(cs1, cs2) );
> > +  }
> > +}
> > +
> > +template<typename CStr>
> > +void
> > +test_relational_type(bool use_overloaded)
> > +{
> > +  test_relational<CStr, CStr>(use_overloaded);
> > +  test_relational<CStr, const char*>(use_overloaded);
> > +  test_relational<const char*, CStr>(use_overloaded);
> > +}
> > +
> > +template<typename RC>
> > +void
> > +test_relational_return()
> > +{
> > +  test_relational_type<CStrMem<RC>>(true);
> > +  test_relational_type<CStrFriend<RC>>(true);
> > +  test_relational_type<CStrFree<RC>>(true);
> > +  test_relational<CStrMixed<RC>, CStrFree<RC>>(true);
> > +  test_relational<CStrFree<RC>, CStrMixed<RC>>(true);
> > +}
> > +
> > +template<typename CStr1, typename CStr2>
> > +void
> > +test_spaceship(bool use_overloaded)
> > +{
> > +  CStr1 cs1{s1}; CStr2 cs2{s2};
> > +
> > +  if (use_overloaded)
> > +  {
> > +    // Overloaded operaetors compare content of the string,
> > +    // and cs1 > cs2;
> > +    VERIFY( (cs1 <=> cs2) > 0 );
> > +    VERIFY( std::compare_three_way{}(cs1, cs2) > 0 );
> > +  }
> > +  else
> > +  {
> > +    // Without overloaded operators, we comapre pointers,
> > +    // and cs1 < cs2;
> > +    VERIFY( (cs1 <=> cs2) < 0 );
> > +    VERIFY( std::compare_three_way{}(cs1, cs2) < 0 );
> > +  }
> > +}
> > +
> > +template<typename CStr>
> > +void
> > +test_spaceship_type(bool use_overloaded)
> > +{
> > +  test_spaceship<CStr, CStr>(use_overloaded);
> > +  test_spaceship<CStr, const char*>(use_overloaded);
> > +  test_spaceship<const char*, CStr>(use_overloaded);
> > +}
> > +
> > +template<typename Ordering>
> > +void
> > +test_std_ordering()
> > +{
> > +  using RC = ReturnOrd<Ordering>;
> > +  test_relational_return<RC>();
> > +  test_spaceship_type<CStrMem<RC>>(true);
> > +  test_spaceship_type<CStrFriend<RC>>(true);
> > +  test_spaceship_type<CStrFree<RC>>(true);
> > +  test_spaceship<CStrMixed<RC>, CStrFree<RC>>(true);
> > +  test_spaceship<CStrFree<RC>, CStrMixed<RC>>(true);
> > +}
> > +
> > +template<typename CStr1, typename CStr2>
> > +void
> > +test_no_relational()
> > +{
> > +  CStr1 c1{}; CStr2 c2{};
> > +  static_assert(!requires { c1 < c2; });
> > +  static_assert(!requires { c1 > c2; });
> > +  static_assert(!requires { c1 <= c2; });
> > +  static_assert(!requires { c1 >= c2; });
> > +}
> > +
> > +template<typename CStr>
> > +void
> > +test_no_relational_type()
> > +{
> > +  test_no_relational<CStr, CStr>();
> > +  test_no_relational<CStr, const char*>();
> > +  test_no_relational<const char*, CStr>();
> > +}
> > +
> > +template<typename RC>
> > +void
> > +test_no_relational_return()
> > +{
> > +  test_no_relational_type<CStrMem<RC>>();
> > +  test_no_relational_type<CStrFriend<RC>>();
> > +  test_no_relational_type<CStrFree<RC>>();
> > +  test_no_relational<CStrMixed<RC>, CStrFree<RC>>();
> > +  test_no_relational<CStrFree<RC>, CStrMixed<RC>>();
> > +}
> > +
> > +int main()
> > +{
> > +  test_std_ordering<std::strong_ordering>();
> > +  test_std_ordering<std::weak_ordering>();
> > +  test_std_ordering<std::partial_ordering>();
> > +
> > +  test_relational_type<CStrNone>(false);
> > +  test_relational_return<ReturnInt>();
> > +  test_relational_return<CustomOrd>();
> > +
> > +  test_no_relational_return<ReturnVoid>();
> > +  test_no_relational_return<NoOperators>();
> > +}
> > --
> > 2.52.0
> >
>
>

Reply via email to