[libcxx-commits] [libcxx] [libc++] Implement comparison operators for `tuple` added in C++23 (PR #148799)

A. Jiang via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 15 01:01:48 PDT 2025


https://github.com/frederick-vs-ja created https://github.com/llvm/llvm-project/pull/148799

And constrain the new `operator==` since C++26.

This patch implements parts of P2165R4, P2944R3, and a possibly improved resolution of LWG3882. Currently, libstdc++ and MSVC STL constrain the new overloads in the same way.

Fixes #136765.

>From 9d4fbca4019bbfa99f6ab349b83626d360147f20 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Tue, 15 Jul 2025 16:00:38 +0800
Subject: [PATCH] [libc++] Implement comparison operators for `tuple` added in
 C++23

And constrain the new `operator==` since C++26.

This patch implements parts of P2165R4, P2944R3, and a possibly improved
resolution of LWG3882. Currently, libstdc++ and MSVC STL constrain the
new overloads in the same way.
---
 libcxx/docs/Status/Cxx23Papers.csv            |   2 +-
 libcxx/docs/Status/Cxx2cIssues.csv            |   1 +
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/tuple                          | 130 ++++-
 .../tuple/tuple.tuple/tuple.rel/eq.pass.cpp   | 380 +++++++++-----
 .../tuple/tuple.tuple/tuple.rel/lt.pass.cpp   | 469 +++++++++++-------
 ...ze_incompatible_three_way.compile.pass.cpp |  19 +-
 .../tuple.tuple/tuple.rel/three_way.pass.cpp  | 172 ++++++-
 8 files changed, 821 insertions(+), 354 deletions(-)

diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index e4fa07d82289d..f1d8e9a2bd09c 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -60,7 +60,7 @@
 "`P1642R11 <https://wg21.link/P1642R11>`__","Freestanding ``[utilities]``, ``[ranges]``, and ``[iterators]``","2022-07 (Virtual)","","",""
 "`P1899R3 <https://wg21.link/P1899R3>`__","``stride_view``","2022-07 (Virtual)","","",""
 "`P2093R14 <https://wg21.link/P2093R14>`__","Formatted output","2022-07 (Virtual)","|Complete|","18",""
-"`P2165R4 <https://wg21.link/P2165R4>`__","Compatibility between ``tuple``, ``pair`` and ``tuple-like`` objects","2022-07 (Virtual)","|Partial|","","Only the part for ``zip_view`` is implemented."
+"`P2165R4 <https://wg21.link/P2165R4>`__","Compatibility between ``tuple``, ``pair`` and ``tuple-like`` objects","2022-07 (Virtual)","|Partial|","","Changes of ``tuple``, ``adjacent_view``, and ``cartesian_product_view`` are not yet implemented."
 "`P2278R4 <https://wg21.link/P2278R4>`__","``cbegin`` should always return a constant iterator","2022-07 (Virtual)","","",""
 "`P2286R8 <https://wg21.link/P2286R8>`__","Formatting Ranges","2022-07 (Virtual)","|Complete|","16",""
 "`P2291R3 <https://wg21.link/P2291R3>`__","Add Constexpr Modifiers to Functions ``to_chars`` and ``from_chars`` for Integral Types in ``<charconv>`` Header","2022-07 (Virtual)","|Complete|","16",""
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index c8a3aa95b7480..70ef723cd2b02 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -149,4 +149,5 @@
 "`LWG3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Adopted Yet","|Complete|","16",""
 "`LWG4139 <https://wg21.link/LWG4139>`__","ยง[time.zone.leap] recursive constraint in <=>","Not Adopted Yet","|Complete|","20",""
 "`LWG3456 <https://wg21.link/LWG3456>`__","Pattern used by std::from_chars is underspecified (option B)","Not Adopted Yet","|Complete|","20",""
+"`LWG3882 <https://wg21.link/LWG3882>`__","``tuple`` relational operators have confused friendships","Not Adopted Yet","|Complete|","21","The comparsion operators are constrained harder than the proposed resolution. libstdc++ and MSVC STL do the same."
 "","","","","",""
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index a1854a6acc41a..6d9f245ab200d 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -59,7 +59,7 @@
 "`P2248R8 <https://wg21.link/P2248R8>`__","Enabling list-initialization for algorithms","2024-03 (Tokyo)","","",""
 "`P2810R4 <https://wg21.link/P2810R4>`__","``is_debugger_present`` ``is_replaceable``","2024-03 (Tokyo)","","",""
 "`P1068R11 <https://wg21.link/P1068R11>`__","Vector API for random number generation","2024-03 (Tokyo)","","",""
-"`P2944R3 <https://wg21.link/P2944R3>`__","Comparisons for ``reference_wrapper``","2024-03 (Tokyo)","|Partial|","","The changes to ``optional`` and ``tuple``'s equality overload from P2165R4 are not yet implemented"
+"`P2944R3 <https://wg21.link/P2944R3>`__","Comparisons for ``reference_wrapper``","2024-03 (Tokyo)","|Partial|","","The changes to ``optional`` are not yet implemented"
 "`P2642R6 <https://wg21.link/P2642R6>`__","Padded ``mdspan`` layouts","2024-03 (Tokyo)","","",""
 "`P3029R1 <https://wg21.link/P3029R1>`__","Better ``mdspan``'s CTAD","2024-03 (Tokyo)","|Complete|","19",""
 "","","","","",""
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 75021f0ea51f6..94cdf9d779304 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -230,6 +230,7 @@ template <class... Types>
 #  include <__tuple/sfinae_helpers.h>
 #  include <__tuple/tuple_element.h>
 #  include <__tuple/tuple_indices.h>
+#  include <__tuple/tuple_like.h>
 #  include <__tuple/tuple_like_ext.h>
 #  include <__tuple/tuple_size.h>
 #  include <__tuple/tuple_types.h>
@@ -288,6 +289,72 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 #  ifndef _LIBCPP_CXX03_LANG
 
+template <size_t _Ip>
+struct __tuple_equal {
+  template <class _Tp, class _Up>
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool operator()(const _Tp& __x, const _Up& __y) {
+    return __tuple_equal<_Ip - 1>()(__x, __y) && std::get<_Ip - 1>(__x) == std::get<_Ip - 1>(__y);
+  }
+
+#    if _LIBCPP_STD_VER >= 26
+  template <class _Tp, class _Up>
+  static constexpr bool __can_compare =
+      __tuple_equal<_Ip - 1>::template __can_compare<_Tp, _Up> && requires(const _Tp& __x, const _Up& __y) {
+        { std::get<_Ip - 1>(__x) == std::get<_Ip - 1>(__y) } -> __boolean_testable;
+      };
+#    endif // _LIBCPP_STD_VER >= 26
+};
+
+template <>
+struct __tuple_equal<0> {
+  template <class _Tp, class _Up>
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool operator()(const _Tp&, const _Up&) {
+    return true;
+  }
+
+#    if _LIBCPP_STD_VER >= 26
+  template <class _Tp, class _Up>
+  static constexpr bool __can_compare = true;
+#    endif // _LIBCPP_STD_VER >= 26
+};
+
+#    if _LIBCPP_STD_VER >= 20
+template <class _Ret, class _Tp, class _Up, size_t... _Is>
+_LIBCPP_HIDE_FROM_ABI constexpr _Ret __tuple_compare_three_way(const _Tp& __x, const _Up& __y, index_sequence<_Is...>) {
+  _Ret __result = strong_ordering::equal;
+  static_cast<void>(
+      ((__result = std::__synth_three_way(std::get<_Is>(__x), std::get<_Is>(__y)), __result != 0) || ...));
+  return __result;
+}
+#    endif // _LIBCPP_STD_VER >= 20
+
+#    if _LIBCPP_STD_VER >= 23
+template <class>
+inline constexpr bool __is_tuple_v = false;
+
+template <class... _Tp>
+inline constexpr bool __is_tuple_v<tuple<_Tp...>> = true;
+
+template <class _Tp>
+concept __tuple_like_no_tuple = __tuple_like<_Tp> && !__is_tuple_v<_Tp>;
+
+template <class _Tp, class _Up, class _IndexSeq>
+struct __tuple_common_comparison_category_impl {};
+template <class _Tp, class _Up, size_t... _Indices>
+  requires requires {
+    typename common_comparison_category_t<
+        __synth_three_way_result<tuple_element_t<_Indices, _Tp>, tuple_element_t<_Indices, _Up>>...>;
+  }
+struct __tuple_common_comparison_category_impl<_Tp, _Up, index_sequence<_Indices...>> {
+  using type _LIBCPP_NODEBUG = common_comparison_category_t<
+      __synth_three_way_result<tuple_element_t<_Indices, _Tp>, tuple_element_t<_Indices, _Up>>...>;
+};
+
+template <__tuple_like _Tp, __tuple_like _Up>
+using __tuple_common_comparison_category _LIBCPP_NODEBUG =
+    __tuple_common_comparison_category_impl<_Tp, _Up, make_index_sequence<tuple_size_v<_Tp>>>::type;
+#    endif // _LIBCPP_STD_VER >= 23
+
 // __tuple_leaf
 
 template <size_t _Ip, class _Hp, bool = is_empty<_Hp>::value && !__libcpp_is_final<_Hp>::value >
@@ -997,7 +1064,25 @@ public:
       noexcept(__all<is_nothrow_swappable_v<const _Tp&>...>::value) {
     __base_.swap(__t.__base_);
   }
-#    endif // _LIBCPP_STD_VER >= 23
+
+  template <__tuple_like_no_tuple _UTuple>
+#      if _LIBCPP_STD_VER >= 26
+    requires(__tuple_equal<sizeof...(_Tp)>::template __can_compare<tuple, _UTuple>) &&
+            (sizeof...(_Tp) == tuple_size_v<_UTuple>)
+#      endif // _LIBCPP_STD_VER >= 26
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const tuple& __x, const _UTuple& __y) {
+    static_assert(sizeof...(_Tp) == tuple_size_v<_UTuple>, "Can't compare tuple-like values of different sizes");
+    return __tuple_equal<sizeof...(_Tp)>()(__x, __y);
+  }
+
+  template <__tuple_like_no_tuple _UTuple>
+    requires(sizeof...(_Tp) == tuple_size_v<_UTuple>)
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __tuple_common_comparison_category<tuple, _UTuple>
+  operator<=>(const tuple& __x, const _UTuple& __y) {
+    return std::__tuple_compare_three_way<__tuple_common_comparison_category<tuple, _UTuple>>(
+        __x, __y, index_sequence_for<_Tp...>{});
+  }
+#    endif   // _LIBCPP_STD_VER >= 23
 };
 
 _LIBCPP_DIAGNOSTIC_PUSH
