[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