diff --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv --- a/libcxx/docs/Status/Cxx2bIssues.csv +++ b/libcxx/docs/Status/Cxx2bIssues.csv @@ -7,7 +7,7 @@ "`3236 `__","Random access iterator requirements lack limiting relational operators domain to comparing those from the same range","November 2020","|Nothing To Do|","","|ranges|" "`3265 `__","``move_iterator``'s conversions are more broken after P1207","November 2020","Fixed by `LWG3435 `__","" "`3435 `__","``three_way_comparable_with, reverse_iterator>``","November 2020","|Complete|","13.0" -"`3432 `__","Missing requirement for ``comparison_category``","November 2020","","","|spaceship|" +"`3432 `__","Missing requirement for ``comparison_category``","November 2020","|Complete|","16.0","|spaceship|" "`3447 `__","Deduction guides for ``take_view`` and ``drop_view`` have different constraints","November 2020","|Complete|","14.0" "`3450 `__","The const overloads of ``take_while_view::begin/end`` are underconstrained","November 2020","","","|ranges|" "`3464 `__","``istream::gcount()`` can overflow","November 2020","","" diff --git a/libcxx/docs/Status/SpaceshipProjects.csv b/libcxx/docs/Status/SpaceshipProjects.csv --- a/libcxx/docs/Status/SpaceshipProjects.csv +++ b/libcxx/docs/Status/SpaceshipProjects.csv @@ -33,7 +33,7 @@ | `[stacktrace.entry.cmp] `_,| stacktrace_entry,None,Unassigned,|Not Started| | `[stacktrace.basic.cmp] `_,| basic_stacktrace,[alg.three.way],Unassigned,|Not Started| | `[string.cmp] `_,| `basic_string `_,None,Christopher Di Bella,|In Progress| -| `[string.view.comparison] `_,| `basic_string_view `_,None,Christopher Di Bella,|In Progress| +| `[string.view.comparison] `_,|,None,Mark de Wever,|Complete| | `[array.syn] `_ (`general `_),| array,[expos.only.func],Unassigned,|Not Started| | `[deque.syn] `_ (`general `_),| deque,[expos.only.func],Unassigned,|Not Started| | `[forward.list.syn] `_ (`general `_),| forward_list,[expos.only.func],Unassigned,|Not Started| diff --git a/libcxx/include/__compare/ordering.h b/libcxx/include/__compare/ordering.h --- a/libcxx/include/__compare/ordering.h +++ b/libcxx/include/__compare/ordering.h @@ -312,6 +312,12 @@ inline constexpr strong_ordering strong_ordering::equivalent(_OrdResult::__equiv); inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater); +/// [cmp.categories.pre]/1 +/// The types partial_ordering, weak_ordering, and strong_ordering are +/// collectively termed the comparison category types. +template +concept __comparison_category = __one_of_v<_Tp, partial_ordering, weak_ordering, strong_ordering>; + #endif // _LIBCPP_STD_VER > 17 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__string/char_traits.h b/libcxx/include/__string/char_traits.h --- a/libcxx/include/__string/char_traits.h +++ b/libcxx/include/__string/char_traits.h @@ -17,6 +17,7 @@ #include <__config> #include <__functional/hash.h> #include <__iterator/iterator_traits.h> +#include #include #include #include @@ -193,6 +194,9 @@ typedef streamoff off_type; typedef streampos pos_type; typedef mbstate_t state_type; +#if _LIBCPP_STD_VER > 17 + using comparison_category = strong_ordering; +#endif static inline _LIBCPP_CONSTEXPR_AFTER_CXX14 void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;} @@ -307,6 +311,9 @@ typedef streamoff off_type; typedef streampos pos_type; typedef mbstate_t state_type; +# if _LIBCPP_STD_VER > 17 + using comparison_category = strong_ordering; +# endif static inline _LIBCPP_CONSTEXPR_AFTER_CXX14 void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;} @@ -423,6 +430,9 @@ typedef streamoff off_type; typedef u8streampos pos_type; typedef mbstate_t state_type; +# if _LIBCPP_STD_VER > 17 + using comparison_category = strong_ordering; +# endif static inline constexpr void assign(char_type& __c1, const char_type& __c2) noexcept {__c1 = __c2;} @@ -524,6 +534,9 @@ typedef streamoff off_type; typedef u16streampos pos_type; typedef mbstate_t state_type; +#if _LIBCPP_STD_VER > 17 + using comparison_category = strong_ordering; +#endif static inline _LIBCPP_CONSTEXPR_AFTER_CXX14 void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;} @@ -615,6 +628,9 @@ typedef streamoff off_type; typedef u32streampos pos_type; typedef mbstate_t state_type; +#if _LIBCPP_STD_VER > 17 + using comparison_category = strong_ordering; +#endif static inline _LIBCPP_CONSTEXPR_AFTER_CXX14 void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;} diff --git a/libcxx/include/string_view b/libcxx/include/string_view --- a/libcxx/include/string_view +++ b/libcxx/include/string_view @@ -14,6 +14,8 @@ string_view synopsis +#include + namespace std { // 7.2, Class template basic_string_view @@ -30,21 +32,25 @@ template constexpr bool operator==(basic_string_view x, basic_string_view y) noexcept; - template + template // Removed in C++20 constexpr bool operator!=(basic_string_view x, basic_string_view y) noexcept; - template + template // Removed in C++20 constexpr bool operator< (basic_string_view x, basic_string_view y) noexcept; - template + template // Removed in C++20 constexpr bool operator> (basic_string_view x, basic_string_view y) noexcept; - template + template // Removed in C++20 constexpr bool operator<=(basic_string_view x, basic_string_view y) noexcept; - template + template // Removed in C++20 constexpr bool operator>=(basic_string_view x, basic_string_view y) noexcept; + template // Since C++20 + constexpr see below operator<=>(basic_string_view x, + basic_string_view y) noexcept; + // see below, sufficient additional overloads of comparison functions // 7.10, Inserters and extractors @@ -770,6 +776,8 @@ return __lhs.compare(__rhs) == 0; } +#if _LIBCPP_STD_VER < 20 +// This overload is automatically generated in C++20. template _LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY bool operator==(typename common_type >::type __lhs, @@ -778,7 +786,41 @@ if ( __lhs.size() != __rhs.size()) return false; return __lhs.compare(__rhs) == 0; } +#endif // _LIBCPP_STD_VER > 17 + +// operator <=> + +#if _LIBCPP_STD_VER > 17 + +template +_LIBCPP_HIDE_FROM_ABI constexpr auto +operator<=>(basic_string_view<_CharT, _Traits> __lhs, basic_string_view<_CharT, _Traits> __rhs) noexcept { + if constexpr (requires { typename _Traits::comparison_category; }) { + // [string.view]/4 + static_assert( + __comparison_category, + "return type is not a comparison category type"); + return static_cast(__lhs.compare(__rhs) <=> 0); + } else { + return static_cast(__lhs.compare(__rhs) <=> 0); + } +} + +template +_LIBCPP_HIDE_FROM_ABI constexpr auto operator<=>( + basic_string_view<_CharT, _Traits> __lhs, common_type_t> __rhs) noexcept { + if constexpr (requires { typename _Traits::comparison_category; }) { + // [string.view]/4 + static_assert( + __comparison_category, + "return type is not a comparison category type"); + return static_cast(__lhs.compare(__rhs) <=> 0); + } else { + return static_cast(__lhs.compare(__rhs) <=> 0); + } +} +#else // _LIBCPP_STD_VER > 17 // operator != template @@ -911,6 +953,7 @@ return __lhs.compare(__rhs) >= 0; } +#endif // _LIBCPP_STD_VER > 17 template basic_ostream<_CharT, _Traits>& diff --git a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp --- a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp +++ b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp @@ -15,6 +15,7 @@ // typedef streamoff off_type; // typedef streampos pos_type; // typedef mbstate_t state_type; +// using comparison_category = strong_ordering; #include #include @@ -28,6 +29,9 @@ static_assert((std::is_same::off_type, std::streamoff>::value), ""); static_assert((std::is_same::pos_type, std::streampos>::value), ""); static_assert((std::is_same::state_type, std::mbstate_t>::value), ""); +#if TEST_STD_VER > 17 + static_assert(std::is_same_v::comparison_category, std::strong_ordering>); +#endif return 0; } diff --git a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp --- a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp +++ b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp @@ -15,6 +15,7 @@ // typedef streamoff off_type; // typedef u16streampos pos_type; // typedef mbstate_t state_type; +// using comparison_category = strong_ordering; #include #include @@ -29,6 +30,9 @@ static_assert((std::is_same::off_type, std::streamoff>::value), ""); static_assert((std::is_same::pos_type, std::u16streampos>::value), ""); static_assert((std::is_same::state_type, std::mbstate_t>::value), ""); +#if TEST_STD_VER > 17 + static_assert(std::is_same_v::comparison_category, std::strong_ordering>); +#endif return 0; } diff --git a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp --- a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp +++ b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp @@ -15,13 +15,19 @@ // typedef streamoff off_type; // typedef u32streampos pos_type; // typedef mbstate_t state_type; +// using comparison_category = strong_ordering; #include #include #include +#include "test_macros.h" + static_assert((std::is_same::char_type, char32_t>::value), ""); static_assert((std::is_same::int_type, std::uint_least32_t>::value), ""); static_assert((std::is_same::off_type, std::streamoff>::value), ""); static_assert((std::is_same::pos_type, std::u32streampos>::value), ""); static_assert((std::is_same::state_type, std::mbstate_t>::value), ""); +#if TEST_STD_VER > 17 +static_assert(std::is_same_v::comparison_category, std::strong_ordering>); +#endif diff --git a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp --- a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp +++ b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp @@ -16,6 +16,7 @@ // typedef streamoff off_type; // typedef u8streampos pos_type; // typedef mbstate_t state_type; +// using comparison_category = strong_ordering; #include #include @@ -31,6 +32,7 @@ static_assert((std::is_same::off_type, std::streamoff>::value), ""); static_assert((std::is_same::pos_type, std::u8streampos>::value), ""); static_assert((std::is_same::state_type, std::mbstate_t>::value), ""); + static_assert(std::is_same_v::comparison_category, std::strong_ordering>); #endif return 0; diff --git a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp --- a/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp +++ b/libcxx/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp @@ -15,6 +15,7 @@ // typedef streamoff off_type; // typedef streampos pos_type; // typedef mbstate_t state_type; +// using comparison_category = strong_ordering; // UNSUPPORTED: no-wide-characters @@ -30,6 +31,9 @@ static_assert((std::is_same::off_type, std::streamoff>::value), ""); static_assert((std::is_same::pos_type, std::wstreampos>::value), ""); static_assert((std::is_same::state_type, std::mbstate_t>::value), ""); +#if TEST_STD_VER > 17 + static_assert(std::is_same_v::comparison_category, std::strong_ordering>); +#endif return 0; } diff --git a/libcxx/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp b/libcxx/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp @@ -0,0 +1,149 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// Starting with C++20 the spaceship operator was included. This tests +// comparison in that context, thus doesn't support older language versions. +// These are tested per operator. + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// + +// template +// constexpr bool operator==(basic_string_view lhs, basic_string_view rhs); +// template +// constexpr auto operator<=>(basic_string_view lhs, basic_string_view rhs); +// (plus "sufficient additional overloads" to make implicit conversions work as intended) + +#include + +#include +#include +#include + +#include "constexpr_char_traits.h" +#include "make_string.h" +#include "test_comparisons.h" +#include "test_macros.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +// Copied from constexpr_char_traits, but it doesn't have a full implementation. +// It has a comparison_category used in the tests. +template +struct char_traits { + using char_type = CharT; + using int_type = int; + using off_type = std::streamoff; + using pos_type = std::streampos; + using state_type = std::mbstate_t; + using comparison_category = Ordering; + + static constexpr void assign(char_type& __c1, const char_type& __c2) noexcept { __c1 = __c2; } + static constexpr bool eq(char_type __c1, char_type __c2) noexcept { return __c1 == __c2; } + static constexpr bool lt(char_type __c1, char_type __c2) noexcept { return __c1 < __c2; } + static constexpr int compare(const char_type* __s1, const char_type* __s2, size_t __n) { + for (; __n; --__n, ++__s1, ++__s2) { + if (lt(*__s1, *__s2)) + return -1; + if (lt(*__s2, *__s1)) + return 1; + } + return 0; + } + + static constexpr size_t length(const char_type* __s); + static constexpr const char_type* find(const char_type* __s, size_t __n, const char_type& __a); + static constexpr char_type* move(char_type* __s1, const char_type* __s2, size_t __n); + static constexpr char_type* copy(char_type* __s1, const char_type* __s2, size_t __n); + static constexpr char_type* assign(char_type* __s, size_t __n, char_type __a); + static constexpr int_type not_eof(int_type __c) noexcept { return eq_int_type(__c, eof()) ? ~eof() : __c; } + static constexpr char_type to_char_type(int_type __c) noexcept { return char_type(__c); } + static constexpr int_type to_int_type(char_type __c) noexcept { return int_type(__c); } + static constexpr bool eq_int_type(int_type __c1, int_type __c2) noexcept { return __c1 == __c2; } + static constexpr int_type eof() noexcept { return int_type(EOF); } +}; + +template +constexpr void test() { + AssertOrderAreNoexcept(); + AssertOrderReturn(); + + using CharT = typename T::value_type; + + // sorted values + std::array v{ + SV(""), + SV("abc"), + SV("abcdef"), + }; + + // sorted values with embedded NUL character + std::array vn{ + SV("abc"), + SV("abc\0"), + SV("abc\0def"), + }; + static_assert(v.size() == vn.size()); + + for (size_t i = 0; i < v.size(); ++i) { + for (size_t j = 0; j < v.size(); ++j) { + assert(testOrder(v[i], v[j], i == j ? Ordering::equivalent : i < j ? Ordering::less : Ordering::greater)); + assert(testOrder( + v[i], + std::basic_string{v[j]}, + i == j ? Ordering::equivalent + : i < j ? Ordering::less + : Ordering::greater)); + + assert(testOrder( + v[i], + std::basic_string{v[j]}.c_str(), + i == j ? Ordering::equivalent + : i < j ? Ordering::less + : Ordering::greater)); + + // NUL test omitted for c-strings since it will fail. + assert(testOrder(vn[i], vn[j], i == j ? Ordering::equivalent : i < j ? Ordering::less : Ordering::greater)); + assert(testOrder( + vn[i], + std::basic_string{vn[j]}, + i == j ? Ordering::equivalent + : i < j ? Ordering::less + : Ordering::greater)); + } + } +} + +template +constexpr void test_all_orderings() { + test>(); // Strong ordering in its char_traits + test>, + std::weak_ordering>(); // No ordering in its char_traits + test>, std::weak_ordering>(); + test>, std::partial_ordering>(); +} + +constexpr bool test_all_types() { + test_all_orderings(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_all_orderings(); +#endif + test_all_orderings(); + test_all_orderings(); + test_all_orderings(); + + return true; +} + +int main(int, char**) { + test_all_types(); + static_assert(test_all_types()); + + return 0; +} diff --git a/libcxx/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp b/libcxx/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// + +// template +// constexpr auto operator<=>(basic_string_view lhs, basic_string_view rhs); +// +// LWG 3432 +// [string.view]/4 +// Mandates: R denotes a comparison category type ([cmp.categories]). + +#include + +#include "test_macros.h" + +template +struct traits { + typedef CharT char_type; + typedef int int_type; + typedef std::streamoff off_type; + typedef std::streampos pos_type; + typedef std::mbstate_t state_type; + using comparison_category = Ordering; + + static constexpr void assign(char_type&, const char_type&) noexcept; + static constexpr bool eq(char_type&, const char_type&) noexcept; + static constexpr bool lt(char_type&, const char_type&) noexcept; + + static constexpr int compare(const char_type*, const char_type*, size_t) { return 0; } + static constexpr size_t length(const char_type*); + static constexpr const char_type* find(const char_type*, size_t, const char_type&); + static constexpr char_type* move(char_type*, const char_type*, size_t); + static constexpr char_type* copy(char_type*, const char_type*, size_t); + static constexpr char_type* assign(char_type*, size_t, char_type); + + static constexpr int_type not_eof(int_type) noexcept; + + static constexpr char_type to_char_type(int_type) noexcept; + + static constexpr int_type to_int_type(char_type) noexcept; + + static constexpr bool eq_int_type(int_type, int_type) noexcept; + + static constexpr int_type eof() noexcept; +}; + +template +void test() { + using type = std::basic_string_view>; + if constexpr (Valid) + type{} <=> type{}; + else +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + // These diagnostics are issued for + // - Every invalid ordering + // - Every type + // expected-error-re@string_view:* 15 {{{{(static_assert|static assertion)}} failed{{.*}}return type is not a comparison category type}} + + // This diagnostic is not issued for Ordering == void. + // expected-error@string_view:* 10 {{no matching conversion for static_cast from}} +#else + // One less test run when wchar_t is unavailable. + // expected-error-re@string_view:* 12 {{{{(static_assert|static assertion)}} failed{{.*}}return type is not a comparison category type}} + // expected-error@string_view:* 8 {{no matching conversion for static_cast from}} +#endif + type{} <=> type{}; +} + +template +void test_all_orders() { + test(); + test(); + test(); + + test(); + test(); + test(); +} + +void test_all_types() { + test_all_orders(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_all_orders(); +#endif + test_all_orders(); + test_all_orders(); + test_all_orders(); +} diff --git a/libcxx/test/support/constexpr_char_traits.h b/libcxx/test/support/constexpr_char_traits.h --- a/libcxx/test/support/constexpr_char_traits.h +++ b/libcxx/test/support/constexpr_char_traits.h @@ -23,6 +23,8 @@ typedef std::streamoff off_type; typedef std::streampos pos_type; typedef std::mbstate_t state_type; + // The comparison_category is omitted so the class will have weak_ordering + // in C++20. This is intentional. static TEST_CONSTEXPR_CXX14 void assign(char_type& __c1, const char_type& __c2) TEST_NOEXCEPT {__c1 = __c2;} diff --git a/libcxx/test/support/test_comparisons.h b/libcxx/test/support/test_comparisons.h --- a/libcxx/test/support/test_comparisons.h +++ b/libcxx/test/support/test_comparisons.h @@ -25,6 +25,7 @@ #include #include +#include #include "test_macros.h" // Test all six comparison operations for sanity @@ -92,8 +93,7 @@ } template -void AssertComparisonsAreNoexcept() -{ +TEST_CONSTEXPR_CXX14 void AssertComparisonsAreNoexcept() { ASSERT_NOEXCEPT(std::declval() == std::declval()); ASSERT_NOEXCEPT(std::declval() != std::declval()); ASSERT_NOEXCEPT(std::declval() < std::declval()); @@ -103,8 +103,7 @@ } template -void AssertComparisonsReturnBool() -{ +TEST_CONSTEXPR_CXX14 void AssertComparisonsReturnBool() { ASSERT_SAME_TYPE(decltype(std::declval() == std::declval()), bool); ASSERT_SAME_TYPE(decltype(std::declval() != std::declval()), bool); ASSERT_SAME_TYPE(decltype(std::declval() < std::declval()), bool); @@ -113,7 +112,6 @@ ASSERT_SAME_TYPE(decltype(std::declval() >= std::declval()), bool); } - template void AssertComparisonsConvertibleToBool() { @@ -127,21 +125,26 @@ #if TEST_STD_VER > 17 template -void AssertOrderAreNoexcept() { - AssertComparisonsAreNoexcept(); - ASSERT_NOEXCEPT(std::declval() <=> std::declval()); +constexpr void AssertOrderAreNoexcept() { + AssertComparisonsAreNoexcept(); + ASSERT_NOEXCEPT(std::declval() <=> std::declval()); } template -void AssertOrderReturn() { - AssertComparisonsReturnBool(); - ASSERT_SAME_TYPE(decltype(std::declval() <=> std::declval()), Order); +constexpr void AssertOrderReturn() { + AssertComparisonsReturnBool(); + ASSERT_SAME_TYPE(decltype(std::declval() <=> std::declval()), Order); } template constexpr bool testOrder(const T& t1, const U& t2, Order order) { - return (t1 <=> t2 == order) && - testComparisons(t1, t2, order == Order::equal || order == Order::equivalent, order == Order::less); + bool equal = order == Order::equivalent; + if constexpr (std::same_as) + equal |= order == Order::equal; + + bool less = order == Order::less; + + return (t1 <=> t2 == order) && testComparisons(t1, t2, equal, less); } template