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.

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