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 > > > >
