[libcxx-commits] [libcxx] [libc++] Fix instantiation of incomplete type when evaluating tuple's operator<=> (PR #204679)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Fri Jun 19 13:39:35 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/2] [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/2] 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



More information about the libcxx-commits mailing list