[libcxx-commits] [libcxx] [libc++] Fix instantiation of incomplete type when evaluating tuple comparisons (PR #204679)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jun 25 08:57:36 PDT 2026


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/204679

>From 46399341a055d5fa7f898e6318da6a65a8d19e10 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 16 Jun 2026 18:04:15 -0400
Subject: [PATCH 1/5] [libc++] Fix instantiation of incomplete type when
 evaluating tuple's operator<=>

Instantiating std::tuple<T...> in C++23 and later would require computing
tuple_size_v of the tuple type itself, because the hidden-friend operator<=>
queries it for its return type. That can lead to a hard error if instantiating
tuple_size requires the type to be complete.

This patch resolves that problem by refactoring the metaprogramming around
operator<=>. As a side effect, this should also be slightly more efficient
at compile-time because we don't cause the instantiation of tuple_element
for the current tuple.

rdar://179086119
---
 libcxx/include/tuple                          | 37 +++++++++++--------
 ...tuple_size_self_reference.compile.pass.cpp | 35 ++++++++++++++++++
 2 files changed, 57 insertions(+), 15 deletions(-)
 create mode 100644 libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp

diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 2b32df6bce74a..bf3061dc23f44 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -320,22 +320,29 @@ _LIBCPP_HIDE_FROM_ABI constexpr _Ret __tuple_compare_three_way(const _Tp& __x, c
 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 _Tuple1, class _Tuple2>
+struct __tuple_common_comparison_category;
 
-template <class _Tp, class _Up, size_t... _Is>
-  requires requires {
-    typename common_comparison_category_t<
-        __synth_three_way_result<tuple_element_t<_Is, _Tp>, tuple_element_t<_Is, _Up>>...>;
-  }
-struct __tuple_common_comparison_category_impl<_Tp, _Up, index_sequence<_Is...>> {
-  using type _LIBCPP_NODEBUG =
-      common_comparison_category_t<__synth_three_way_result<tuple_element_t<_Is, _Tp>, tuple_element_t<_Is, _Up>>...>;
+template <class... _Tp, class... _Up>
+  requires requires { typename common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>; }
+struct __tuple_common_comparison_category<tuple<_Tp...>, tuple<_Up...>> {
+  using type _LIBCPP_NODEBUG = common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>;
+};
+
+template <class _Tuple1, class _Tuple2>
+using __tuple_common_comparison_category_t _LIBCPP_NODEBUG =
+    typename __tuple_common_comparison_category<_Tuple1, _Tuple2>::type;
+
+template <__tuple_like _Tp, class _IndexSequence = make_index_sequence<tuple_size_v<_Tp>>>
+struct __to_tuple;
+
+template <__tuple_like _Tp, size_t... _Index>
+struct __to_tuple<_Tp, index_sequence<_Index...>> {
+  using type _LIBCPP_NODEBUG = tuple<tuple_element_t<_Index, _Tp>...>;
 };
 
-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;
+template <__tuple_like _Tp>
+using __to_tuple_t _LIBCPP_NODEBUG = typename __to_tuple<_Tp>::type;
 #    endif // _LIBCPP_STD_VER >= 23
 
 // __tuple_leaf
@@ -1010,9 +1017,9 @@ public:
 
   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>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __tuple_common_comparison_category_t<tuple, __to_tuple_t<_UTuple>>
   operator<=>(const tuple& __x, const _UTuple& __y) {
-    return std::__tuple_compare_three_way<__tuple_common_comparison_category<tuple, _UTuple>>(
+    return std::__tuple_compare_three_way< __tuple_common_comparison_category_t<tuple, __to_tuple_t<_UTuple>>>(
         __x, __y, index_sequence_for<_Tp...>{});
   }
 #    endif   // _LIBCPP_STD_VER >= 23
diff --git a/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp
new file mode 100644
index 0000000000000..811d84f2da2f7
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// Instantiating std::tuple<T...> must not form tuple_size_v of the tuple itself, as the tuple-like
+// operator<=> (C++23) and operator== (C++26) once did, which instantiated user tuple_size
+// specializations against the still-incomplete tuple.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <tuple>
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+struct Base {};
+
+template <std::derived_from<Base> T>
+struct std::tuple_size<T> : std::integral_constant<std::size_t, 1> {};
+
+void test() {
+  std::tuple<int> t = {1};
+  std::array<int, 1> a{0};
+  (void)(t <=> a);
+  (void)(t == a);
+}

>From 90c76f0cdce3892b39efdd89ec4d505248291266 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 19 Jun 2026 16:39:12 -0400
Subject: [PATCH 2/5] Move into tuple

---
 libcxx/include/tuple | 26 +++++++++++---------------
 1 file changed, 11 insertions(+), 15 deletions(-)

diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index bf3061dc23f44..1412627205bff 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -320,19 +320,6 @@ _LIBCPP_HIDE_FROM_ABI constexpr _Ret __tuple_compare_three_way(const _Tp& __x, c
 template <class _Tp>
 concept __tuple_like_no_tuple = __tuple_like<_Tp> && !__is_tuple_v<_Tp>;
 
-template <class _Tuple1, class _Tuple2>
-struct __tuple_common_comparison_category;
-
-template <class... _Tp, class... _Up>
-  requires requires { typename common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>; }
-struct __tuple_common_comparison_category<tuple<_Tp...>, tuple<_Up...>> {
-  using type _LIBCPP_NODEBUG = common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>;
-};
-
-template <class _Tuple1, class _Tuple2>
-using __tuple_common_comparison_category_t _LIBCPP_NODEBUG =
-    typename __tuple_common_comparison_category<_Tuple1, _Tuple2>::type;
-
 template <__tuple_like _Tp, class _IndexSequence = make_index_sequence<tuple_size_v<_Tp>>>
 struct __to_tuple;
 
@@ -1015,11 +1002,20 @@ public:
     return std::__tuple_compare_equal<sizeof...(_Tp)>(__x, __y);
   }
 
+  template <class _Tuple>
+  struct __common_comparison_category_with;
+
+  template <class... _Up>
+    requires requires { typename common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>; }
+  struct __common_comparison_category_with<tuple<_Up...>> {
+    using type _LIBCPP_NODEBUG = common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>;
+  };
+
   template <__tuple_like_no_tuple _UTuple>
     requires(sizeof...(_Tp) == tuple_size_v<_UTuple>)
-  _LIBCPP_HIDE_FROM_ABI friend constexpr __tuple_common_comparison_category_t<tuple, __to_tuple_t<_UTuple>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr typename __common_comparison_category_with<__to_tuple_t<_UTuple>>::type
   operator<=>(const tuple& __x, const _UTuple& __y) {
-    return std::__tuple_compare_three_way< __tuple_common_comparison_category_t<tuple, __to_tuple_t<_UTuple>>>(
+    return std::__tuple_compare_three_way<typename __common_comparison_category_with<__to_tuple_t<_UTuple>>::type>(
         __x, __y, index_sequence_for<_Tp...>{});
   }
 #    endif   // _LIBCPP_STD_VER >= 23

>From 138e41555ddd6b8d4ccce448fbea5ea0b94dfb0f Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 25 Jun 2026 06:36:32 -0400
Subject: [PATCH 3/5] Generalize the fix to operator==.

Also, add missing coverage for a static_assert diagnostic as a drive-by.
---
 libcxx/include/tuple                          | 21 +++++++---------
 .../size_incompatible_comparison.verify.cpp   | 25 +++++++++++++------
 2 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 1412627205bff..3785bb9961f31 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -295,17 +295,6 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __tuple_compare_equal(c
     return std::__tuple_compare_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, 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>
-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;
-#    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...>) {
@@ -993,9 +982,17 @@ public:
     __base_.swap(__t.__base_);
   }
 
+  template <class _Tuple>
+  static constexpr bool __can_compare_equal = false;
+
+  template <class... _Up>
+  static constexpr bool __can_compare_equal<tuple<_Up...>> = __all<requires(const _Tp& __t, const _Up& __u) {
+    { __t == __u } -> __boolean_testable;
+  }...>::value;
+
   template <__tuple_like_no_tuple _UTuple>
 #      if _LIBCPP_STD_VER >= 26
-    requires __can_tuple_compare_equal<tuple, _UTuple> && (sizeof...(_Tp) == tuple_size_v<_UTuple>)
+    requires(sizeof...(_Tp) == tuple_size_v<_UTuple>) && __can_compare_equal<__to_tuple_t<_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");
diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
index c05438f838f17..0af61c7e840aa 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
@@ -6,27 +6,36 @@
 //
 //===----------------------------------------------------------------------===//
 
-// Disabled in C++26 and later because tuple comparison between different sizes is constrained since P2944R3.
-// UNSUPPORTED: std-at-least-cxx26
-
 // <tuple>
 
 // template <class... Types> class tuple;
 
 // template<class... TTypes, class... UTypes>
-//   bool
-//   operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// bool operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+//
 // template<class... TTypes, class... UTypes>
-//   bool
-//   operator<(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
+// 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
+// <tuple> is not supported in C++03. In C++26 and later, tuple comparison between
+// different sizes is constrained (since P2944R3), so this test does not apply.
+// REQUIRES: c++11 || c++14 || c++17 || c++20 || c++23
 
+#include <array>
 #include <tuple>
 
+#include "test_macros.h"
+
 void f(std::tuple<int> t1, std::tuple<int, long> t2) {
   // We test only the core comparison operators and trust that the others
   // fall back on the same implementations prior to C++20.
   static_cast<void>(t1 == t2); // expected-error@*:* {{}}
   static_cast<void>(t1 < t2); // expected-error@*:* {{}}
+
+#if TEST_STD_VER >= 23
+  std::array<int, 2> a{}; // a tuple-like with a different size
+  static_cast<void>(t1 == a); // expected-error@*:* {{}}
+#endif
 }

>From ae53fd1fc38abe04632d7bc566804f9ad38ffc0a Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 25 Jun 2026 06:41:06 -0400
Subject: [PATCH 4/5] REQUIRES style

---
 .../tuple.rel/tuple_size_self_reference.compile.pass.cpp        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp
index 811d84f2da2f7..11621bf9d812b 100644
--- a/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.rel/tuple_size_self_reference.compile.pass.cpp
@@ -12,7 +12,7 @@
 // operator<=> (C++23) and operator== (C++26) once did, which instantiated user tuple_size
 // specializations against the still-incomplete tuple.
 
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// REQUIRES: std-at-least-c++23
 
 #include <tuple>
 

>From a8da2b05c4197e702e555a9b06bb5c581a8adc62 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 25 Jun 2026 06:44:02 -0400
Subject: [PATCH 5/5] Format test

---
 .../tuple.rel/size_incompatible_comparison.verify.cpp         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
index 0af61c7e840aa..612c5701a5223 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.rel/size_incompatible_comparison.verify.cpp
@@ -32,10 +32,10 @@ void f(std::tuple<int> t1, std::tuple<int, long> t2) {
   // We test only the core comparison operators and trust that the others
   // fall back on the same implementations prior to C++20.
   static_cast<void>(t1 == t2); // expected-error@*:* {{}}
-  static_cast<void>(t1 < t2); // expected-error@*:* {{}}
+  static_cast<void>(t1 < t2);  // expected-error@*:* {{}}
 
 #if TEST_STD_VER >= 23
-  std::array<int, 2> a{}; // a tuple-like with a different size
+  std::array<int, 2> a{};     // a tuple-like with a different size
   static_cast<void>(t1 == a); // expected-error@*:* {{}}
 #endif
 }



More information about the libcxx-commits mailing list