@@ -1019,6 +1104,21 @@ public:
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(tuple&) _NOEXCEPT {}
 #    if _LIBCPP_STD_VER >= 23
   _LIBCPP_HIDE_FROM_ABI constexpr void swap(const tuple&) const noexcept {}
+
+  template <__tuple_like_no_tuple _UTuple>
+#      if _LIBCPP_STD_VER >= 26
+    requires(tuple_size_v<_UTuple> == 0)
+#      endif // _LIBCPP_STD_VER >= 26
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const tuple& __x, const _UTuple& __y) {
+    static_assert(tuple_size_v<_UTuple> == 0, "Can't compare tuple-like values of different sizes");
+    return true;
+  }
+
+  template <__tuple_like_no_tuple _UTuple>
+    requires(tuple_size_v<_UTuple> == 0)
+  _LIBCPP_HIDE_FROM_ABI friend constexpr strong_ordering operator<=>(const tuple& __x, const _UTuple& __y) {
+    return strong_ordering::equal;
+  }
 #    endif
 };
 _LIBCPP_DIAGNOSTIC_POP
@@ -1137,22 +1237,6 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 tuple<_Tp&&...> forwa
   return tuple<_Tp&&...>(std::forward<_Tp>(__t)...);
 }
 
-template <size_t _Ip>
-struct __tuple_equal {
-  template <class _Tp, class _Up>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool operator()(const _Tp& __x, const _Up& __y) {
-    return __tuple_equal<_Ip - 1>()(__x, __y) && std::get<_Ip - 1>(__x) == std::get<_Ip - 1>(__y);
-  }
-};
-
-template <>
-struct __tuple_equal<0> {
-  template <class _Tp, class _Up>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool operator()(const _Tp&, const _Up&) {
-    return true;
-  }
-};
-
 template <class... _Tp, class... _Up>
 #    if _LIBCPP_STD_VER >= 26
   requires(__all<requires(const _Tp& __t, const _Up& __u) {
@@ -1169,20 +1253,12 @@ operator==(const tuple<_Tp...>& __x, const tuple<_Up...>& __y) {
 
 // operator<=>
 
-template <class... _Tp, class... _Up, size_t... _Is>
-_LIBCPP_HIDE_FROM_ABI constexpr auto
-__tuple_compare_three_way(const tuple<_Tp...>& __x, const tuple<_Up...>& __y, index_sequence<_Is...>) {
-  common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...> __result = strong_ordering::equal;
-  static_cast<void>(
-      ((__result = std::__synth_three_way(std::get<_Is>(__x), std::get<_Is>(__y)), __result != 0) || ...));
-  return __result;
-}
-
 template <class... _Tp, class... _Up>
   requires(sizeof...(_Tp) == sizeof...(_Up))
 _LIBCPP_HIDE_FROM_ABI constexpr common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>
 operator<=>(const tuple<_Tp...>& __x, const tuple<_Up...>& __y) {
-  return std::__tuple_compare_three_way(__x, __y, index_sequence_for<_Tp...>{});
+  return std::__tuple_compare_three_way<common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>>(
+      __x, __y, index_sequence_for<_Tp...>{});
 }
 
 #    else // _LIBCPP_STD_VER >= 20
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
index 779a89b163f04..b0301f3ad93e7 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp
@@ -13,6 +13,8 @@
 // template<class... TTypes, class... UTypes>
 //   bool
 //   operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// template<tuple-like UTuple>
+//   friend constexpr bool operator==(const tuple& t, const UTuple& u); // since C++23
 
 // UNSUPPORTED: c++03
 
@@ -23,6 +25,13 @@
 #include "test_comparisons.h"
 #include "test_macros.h"
 
+#if TEST_STD_VER >= 23
+#  include <ranges>
+#endif
+#if TEST_STD_VER >= 26
+#  include <complex>
+#endif
+
 #if TEST_STD_VER >= 26
 
 // Test SFINAE.
@@ -41,140 +50,249 @@ static_assert(
 static_assert(
     !std::equality_comparable_with<std::tuple<EqualityComparable, EqualityComparable>, std::tuple<EqualityComparable>>);
 
+// Heterogeneous comparisons.
+// TODO: Use equality_comparable_with once other changes of tuple introduced in P2165R4 are implemented.
+template <class T, class U>
+concept can_eq_compare = requires(const T& t, const U& u) { t == u; };
+
+static_assert(can_eq_compare<std::tuple<EqualityComparable>, std::array<EqualityComparable, 1>>);
+static_assert(!can_eq_compare<std::tuple<EqualityComparable>, std::array<NonComparable, 1>>);
+
+static_assert(can_eq_compare<std::tuple<EqualityComparable, EqualityComparable>,
+                             std::pair<EqualityComparable, EqualityComparable>>);
+static_assert(
+    !can_eq_compare<std::tuple<EqualityComparable, EqualityComparable>, std::pair<EqualityComparable, NonComparable>>);
+
+static_assert(can_eq_compare<std::tuple<int*, int*>, std::ranges::subrange<const int*>>);
+static_assert(!can_eq_compare<std::tuple<int (*)[1], int (*)[1]>, std::ranges::subrange<const int*>>);
+static_assert(can_eq_compare<std::tuple<double, double>, std::complex<float>>);
+static_assert(!can_eq_compare<std::tuple<int*, int*>, std::complex<float>>);
+
+// Size mismatch in heterogeneous comparisons.
+static_assert(!can_eq_compare<std::tuple<>, std::array<EqualityComparable, 2>>);
+static_assert(!can_eq_compare<std::tuple<EqualityComparable>, std::array<EqualityComparable, 2>>);
+static_assert(!can_eq_compare<std::tuple<>, std::pair<EqualityComparable, EqualityComparable>>);
+static_assert(!can_eq_compare<std::tuple<EqualityComparable>, std::pair<EqualityComparable, EqualityComparable>>);
+static_assert(!can_eq_compare<std::tuple<int*>, std::ranges::subrange<int*>>);
+static_assert(!can_eq_compare<std::tuple<double>, std::complex<double>>);
+
 #endif
 
-int main(int, char**)
-{
-    {
-        typedef std::tuple<> T1;
-        typedef std::tuple<> T2;
-        const T1 t1;
-        const T2 t2;
-        assert(t1 == t2);
-        assert(!(t1 != t2));
-    }
-    {
-        typedef std::tuple<int> T1;
-        typedef std::tuple<double> T2;
-        const T1 t1(1);
-        const T2 t2(1.1);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<int> T1;
-        typedef std::tuple<double> T2;
-        const T1 t1(1);
-        const T2 t2(1);
-        assert(t1 == t2);
-        assert(!(t1 != t2));
-    }
-    {
-        typedef std::tuple<int, double> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1, 2);
-        assert(t1 == t2);
-        assert(!(t1 != t2));
-    }
-    {
-        typedef std::tuple<int, double> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1, 3);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<int, double> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1.1, 2);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<int, double> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1.1, 3);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 2, 3);
-        assert(t1 == t2);
-        assert(!(t1 != t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1.1, 2, 3);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 3, 3);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 2, 4);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 3, 2);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1.1, 2, 2);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1.1, 3, 3);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1.1, 3, 2);
-        assert(!(t1 == t2));
-        assert(t1 != t2);
-    }
-#if TEST_STD_VER > 11
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        constexpr T1 t1(1, 2, 3);
-        constexpr T2 t2(1.1, 3, 2);
-        static_assert(!(t1 == t2), "");
-        static_assert(t1 != t2, "");
-    }
+TEST_CONSTEXPR_CXX14 bool test() {
+  {
+    typedef std::tuple<> T1;
+    typedef std::tuple<> T2;
+    const T1 t1;
+    const T2 t2;
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    typedef std::tuple<int> T1;
+    typedef std::tuple<double> T2;
+    const T1 t1(1);
+    const T2 t2(1.1);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<int> T1;
+    typedef std::tuple<double> T2;
+    const T1 t1(1);
+    const T2 t2(1);
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    typedef std::tuple<int, double> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1, 2);
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    typedef std::tuple<int, double> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1, 3);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<int, double> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1.1, 2);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<int, double> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1.1, 3);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 2, 3);
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1.1, 2, 3);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 3, 3);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 2, 4);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 3, 2);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1.1, 2, 2);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1.1, 3, 3);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1.1, 3, 2);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+#if TEST_STD_VER >= 14
+  {
+    using T1 = std::tuple<long, int, double>;
+    using T2 = std::tuple<double, long, int>;
+    constexpr T1 t1(1, 2, 3);
+    constexpr T2 t2(1.1, 3, 2);
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
 #endif
+#if TEST_STD_VER >= 23
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2};
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3};
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
+
+    int arr[1]{};
+    T1 t1{arr, arr + 1};
+    T2 t2{arr};
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
 
