Skip to content

[libc++] Implement comparison operators for tuple added in C++23 #148799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

frederick-vs-ja
Copy link
Contributor

@frederick-vs-ja frederick-vs-ja commented Jul 15, 2025

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.

Also set feature-test macro __cpp_lib_constrained_equality and add related release note, as P2944R3 will completed with this patch.

Fixes #136765. Fixes #136770. Fixes #105424.

@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner July 15, 2025 08:01
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 15, 2025

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

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.


Patch is 42.85 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/148799.diff

8 Files Affected:

  • (modified) libcxx/docs/Status/Cxx23Papers.csv (+1-1)
  • (modified) libcxx/docs/Status/Cxx2cIssues.csv (+1)
  • (modified) libcxx/docs/Status/Cxx2cPapers.csv (+1-1)
  • (modified) libcxx/include/tuple (+103-27)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/eq.pass.cpp (+249-131)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/lt.pass.cpp (+294-175)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_three_way.compile.pass.cpp (+17-2)
  • (modified) libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/three_way.pass.cpp (+155-17)
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>;
...
[truncated]

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.
And add a release note for P2944R3 and P3379R0.
Comment on lines +223 to +225
# include <__fwd/complex.h>
# include <__fwd/pair.h>
# include <__fwd/subrange.h>
Copy link
Contributor Author

@frederick-vs-ja frederick-vs-ja Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that module builds need these inclusions to make std::get visible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about including <__fwd/get.h> instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<__fwd/get.h> declares overloads for variant. I'm not sure whether this would be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that seems overkill, but IMO that's better than including forward declarations for seemingly unrelated classes like complex and subrange. What's the modules error you got?

template <class _Tp, class _Up, class _IndexSeq>
struct __tuple_common_comparison_category_impl {};
template <class _Tp, class _Up, size_t... _Indices>
requires(tuple_size_v<_Tp> == tuple_size_v<_Up>) && requires {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(tuple_size_v<_Tp> == tuple_size_v<_Up>) && is added for CWG2369. The version of Clang used for CI hasn't got #122423 landed.

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch!

@@ -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."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In principle, this would need to be an update to the BEGIN-RST-NOTES tag on #105200. But I agree this is not ergonomic since you really want the change to be associated to this PR and blameable somehow. Perhaps this means that we should make the status pages be the canonical representation of our conformance state, or perhaps it means that the version of the notes in the status pages should be the canonical ones? As I said in #148874, I'd love to find a workflow that works and is relatively simple.

Comment on lines +223 to +225
# include <__fwd/complex.h>
# include <__fwd/pair.h>
# include <__fwd/subrange.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about including <__fwd/get.h> instead?

@tru tru moved this from Needs Triage to Needs Backport PR in LLVM Release Status Jul 17, 2025
Comment on lines +223 to +225
# include <__fwd/complex.h>
# include <__fwd/pair.h>
# include <__fwd/subrange.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that seems overkill, but IMO that's better than including forward declarations for seemingly unrelated classes like complex and subrange. What's the modules error you got?

Comment on lines +308 to +309
template <class _Tp, class _Up, class _IndexSeq>
inline constexpr bool __can_tuple_compare_equal_impl = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can simplify this a bit by doing this instead:

template <class _Tp, class _Up, class _IndexSeq = make_index_sequence<tuple_size_v<_Tp>>>
inline constexpr bool __can_tuple_compare_equal = false;


template <class _Tp, class _Up, size_t... _Is>
   requires(tuple_size_v<_Tp> == tuple_size_v<_Up>)
inline constexpr bool __can_tuple_compare_equal<_Tp, _Up, index_sequence<_Is...>> =
  __all<requires(const tuple_element_t<_Is, _Tp>& __t, const tuple_element_t<_Is, _Up>& __u) {
    { __t == __u } -> __boolean_testable;
  }...>::value;

I think this lets you get rid of the _impl layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
Status: Needs Backport PR
3 participants