+    int arr[1]{};
+    T1 t1{arr, arr};
+    T2 t2{arr};
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+  {
+    assert((std::tuple<>{} == std::array<int*, 0>{}));
+    assert((std::tuple<>{} == std::array<double, 0>{}));
+  }
+#endif
+#if TEST_STD_VER >= 26
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    assert(t1 == t2);
+    assert(!(t1 != t2));
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    assert(!(t1 == t2));
+    assert(t1 != t2);
+  }
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+#if TEST_STD_VER >= 14
+  static_assert(test(), "");
+#endif
   return 0;
 }
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/lt.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/lt.pass.cpp
index 0ece614a23e80..ef50454885a2b 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/lt.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/lt.pass.cpp
@@ -28,186 +28,305 @@
 
 // UNSUPPORTED: c++03
 
-#include <tuple>
-#include <string>
 #include <cassert>
+#include <tuple>
 
 #include "test_macros.h"
 
-int main(int, char**)
-{
-    {
-        typedef std::tuple<> T1;
-        typedef std::tuple<> T2;
-        const T1 t1;
-        const T2 t2;
-        assert(!(t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long> T1;
-        typedef std::tuple<double> T2;
-        const T1 t1(1);
-        const T2 t2(1);
-        assert(!(t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long> T1;
-        typedef std::tuple<double> T2;
-        const T1 t1(1);
-        const T2 t2(0.9);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long> T1;
-        typedef std::tuple<double> T2;
-        const T1 t1(1);
-        const T2 t2(1.1);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1, 2);
-        assert(!(t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(0.9, 2);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1.1, 2);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1, 1);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int> T1;
-        typedef std::tuple<double, long> T2;
-        const T1 t1(1, 2);
-        const T2 t2(1, 3);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 2, 3);
-        assert(!(t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(0.9, 2, 3);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1.1, 2, 3);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 1, 3);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 3, 3);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 2, 2);
-        assert(!(t1 <  t2));
-        assert(!(t1 <= t2));
-        assert( (t1 >  t2));
-        assert( (t1 >= t2));
-    }
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        const T1 t1(1, 2, 3);
-        const T2 t2(1, 2, 4);
-        assert( (t1 <  t2));
-        assert( (t1 <= t2));
-        assert(!(t1 >  t2));
-        assert(!(t1 >= t2));
-    }
-#if TEST_STD_VER > 11
-    {
-        typedef std::tuple<long, int, double> T1;
-        typedef std::tuple<double, long, int> T2;
-        constexpr T1 t1(1, 2, 3);
-        constexpr T2 t2(1, 2, 4);
-        static_assert( (t1 <  t2), "");
-        static_assert( (t1 <= t2), "");
-        static_assert(!(t1 >  t2), "");
-        static_assert(!(t1 >= t2), "");
-    }
+#if TEST_STD_VER >= 23
+#  include <array>
+#  include <ranges>
+#  include <utility>
+#endif
+#if TEST_STD_VER >= 26
+#  include <complex>
+#endif
+
+TEST_CONSTEXPR_CXX14 bool test() {
+  {
+    typedef std::tuple<> T1;
+    typedef std::tuple<> T2;
+    const T1 t1;
+    const T2 t2;
+    assert(!(t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long> T1;
+    typedef std::tuple<double> T2;
+    const T1 t1(1);
+    const T2 t2(1);
+    assert(!(t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long> T1;
+    typedef std::tuple<double> T2;
+    const T1 t1(1);
+    const T2 t2(0.9);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long> T1;
+    typedef std::tuple<double> T2;
+    const T1 t1(1);
+    const T2 t2(1.1);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1, 2);
+    assert(!(t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(0.9, 2);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1.1, 2);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1, 1);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int> T1;
+    typedef std::tuple<double, long> T2;
+    const T1 t1(1, 2);
+    const T2 t2(1, 3);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 2, 3);
+    assert(!(t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(0.9, 2, 3);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1.1, 2, 3);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 1, 3);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 3, 3);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 2, 2);
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert((t1 > t2));
+    assert((t1 >= t2));
+  }
+  {
+    typedef std::tuple<long, int, double> T1;
+    typedef std::tuple<double, long, int> T2;
+    const T1 t1(1, 2, 3);
+    const T2 t2(1, 2, 4);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+#if TEST_STD_VER >= 14
+  {
+    using T1 = std::tuple<long, int, double>;
+    using T2 = std::tuple<double, long, int>;
+    constexpr T1 t1(1, 2, 3);
+    constexpr T2 t2(1, 2, 4);
+    assert((t1 < t2));
+    assert((t1 <= t2));
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+#endif
+#if TEST_STD_VER >= 23
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 3};
+    assert(t1 < t2);
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    assert(t1 < t2);
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
+
+    int arr[1]{};
+    T1 t1{arr, arr + 1};
+    T2 t2{arr};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
+
+    int arr[1]{};
+    T1 t1{arr + 1, arr + 1};
+    T2 t2{arr};
+    assert(!(t1 < t2));
+    assert(!(t1 <= t2));
+    assert(t1 > t2);
+    assert(t1 >= t2);
+  }
+  {
+    constexpr std::tuple<> t1{};
+    constexpr std::array<int*, 0> t2{};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+  {
+    constexpr std::tuple<> t1{};
+    constexpr std::array<double, 0> t2{};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+#endif
+#if TEST_STD_VER >= 26
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    assert(!(t1 < t2));
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(t1 >= t2);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    assert(t1 < t2);
+    assert(t1 <= t2);
+    assert(!(t1 > t2));
+    assert(!(t1 >= t2));
+  }
 #endif
 
+  return true;
+}
+
+int main(int, char**) {
+  test();
+#if TEST_STD_VER >= 14
+  static_assert(test(), "");
+#endif
   return 0;
 }
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
index f9c72a1742e72..8eae8d675c0a5 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp
@@ -13,16 +13,31 @@
 // template<class... TTypes, class... UTypes>
 //   auto
 //   operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// template<tuple-like UTuple>
+//   friend constexpr auto operator<=>(const tuple& t, const UTuple& u); // since C++23
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
+#include <array>
+#include <complex>
+#include <ranges>
 #include <tuple>
+#include <utility>
 
 template <class T, class U>
 concept can_compare = requires(T t, U u) { t <=> u; };
 
-typedef std::tuple<int> T1;
-typedef std::tuple<int, long> T2;
+using T1  = std::tuple<int>;
+using T2  = std::tuple<int, long>;
+using T1P = std::tuple<int*>;
 
 static_assert(!can_compare<T1, T2>);
 static_assert(!can_compare<T2, T1>);
+static_assert(!can_compare<T1, std::array<int, 2>>);
+static_assert(!can_compare<std::array<int, 2>, T1>);
+static_assert(!can_compare<T1, std::pair<int, long>>);
+static_assert(!can_compare<std::pair<int, long>, T1>);
+static_assert(!can_compare<T1, std::complex<double>>);
+static_assert(!can_compare<std::complex<double>, T1>);
+static_assert(!can_compare<T1P, std::ranges::subrange<int*>>);
+static_assert(!can_compare<std::ranges::subrange<int*>, T1P>);
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
index d954314874c4f..cdee6f2b28f00 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp
@@ -13,6 +13,8 @@
 // template<class... TTypes, class... UTypes>
 //   auto
 //   operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// template<tuple-like UTuple>
+//   friend constexpr auto operator<=>(const tuple& t, const UTuple& u); // since C++23
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
@@ -28,6 +30,15 @@ TEST_MSVC_DIAGNOSTIC_IGNORED(4242 4244)
 #include <tuple>
 #include <type_traits> // std::is_constant_evaluated
 
+#if TEST_STD_VER >= 23
+#  include <array>
+#  include <ranges>
+#  include <utility>
+#endif
+#if TEST_STD_VER >= 26
+#  include <complex>
+#endif
+
 // A custom three-way result type
 struct CustomEquality {
   friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
@@ -36,6 +47,11 @@ struct CustomEquality {
 };
 
 constexpr bool test() {
+  struct WeakSpaceship {
+    constexpr bool operator==(const WeakSpaceship&) const { return true; }
+    constexpr std::weak_ordering operator<=>(const WeakSpaceship&) const { return std::weak_ordering::equivalent; }
+  };
+
   // Empty tuple
   {
     typedef std::tuple<> T0;
@@ -135,23 +151,17 @@ constexpr bool test() {
     ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
   }
   {
-    struct WeakSpaceship {
-      constexpr bool operator==(const WeakSpaceship&) const { return true; }
-      constexpr std::weak_ordering operator<=>(const WeakSpaceship&) const { return std::weak_ordering::equivalent; }
-    };
-    {
-      typedef std::tuple<int, unsigned int, WeakSpaceship> T1;
-      typedef std::tuple<int, unsigned long, WeakSpaceship> T2;
-      // Strongly ordered members and a weakly ordered member yields weak ordering.
-      ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
-    }
-    {
-      typedef std::tuple<unsigned int, int, WeakSpaceship> T1;
-      typedef std::tuple<double, long, WeakSpaceship> T2;
-      // Doubles are partially ordered, so one partial, one strong, and one weak ordering
-      // yields partial ordering.
-      ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
-    }
+    typedef std::tuple<int, unsigned int, WeakSpaceship> T1;
+    typedef std::tuple<int, unsigned long, WeakSpaceship> T2;
+    // Strongly ordered members and a weakly ordered member yields weak ordering.
+    ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
+  }
+  {
+    typedef std::tuple<unsigned int, int, WeakSpaceship> T1;
+    typedef std::tuple<double, long, WeakSpaceship> T2;
+    // Doubles are partially ordered, so one partial, one strong, and one weak ordering
+    // yields partial ordering.
+    ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
   }
   {
     struct NoSpaceship {
@@ -224,6 +234,134 @@ constexpr bool test() {
     }
   }
 
+// Heterogeneous comparisons enabled by P2165R4.
+#if TEST_STD_VER >= 23
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<int, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1, 2};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::equal);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<int, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1, 0};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::greater);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::less);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::equivalent);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::pair<double, long>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::less);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::equivalent);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::array<double, 2>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::less);
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
+
+    int arr[1]{};
+    T1 t1{arr, arr + 1};
+    T2 t2{arr};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::equal);
+  }
+  {
+    using T1 = std::tuple<const int*, const int*>;
+    using T2 = std::ranges::subrange<const int*>;
+
+    int arr[1]{};
+    T1 t1{arr + 1, arr + 1};
+    T2 t2{arr};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::greater);
+  }
+  {
+    constexpr std::tuple<WeakSpaceship, WeakSpaceship> t1{};
+    constexpr std::pair<WeakSpaceship, WeakSpaceship> t2{};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::weak_ordering);
+    assert((t1 <=> t2) == std::weak_ordering::equivalent);
+  }
+  {
+    constexpr std::tuple<WeakSpaceship, WeakSpaceship> t1{};
+    constexpr std::array<WeakSpaceship, 2> t2{};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::weak_ordering);
+    assert((t1 <=> t2) == std::weak_ordering::equivalent);
+  }
+  {
+    constexpr std::tuple<> t1{};
+    constexpr std::array<int*, 0> t2{};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::equal);
+  }
+  {
+    constexpr std::tuple<> t1{};
+    constexpr std::array<double, 0> t2{};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::equal);
+  }
+  {
+    constexpr std::tuple<> t1{};
+    constexpr std::array<WeakSpaceship, 0> t2{};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::strong_ordering);
+    assert((t1 <=> t2) == std::strong_ordering::equal);
+  }
+#endif
+#if TEST_STD_VER >= 26
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.0, 2.0};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::equivalent);
+  }
+  {
+    using T1 = std::tuple<long, int>;
+    using T2 = std::complex<double>;
+    constexpr T1 t1{1, 2};
+    constexpr T2 t2{1.1, 3.0};
+    ASSERT_SAME_TYPE(decltype(t1 <=> t2), std::partial_ordering);
+    assert((t1 <=> t2) == std::partial_ordering::less);
+  }
+#endif
+
   return true;
 }
 



More information about the libcxx-commits mailing list