public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20
@ 2024-01-23 23:53 Patrick Palka
  2024-01-23 23:53 ` [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc Patrick Palka
  2024-01-24  0:21 ` [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Jonathan Wakely
  0 siblings, 2 replies; 13+ messages in thread
From: Patrick Palka @ 2024-01-23 23:53 UTC (permalink / raw)
  To: gcc-patches; +Cc: libstdc++, Patrick Palka

This is consistent with std::tuple's __const_assignable member function,
and will be reused when implementing the new pair::operator= overloads
from P2165R4.

libstdc++-v3/ChangeLog:

	* include/bits/stl_pair.h (pair::_S_const_assignable): Define,
	factored out from ...
	(pair::operator=): ... the constraints of the const overloads.
---
 libstdc++-v3/include/bits/stl_pair.h | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index acd0c7b58f9..b81b479ad43 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -404,6 +404,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return false;
 	}
 
+      template<typename _U1, typename _U2>
+	static constexpr bool
+	_S_const_assignable()
+	{
+	  if constexpr (is_assignable_v<const _T1&, _U1>)
+	    return is_assignable_v<const _T2&, _U2>;
+	  return false;
+	}
+
       template<typename _U1, typename _U2>
 	static constexpr bool
 	_S_nothrow_assignable()
@@ -468,8 +477,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Copy assignment operator (const)
       constexpr const pair&
       operator=(const pair& __p) const
-      requires is_copy_assignable_v<const first_type>
-	&& is_copy_assignable_v<const second_type>
+      requires (_S_const_assignable<const first_type&, const second_type&>())
       {
 	first = __p.first;
 	second = __p.second;
@@ -479,8 +487,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Move assignment operator (const)
       constexpr const pair&
       operator=(pair&& __p) const
-      requires is_assignable_v<const first_type&, first_type>
-	&& is_assignable_v<const second_type&, second_type>
+      requires (_S_const_assignable<first_type, second_type>())
       {
 	first = std::forward<first_type>(__p.first);
 	second = std::forward<second_type>(__p.second);
@@ -491,8 +498,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _U1, typename _U2>
 	constexpr const pair&
 	operator=(const pair<_U1, _U2>& __p) const
-	requires is_assignable_v<const first_type&, const _U1&>
-	  && is_assignable_v<const second_type&, const _U2&>
+	requires (_S_const_assignable<const _U1&, const _U2&>())
 	{
 	  first = __p.first;
 	  second = __p.second;
@@ -503,8 +509,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _U1, typename _U2>
 	constexpr const pair&
 	operator=(pair<_U1, _U2>&& __p) const
-	requires is_assignable_v<const first_type&, _U1>
-	  && is_assignable_v<const second_type&, _U2>
+	requires (_S_const_assignable<_U1, _U2>())
 	{
 	  first = std::forward<_U1>(__p.first);
 	  second = std::forward<_U2>(__p.second);
-- 
2.43.0.386.ge02ecfcc53


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-23 23:53 [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Patrick Palka
@ 2024-01-23 23:53 ` Patrick Palka
  2024-01-24 12:01   ` Jonathan Wakely
  2024-01-24  0:21 ` [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Jonathan Wakely
  1 sibling, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-23 23:53 UTC (permalink / raw)
  To: gcc-patches; +Cc: libstdc++, Patrick Palka

Tested on x86_64-pc-linux-gnu, does this look OK for trunK?

-- >8 --

This implements the C++23 paper P2165R4 Compatibility between tuple,
pair and tuple-like objects, which builds upon many changes from the
earlier C++23 paper P2321R2 zip.

Some declarations had to be moved around so that they're visible from
<bits/stl_pair.h> without introducing new includes.  In the end, the
only new include is for <bits/utility.h> from <bits/stl_iterator.h>,
for tuple_element_t.

libstdc++-v3/ChangeLog:

	* include/bits/ranges_util.h (__detail::__pair_like): Don't
	define in C++23 mode.
	(__detail::__pair_like_convertible_from): Adjust as per P2165R4.
	(__detail::__is_subrange<subrange>): Moved from <ranges>.
	(__detail::__is_tuple_like_v<subrange>): Likewise.
	* include/bits/stl_iterator.h: Include <bits/utility.h> for
	C++23.
	(__different_from): Move to <concepts>.
	(__iter_key_t): Adjust for C++23 as per P2165R4.
	(__iter_val_t): Likewise.
	* include/bits/stl_pair.h (pair, array): Forward declare.
	(get): Forward declare all overloads relevant to P2165R4
	tuple-like constructors.
	(__is_tuple_v): Define for C++23.
	(__is_tuple_like_v): Define for C++23.
	(__tuple_like): Define for C++23 as per P2165R4.
	(__pair_like): Define for C++23 as per P2165R4.
	(__eligibile_tuple_like): Define for C++23.
	(__eligibile_pair_like): Define for C++23.
	(pair::_S_constructible_from_pair_like): Define for C++23.
	(pair::_S_convertible_from_pair_like): Define for C++23.
	(pair::pair): Define overload taking a tuple-like type for
	C++23 as per P2165R4.
	(pair::_S_assignable_from_tuple_like): Define for C++23.
	(pair::_S_const_assignable_from_tuple_like): Define for C++23.
	(pair::operator=): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	* include/bits/utility.h (ranges::__detail::__is_subrange):
	Moved from <ranges>.
	* include/bits/version.def (tuple_like): Define for C++23.
	* include/bits/version.h: Regenerate.
	* include/std/concepts (__different_from): Moved from
	<bits/stl_iterator.h>.
	(ranges::__swap::__adl_swap): Clarify which __detail namespace.
	* include/std/map (__cpp_lib_tuple_like): Define C++23.
	* include/std/ranges (__detail::__is_subrange): Moved to
	<bits/utility.h>.
	(__detail::__is_subrange<subrange>): Moved to <bits/ranges_util.h>
	(__detail::__has_tuple_element): Adjust for C++23 as per P2165R4.
	(__detail::__tuple_or_pair): Remove as per P2165R4.  Replace all
	uses with plain tuple as per P2165R4.
	* include/std/tuple (__cpp_lib_tuple_like): Define for C++23.
	(__tuple_like_tag_t): Define for C++23.
	(__tuple_cmp): Forward declare for C++23.
	(_Tuple_impl::_Tuple_impl): Define overloads taking
	__tuple_like_tag_t and a tuple-like type for C++23.
	(_Tuple_impl::_M_assign): Likewise.
	(tuple::__constructible_from_tuple_like): Define for C++23.
	(tuple::__convertible_from_tuple_like): Define for C++23.
	(tuple::tuple): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	(tuple::__assignable_from_tuple_like): Define for C++23.
	(tuple::__const_assignable_from_tuple_like): Define for C++23.
	(tuple::operator=): Define overloads taking a tuple-like type
	for C++23 as per P2165R4.
	(tuple::__tuple_like_common_comparison_category): Define for C++23.
	(tuple::operator<=>): Define overload taking a tuple-like type
	for C++23 as per P2165R4.
	(array, get): Forward declarations moved to <bits/stl_pair.h>.
	(tuple_cat): Constrain with __tuple_like for C++23 as per P2165R4.
	(apply): Likewise.
	(make_from_tuple): Likewise.
	(__tuple_like_common_reference): Define for C++23.
	(basic_common_reference): Adjust as per P2165R4.
	(__tuple_like_common_type): Define for C++23.
	(common_type): Adjust as per P2165R4.
	* include/std/unordered_map (__cpp_lib_tuple_like): Define for
	C++23.
	* include/std/utility (__cpp_lib_tuple_like): Define for C++23.
	* testsuite/std/ranges/zip/1.cc (test01): Adjust to handle pair
	and 2-tuple interchangeably.
	* testsuite/20_util/pair/p2165r4.cc: New test.
	* testsuite/20_util/tuple/p2165r4.cc: New test.
---
 libstdc++-v3/include/bits/ranges_util.h       |  17 +-
 libstdc++-v3/include/bits/stl_iterator.h      |  16 +-
 libstdc++-v3/include/bits/stl_pair.h          | 167 +++++++++
 libstdc++-v3/include/bits/utility.h           |   8 +
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  11 +
 libstdc++-v3/include/std/concepts             |  11 +-
 libstdc++-v3/include/std/map                  |   1 +
 libstdc++-v3/include/std/ranges               |  48 +--
 libstdc++-v3/include/std/tuple                | 299 +++++++++++++---
 libstdc++-v3/include/std/unordered_map        |   1 +
 libstdc++-v3/include/std/utility              |   1 +
 .../testsuite/20_util/pair/p2165r4.cc         | 169 ++++++++++
 .../testsuite/20_util/tuple/p2165r4.cc        | 319 ++++++++++++++++++
 libstdc++-v3/testsuite/std/ranges/zip/1.cc    |   4 +-
 15 files changed, 997 insertions(+), 83 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc

diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
index bb04c49f044..9b79c3a229d 100644
--- a/libstdc++-v3/include/bits/ranges_util.h
+++ b/libstdc++-v3/include/bits/ranges_util.h
@@ -224,6 +224,10 @@ namespace ranges
 	&& !__uses_nonqualification_pointer_conversion<decay_t<_From>,
 						       decay_t<_To>>;
 
+#if __glibcxx_tuple_like // >= C++23
+    // P2165R4 version of __pair_like is defined in <bits/stl_pair.h>.
+#else
+    // C++20 version of __pair_like from P2321R2.
     template<typename _Tp>
       concept __pair_like
 	= !is_reference_v<_Tp> && requires(_Tp __t)
@@ -235,10 +239,11 @@ namespace ranges
 	  { get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
 	  { get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
 	};
+#endif
 
     template<typename _Tp, typename _Up, typename _Vp>
       concept __pair_like_convertible_from
-	= !range<_Tp> && __pair_like<_Tp>
+	= !range<_Tp> && !is_reference_v<_Vp> && __pair_like<_Tp>
 	&& constructible_from<_Tp, _Up, _Vp>
 	&& __convertible_to_non_slicing<_Up, tuple_element_t<0, _Tp>>
 	&& convertible_to<_Vp, tuple_element_t<1, _Tp>>;
@@ -463,8 +468,18 @@ namespace ranges
     using borrowed_subrange_t = __conditional_t<borrowed_range<_Range>,
 						subrange<iterator_t<_Range>>,
 						dangling>;
+
+  // __is_subrange is defined in <bits/utility.h>.
+  template<typename _Iter, typename _Sent, subrange_kind _Kind>
+    inline constexpr bool __detail::__is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
 } // namespace ranges
 
+#if __glibcxx_tuple_like // >= C++23
+  // __is_tuple_like_v is defined in <bits/stl_pair.h>.
+  template<typename _It, typename _Sent, ranges::subrange_kind _Kind>
+    inline constexpr bool __is_tuple_like_v<ranges::subrange<_It, _Sent, _Kind>> = true;
+#endif
+
 // The following ranges algorithms are used by <ranges>, and are defined here
 // so that <ranges> can avoid including all of <bits/ranges_algo.h>.
 namespace ranges
diff --git a/libstdc++-v3/include/bits/stl_iterator.h b/libstdc++-v3/include/bits/stl_iterator.h
index d71a793e10d..560a10a7abe 100644
--- a/libstdc++-v3/include/bits/stl_iterator.h
+++ b/libstdc++-v3/include/bits/stl_iterator.h
@@ -78,6 +78,10 @@
 # include <bits/stl_construct.h>
 #endif
 
+#if __glibcxx_tuple_like // >= C++23
+# include <bits/utility.h> // for tuple_element_t
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -95,10 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     template<typename _Cat, typename _Limit, typename _Otherwise = _Cat>
       using __clamp_iter_cat
 	= __conditional_t<derived_from<_Cat, _Limit>, _Limit, _Otherwise>;
-
-    template<typename _Tp, typename _Up>
-      concept __different_from
-	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
   }
 #endif
 
@@ -2983,11 +2983,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // of associative containers.
   template<typename _InputIterator>
     using __iter_key_t = remove_const_t<
+#if __glibcxx_tuple_like // >= C++23
+      tuple_element_t<0, typename iterator_traits<_InputIterator>::value_type>>;
+#else
       typename iterator_traits<_InputIterator>::value_type::first_type>;
+#endif
 
   template<typename _InputIterator>
     using __iter_val_t
+#if __glibcxx_tuple_like // >= C++23
+      = tuple_element_t<1, typename iterator_traits<_InputIterator>::value_type>;
+#else
       = typename iterator_traits<_InputIterator>::value_type::second_type;
+#endif
 
   template<typename _T1, typename _T2>
     struct pair;
diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index b81b479ad43..a9b20fbe7ca 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @cond undocumented
 
   // Forward declarations.
+  template<typename, typename>
+    struct pair;
+
   template<typename...>
     class tuple;
 
+  // Declarations of std::array and its std::get overloads, so that
+  // std::tuple_cat can use them if <tuple> is included before <array>.
+  // We also declare the other std::get overloads here so that they're
+  // visible to the P2165R4 tuple-like constructors of pair and tuple.
+  template<typename _Tp, size_t _Nm>
+    struct array;
+
   template<size_t...>
     struct _Index_tuple;
 
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(const pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
+    get(tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
+    get(const tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(const tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&
+    get(array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&&
+    get(array<_Tp, _Nm>&&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&
+    get(const array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&&
+    get(const array<_Tp, _Nm>&&) noexcept;
+
 #if ! __cpp_lib_concepts
   // Concept utility functions, reused in conditionally-explicit
   // constructors.
@@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // lib concepts
 #endif // C++11
 
+#if __glibcxx_tuple_like // >= C++23
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_v = false;
+
+  template<typename... _Ts>
+    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
+
+  // TODO: Reuse __is_tuple_like from <type_traits>?
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_like_v = false;
+
+  template<typename... _Elements>
+    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
+
+  template<typename _T1, typename _T2>
+    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
+
+  template<typename _Tp, size_t _Nm>
+    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
+
+  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
+
+  template<typename _Tp>
+    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp>
+    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
+
+  template<typename _Tp, typename _Tuple>
+    concept __eligible_tuple_like
+      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
+	&& (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp, typename _Pair>
+    concept __eligible_pair_like
+      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+#endif // C++23
+
   template<typename _U1, typename _U2> class __pair_base
   {
 #if __cplusplus >= 201103L && ! __cpp_lib_concepts
@@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return false;
 #endif
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_constructible_from_pair_like()
+	{
+	  return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
+				  decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_convertible_from_pair_like()
+	{
+	  return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
+				decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
     public:
@@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	pair(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_constructible_from_pair_like<_UPair>())
+	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
+	pair(_UPair&& __p)
+	: first(std::get<0>(std::forward<_UPair>(__p))),
+	  second(std::get<1>(std::forward<_UPair>(__p)))
+	{ }
+#endif // C++23
+
   private:
       /// @cond undocumented
       template<typename _U1, typename _U2>
@@ -421,6 +547,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return is_nothrow_assignable_v<_T2&, _U2>;
 	  return false;
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_assignable_from_tuple_like()
+	{
+	  return _S_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+			       decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_const_assignable_from_tuple_like()
+	{
+	  return _S_const_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+				     decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
   public:
@@ -516,6 +660,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 #endif // C++23
+
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_assignable_from_tuple_like<_UPair>())
+	constexpr pair&
+	operator=(_UPair&& __p)
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_const_assignable_from_tuple_like<_UPair>())
+	constexpr const pair&
+	operator=(_UPair&& __p) const
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+#endif // C++23
+
 #else // !__cpp_lib_concepts
       // C++11/14/17 implementation using enable_if, partially constexpr.
 
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index d8a5fb960fe..2a741bf7000 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -266,6 +266,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #endif
 
+#if __glibcxx_ranges
+  namespace ranges::__detail
+  {
+    template<typename _Range>
+      inline constexpr bool __is_subrange = false;
+  } // namespace __detail
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 8fb8a2877ee..502961eb269 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1780,6 +1780,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = tuple_like;
+  values = {
+    v = 202207;
+    cxxmin = 23;
+  };
+};
+
 // Standard test specifications.
 stds[97] = ">= 199711L";
 stds[03] = ">= 199711L";
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9ba99deeda6..511030bde47 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2169,4 +2169,15 @@
 #endif /* !defined(__cpp_lib_generator) && defined(__glibcxx_want_generator) */
 #undef __glibcxx_want_generator
 
+// from version.def line 1774
+#if !defined(__cpp_lib_tuple_like)
+# if (__cplusplus >= 202100L)
+#  define __glibcxx_tuple_like 202207L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_tuple_like)
+#   define __cpp_lib_tuple_like 202207L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_tuple_like) && defined(__glibcxx_want_tuple_like) */
+#undef __glibcxx_want_tuple_like
+
 #undef __glibcxx_want_all
diff --git a/libstdc++-v3/include/std/concepts b/libstdc++-v3/include/std/concepts
index 66ed3714b25..4f3e059b051 100644
--- a/libstdc++-v3/include/std/concepts
+++ b/libstdc++-v3/include/std/concepts
@@ -62,6 +62,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     concept same_as
       = __detail::__same_as<_Tp, _Up> && __detail::__same_as<_Up, _Tp>;
 
+  namespace __detail
+  {
+    template<typename _Tp, typename _Up>
+      concept __different_from
+	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
+  } // namespace __detail
+
   /// [concept.derived], concept derived_from
   template<typename _Derived, typename _Base>
     concept derived_from = __is_base_of(_Base, _Derived)
@@ -185,8 +192,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       template<typename _Tp, typename _Up>
 	concept __adl_swap
-	  = (__detail::__class_or_enum<remove_reference_t<_Tp>>
-	    || __detail::__class_or_enum<remove_reference_t<_Up>>)
+	  = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
+	    || std::__detail::__class_or_enum<remove_reference_t<_Up>>)
 	  && requires(_Tp&& __t, _Up&& __u) {
 	    swap(static_cast<_Tp&&>(__t), static_cast<_Up&&>(__u));
 	  };
diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
index dcfd222d173..4a96e59a5bc 100644
--- a/libstdc++-v3/include/std/map
+++ b/libstdc++-v3/include/std/map
@@ -74,6 +74,7 @@
 #define __glibcxx_want_map_try_emplace
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index f2413badd9c..7d739852677 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -2389,11 +2389,7 @@ namespace views::__adaptor
 	inline constexpr bool __is_basic_string_view<basic_string_view<_CharT, _Traits>>
 	  = true;
 
-      template<typename _Range>
-	inline constexpr bool __is_subrange = false;
-
-      template<typename _Iter, typename _Sent, subrange_kind _Kind>
-	inline constexpr bool __is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
+      using ranges::__detail::__is_subrange;
 
       template<typename _Range>
 	inline constexpr bool __is_iota_view = false;
@@ -4166,6 +4162,10 @@ namespace views::__adaptor
 
   namespace __detail
   {
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _Tp, size_t _Nm>
+    concept __has_tuple_element = __tuple_like<_Tp> && _Nm < tuple_size_v<_Tp>;
+#else
     template<typename _Tp, size_t _Nm>
     concept __has_tuple_element = requires(_Tp __t)
       {
@@ -4175,6 +4175,7 @@ namespace views::__adaptor
 	{ std::get<_Nm>(__t) }
 	  -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
       };
+#endif
 
     template<typename _Tp, size_t _Nm>
       concept __returnable_element
@@ -4559,23 +4560,12 @@ namespace views::__adaptor
 	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
 	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
 
-    template<typename... _Ts>
-      struct __tuple_or_pair
-      { using type = std::tuple<_Ts...>; };
-
-    template<typename _Tp, typename _Up>
-      struct __tuple_or_pair<_Tp, _Up>
-      { using type = pair<_Tp, _Up>; };
-
-    template<typename... _Ts>
-      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
-
     template<typename _Fp, typename _Tuple>
       constexpr auto
       __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
       {
 	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
-	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
+	  return tuple<invoke_result_t<_Fp&, _Ts>...>
 	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
 	}, std::forward<_Tuple>(__tuple));
       }
@@ -4696,7 +4686,7 @@ namespace views::__adaptor
 #ifdef __clang__ // LLVM-61763 workaround
   public:
 #endif
-    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr explicit
     _Iterator(decltype(_M_current) __current)
@@ -4728,7 +4718,7 @@ namespace views::__adaptor
     // iterator_category defined in __zip_view_iter_cat
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
     using difference_type
       = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
 
@@ -4900,7 +4890,7 @@ namespace views::__adaptor
   template<bool _Const>
   class zip_view<_Vs...>::_Sentinel
   {
-    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
+    tuple<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
 
     constexpr explicit
     _Sentinel(decltype(_M_end) __end)
@@ -8325,8 +8315,7 @@ namespace views::__adaptor
 		    && __detail::__cartesian_product_is_common<_First, _Vs...>)
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<_First>,
-						 iterator_t<_Vs>...>;
+	using _Ret = tuple<iterator_t<_First>, iterator_t<_Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8342,8 +8331,7 @@ namespace views::__adaptor
     end() const requires __detail::__cartesian_product_is_common<const _First, const _Vs...>
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<const _First>,
-						 iterator_t<const _Vs>...>;
+	using _Ret = tuple<iterator_t<const _First>, iterator_t<const _Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8416,8 +8404,8 @@ namespace views::__adaptor
   {
     using _Parent = __maybe_const_t<_Const, cartesian_product_view>;
     _Parent* _M_parent = nullptr;
-    __detail::__tuple_or_pair_t<iterator_t<__maybe_const_t<_Const, _First>>,
-				iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__maybe_const_t<_Const, _First>>,
+	  iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr
     _Iterator(_Parent& __parent, decltype(_M_current) __current)
@@ -8444,11 +8432,11 @@ namespace views::__adaptor
     using iterator_category = input_iterator_tag;
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__maybe_const_t<_Const, _First>>,
-				    range_value_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__maybe_const_t<_Const, _First>>,
+	      range_value_t<__maybe_const_t<_Const, _Vs>>...>;
     using reference
-      = __detail::__tuple_or_pair_t<range_reference_t<__maybe_const_t<_Const, _First>>,
-				    range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_reference_t<__maybe_const_t<_Const, _First>>,
+	      range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
     using difference_type = decltype(cartesian_product_view::_S_difference_type());
 
     _Iterator() = default;
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index be92f1eb973..182f3cc5e6a 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -50,6 +50,7 @@
 #define __glibcxx_want_apply
 #define __glibcxx_want_make_from_tuple
 #define __glibcxx_want_ranges_zip
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Head _M_head_impl;
     };
 
+#if __cpp_lib_tuple_like // >= C++23
+  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
+
+  // Forward declared for use by the operator<=> overload for tuple-like types.
+  template<typename _Cat, typename _Tp, typename _Up>
+    constexpr _Cat
+    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
+
+  template<typename _Cat, typename _Tp, typename _Up,
+	   size_t _Idx0, size_t... _Idxs>
+    constexpr _Cat
+    __tuple_cmp(const _Tp& __t, const _Up& __u,
+		index_sequence<_Idx0, _Idxs...>);
+#endif // C++23
+
   /**
    * Contains the actual implementation of the @c tuple template, stored
    * as a recursive inheritance hierarchy from the first element (most
@@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename... _UElements>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _UHead>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 	}
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__constructible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(make_index_sequence<sizeof...(_Elements)>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__convertible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(make_index_sequence<sizeof...(_Elements)>{});
+	}
+#endif // C++23
+
     public:
       constexpr
       explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
@@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(_UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(_UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     std::forward<_UTuple>(__u),
+		     make_index_sequence<sizeof...(_Elements)>{})
+	{ }
 #endif // C++23
 
       // Allocator-extended constructors.
@@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<typename _Alloc, __tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     __tag, __a, std::forward<_UTuple>(__u),
+		     make_index_sequence<sizeof...(_Elements)>{})
+	{ }
 #endif // C++23
 
 #else // !(concepts && conditional_explicit)
@@ -1539,6 +1651,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(make_index_sequence<sizeof...(_Elements)>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__const_assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __const_assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(make_index_sequence<sizeof...(_Elements)>{});
+	}
+#endif // C++23
+
     public:
 
       tuple& operator=(const tuple& __u) = delete;
@@ -1661,14 +1793,59 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__assignable_from_tuple_like<_UTuple>())
 	constexpr tuple&
-	operator=(_UTuple&& __u);
+	operator=(_UTuple&& __u)
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
+
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__const_assignable_from_tuple_like<_UTuple>())
+	constexpr const tuple&
+	operator=(_UTuple&& __u) const
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
 
       template<__tuple_like _UTuple>
-	constexpr tuple&
-	operator=(_UTuple&& __u) const;
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr bool
+	operator==(const tuple& __t, const _UTuple& __u)
+	{
+	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
+	      "tuple objects can only be compared if they have equal sizes.");
+	  return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	    return (bool(std::get<_Is>(__t) == std::get<_Is>(__u))
+		    && ...);
+	  }(make_index_sequence<sizeof...(_Elements)>{});
+	}
+
+      template<__tuple_like _UTuple,
+	       typename = make_index_sequence<tuple_size_v<_UTuple>>>
+	struct __tuple_like_common_comparison_category;
+
+      template<__tuple_like _UTuple, size_t... _Is>
+	requires requires
+	  { typename void_t<__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>; }
+	struct __tuple_like_common_comparison_category<_UTuple, index_sequence<_Is...>>
+	{
+	  using type = common_comparison_category_t
+	    <__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>;
+	};
+
+      template<__tuple_like _UTuple>
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr typename __tuple_like_common_comparison_category<_UTuple>::type
+	operator<=>(const tuple& __t, const _UTuple& __u)
+	{
+	  using _Cat = typename __tuple_like_common_comparison_category<_UTuple>::type;
+	  return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Elements...>());
+	}
 #endif // C++23
 
 #else // ! (concepts && consteval)
@@ -2433,27 +2610,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     forward_as_tuple(_Elements&&... __args) noexcept
     { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }
 
-  // Declarations of std::array and its std::get overloads, so that
-  // std::tuple_cat can use them if <tuple> is included before <array>.
-
-  template<typename _Tp, size_t _Nm> struct array;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&
-    get(array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&&
-    get(array<_Tp, _Nm>&&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&
-    get(const array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&&
-    get(const array<_Tp, _Nm>&&) noexcept;
-
   /// @cond undocumented
   template<size_t, typename, typename, size_t>
     struct __make_tuple_impl;
@@ -2569,8 +2725,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @endcond
 
   /// Create a `tuple` containing all elements from multiple tuple-like objects
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like... _Tpls>
+#else
   template<typename... _Tpls, typename = typename
            enable_if<__and_<__is_tuple_like<_Tpls>...>::value>::type>
+#endif
     constexpr auto
     tuple_cat(_Tpls&&... __tpls)
     -> typename __tuple_cat_result<_Tpls...>::__type
@@ -2722,7 +2882,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
     }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Fn, __tuple_like _Tuple>
+#else
   template <typename _Fn, typename _Tuple>
+#endif
     constexpr decltype(auto)
     apply(_Fn&& __f, _Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_invocable, _Fn, _Tuple>)
@@ -2741,7 +2905,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     __make_from_tuple_impl(_Tuple&& __t, index_sequence<_Idx...>)
     { return _Tp(std::get<_Idx>(std::forward<_Tuple>(__t))...); }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Tp, __tuple_like _Tuple>
+#else
   template <typename _Tp, typename _Tuple>
+#endif
     constexpr _Tp
     make_from_tuple(_Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_constructible, _Tp, _Tuple>)
@@ -2759,17 +2927,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif
 
-#if __cpp_lib_ranges_zip // >= C++23
-  template<typename... _TTypes, typename... _UTypes,
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_reference;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   size_t... _Is>
+    requires requires
+      { typename tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>; }
+  struct __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual, index_sequence<_Is...>>
+  {
+    using type = tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
 	   template<typename> class _TQual, template<typename> class _UQual>
-    requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; }
-  struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual>
-  { using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; };
-
-  template<typename... _TTypes, typename... _UTypes>
-    requires requires { typename tuple<common_type_t<_TTypes, _UTypes>...>; }
-  struct common_type<tuple<_TTypes...>, tuple<_UTypes...>>
-  { using type = tuple<common_type_t<_TTypes, _UTypes>...>; };
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type; }
+  struct basic_common_reference<_TTuple, _UTuple, _TQual, _UQual>
+  {
+    using type = typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_type;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple, size_t... _Is>
+    requires requires
+      { typename tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				   tuple_element_t<_Is, _UTuple>>...>; }
+  struct __tuple_like_common_type<_TTuple, _UTuple, index_sequence<_Is...>>
+  {
+    using type = tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				     tuple_element_t<_Is, _UTuple>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple>
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_type<_TTuple, _UTuple>::type; }
+  struct common_type<_TTuple, _UTuple>
+  {
+    using type = typename __tuple_like_common_type<_TTuple, _UTuple>::type;
+  };
 #endif // C++23
 
   /// @}
diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/include/std/unordered_map
index efad0cef584..ea6129d6494 100644
--- a/libstdc++-v3/include/std/unordered_map
+++ b/libstdc++-v3/include/std/unordered_map
@@ -51,6 +51,7 @@
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
 #define __glibcxx_want_unordered_map_try_emplace
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
index f113d572e59..212513f6f48 100644
--- a/libstdc++-v3/include/std/utility
+++ b/libstdc++-v3/include/std/utility
@@ -92,6 +92,7 @@
 #define __glibcxx_want_tuple_element_t
 #define __glibcxx_want_tuples_by_type
 #define __glibcxx_want_unreachable
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
new file mode 100644
index 00000000000..e9b34a4366f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
@@ -0,0 +1,169 @@
+// Verify P2165R4 enhancements to std::pair.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+
+struct A { };
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(false) pair(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b = pair_like;
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(pair_like);
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::as_const(pair_like);
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(std::as_const(pair_like));
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(true) pair(UPair&&);
+
+  static_assert( !std::is_convertible_v<pair_like_t<A>, pair<B, B>> );
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b{pair_like};
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(pair_like)};
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::as_const(pair_like)};
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(std::as_const(pair_like))};
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr pair& operator=(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr const pair& operator=(UPair&&) const;
+
+  pair_like_t<A> pair_like;
+
+  const pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<typename T>
+using pair_like_array = array<T, 2>;
+
+template<typename T>
+using pair_like_tuple = tuple<T, T>;
+
+int
+main()
+{
+  static_assert( test01<pair_like_array>() );
+  static_assert( test02<pair_like_array>() );
+  static_assert( test03<pair_like_array>() );
+  static_assert( test04<pair_like_array>() );
+
+  static_assert( test01<pair_like_tuple>() );
+  static_assert( test02<pair_like_tuple>() );
+  static_assert( test03<pair_like_tuple>() );
+  static_assert( test04<pair_like_tuple>() );
+}
diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
new file mode 100644
index 00000000000..931dd8916c8
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
@@ -0,0 +1,319 @@
+// Verify P2165R4 enhancements to std::tuple.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <memory>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+using std::allocator;
+using std::allocator_arg_t;
+using std::allocator_arg;
+
+namespace alloc {
+  struct B01;
+  struct B02;
+}
+
+template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
+
+struct A { };
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(false) tuple(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b = tuple_like;
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::as_const(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(std::as_const(tuple_like));
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B01 {
+    int m;
+    B01(A&);
+    B01(A&&);
+    B01(const A&);
+    B01(const A&&);
+    constexpr B01(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    constexpr B01(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test01()
+  {
+    using B = B01;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(false) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+    return true;
+  }
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(true) tuple(UTuple&&);
+
+  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b{tuple_like};
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::as_const(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(std::as_const(tuple_like))};
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B02 {
+    int m;
+    explicit B02(A&);
+    explicit B02(A&&);
+    explicit B02(const A&);
+    explicit B02(const A&&);
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test02()
+  {
+    using B = B02;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(true) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+    return true;
+  }
+}
+
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr tuple& operator=(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr const tuple& operator=(UTuple&&) const;
+
+  tuple_like_t<A> tuple_like;
+
+  const tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test05()
+{
+  // template<tuple-like UTuple>
+  //   constexpr bool operator==(const tuple&, const UTuple&);
+
+  static_assert( tuple{1, 2, 3} == tuple_like_t{1, 2, 3} );
+  static_assert( tuple{1, 2, 4} != tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} == tuple{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} != tuple{1, 2, 4} );
+
+  // template<tuple-like UTuple>
+  //   constexpr bool operator<=>const tuple&, const UTuple&);
+
+  static_assert( (tuple{1, 2, 3} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple{1, 2, 4} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::greater );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 4}) == std::strong_ordering::less  );
+
+  static_assert( tuple{1, 2, 4} > tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} < tuple{1, 2, 4} );
+
+  // template<tuple-like TTuple, tuple-like UTuple, ...>
+  //   struct basic_common_reference<TTuple, UTuple, ...>;
+
+  static_assert( std::same_as<std::common_reference_t<tuple_like_t<int>,
+						      tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_reference_t<tuple<int, long, int>,
+						      tuple_like_t<int>>,
+			      tuple<int, long, int>> );
+
+  // template<tuple-like TTuple, tuple-like UTuple>
+  //   struct common_type<TTuple, UTuple>;
+
+  static_assert( std::same_as<std::common_type_t<tuple_like_t<const int&>,
+						 tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_type_t<tuple<int, long, int>,
+						 tuple_like_t<const int&>>,
+			      tuple<int, long, int>> );
+
+  return true;
+}
+
+template<typename T>
+using tuple_like_array = array<T, 3>;
+
+int
+main()
+{
+  static_assert( test01<tuple_like_array>() );
+  static_assert( alloc::test01<tuple_like_array>() );
+  static_assert( test02<tuple_like_array>() );
+  static_assert( alloc::test02<tuple_like_array>() );
+  static_assert( test03<tuple_like_array>() );
+  static_assert( test04<tuple_like_array>() );
+  static_assert( test05<tuple_like_array>() );
+}
diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
index b7717aed92c..672a8c356d9 100644
--- a/libstdc++-v3/testsuite/std/ranges/zip/1.cc
+++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
@@ -41,8 +41,8 @@ test01()
   VERIFY( i2 == z2.end() );
   VERIFY( ranges::size(z2) == 2 );
   VERIFY( ranges::size(std::as_const(z2)) == 2 );
-  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
-  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
+  VERIFY( std::get<0>(z2[0]) == 1 && std::get<1>(z2[0]) == 3 );
+  VERIFY( std::get<0>(z2[1]) == 2 && std::get<1>(z2[1]) == 4 );
   for (const auto [x, y] : z2)
     {
       VERIFY( y - x == 2 );
-- 
2.43.0.386.ge02ecfcc53


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20
  2024-01-23 23:53 [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Patrick Palka
  2024-01-23 23:53 ` [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc Patrick Palka
@ 2024-01-24  0:21 ` Jonathan Wakely
  1 sibling, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-24  0:21 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

[-- Attachment #1: Type: text/plain, Size: 3167 bytes --]

On Tue, 23 Jan 2024, 23:53 Patrick Palka, <ppalka@redhat.com> wrote:

> This is consistent with std::tuple's __const_assignable member function,
> and will be reused when implementing the new pair::operator= overloads
> from P2165R4.
>


OK



> libstdc++-v3/ChangeLog:
>
>         * include/bits/stl_pair.h (pair::_S_const_assignable): Define,
>         factored out from ...
>         (pair::operator=): ... the constraints of the const overloads.
> ---
>  libstdc++-v3/include/bits/stl_pair.h | 21 +++++++++++++--------
>  1 file changed, 13 insertions(+), 8 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/stl_pair.h
> b/libstdc++-v3/include/bits/stl_pair.h
> index acd0c7b58f9..b81b479ad43 100644
> --- a/libstdc++-v3/include/bits/stl_pair.h
> +++ b/libstdc++-v3/include/bits/stl_pair.h
> @@ -404,6 +404,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>           return false;
>         }
>
> +      template<typename _U1, typename _U2>
> +       static constexpr bool
> +       _S_const_assignable()
> +       {
> +         if constexpr (is_assignable_v<const _T1&, _U1>)
> +           return is_assignable_v<const _T2&, _U2>;
> +         return false;
> +       }
> +
>        template<typename _U1, typename _U2>
>         static constexpr bool
>         _S_nothrow_assignable()
> @@ -468,8 +477,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        /// Copy assignment operator (const)
>        constexpr const pair&
>        operator=(const pair& __p) const
> -      requires is_copy_assignable_v<const first_type>
> -       && is_copy_assignable_v<const second_type>
> +      requires (_S_const_assignable<const first_type&, const
> second_type&>())
>        {
>         first = __p.first;
>         second = __p.second;
> @@ -479,8 +487,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        /// Move assignment operator (const)
>        constexpr const pair&
>        operator=(pair&& __p) const
> -      requires is_assignable_v<const first_type&, first_type>
> -       && is_assignable_v<const second_type&, second_type>
> +      requires (_S_const_assignable<first_type, second_type>())
>        {
>         first = std::forward<first_type>(__p.first);
>         second = std::forward<second_type>(__p.second);
> @@ -491,8 +498,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        template<typename _U1, typename _U2>
>         constexpr const pair&
>         operator=(const pair<_U1, _U2>& __p) const
> -       requires is_assignable_v<const first_type&, const _U1&>
> -         && is_assignable_v<const second_type&, const _U2&>
> +       requires (_S_const_assignable<const _U1&, const _U2&>())
>         {
>           first = __p.first;
>           second = __p.second;
> @@ -503,8 +509,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        template<typename _U1, typename _U2>
>         constexpr const pair&
>         operator=(pair<_U1, _U2>&& __p) const
> -       requires is_assignable_v<const first_type&, _U1>
> -         && is_assignable_v<const second_type&, _U2>
> +       requires (_S_const_assignable<_U1, _U2>())
>         {
>           first = std::forward<_U1>(__p.first);
>           second = std::forward<_U2>(__p.second);
> --
> 2.43.0.386.ge02ecfcc53
>
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-23 23:53 ` [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc Patrick Palka
@ 2024-01-24 12:01   ` Jonathan Wakely
  2024-01-24 12:08     ` Jonathan Wakely
  2024-01-24 15:24     ` Patrick Palka
  0 siblings, 2 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-24 12:01 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> index b81b479ad43..a9b20fbe7ca 100644
> --- a/libstdc++-v3/include/bits/stl_pair.h
> +++ b/libstdc++-v3/include/bits/stl_pair.h
> @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    /// @cond undocumented
>
>    // Forward declarations.
> +  template<typename, typename>
> +    struct pair;

We have a compiler bug where a forward declaration without template
parameter names causes bad diagnostics later. The compiler seems to
try to use the parameter names from the first decl it sees, so we end
up with things like <template-argument-1-1> even when there's a name
available at the site of the actual error. So I think we should name
these _T1 and _T2 here.

> +
>    template<typename...>
>      class tuple;
>
> +  // Declarations of std::array and its std::get overloads, so that
> +  // std::tuple_cat can use them if <tuple> is included before <array>.
> +  // We also declare the other std::get overloads here so that they're
> +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> +  template<typename _Tp, size_t _Nm>
> +    struct array;
> +
>    template<size_t...>
>      struct _Index_tuple;
>
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> +    get(tuple<_Elements...>& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> +    get(const tuple<_Elements...>& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> +    get(tuple<_Elements...>&& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> +    get(const tuple<_Elements...>&& __t) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr _Tp&
> +    get(array<_Tp, _Nm>&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr _Tp&&
> +    get(array<_Tp, _Nm>&&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr const _Tp&
> +    get(const array<_Tp, _Nm>&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr const _Tp&&
> +    get(const array<_Tp, _Nm>&&) noexcept;
> +
>  #if ! __cpp_lib_concepts
>    // Concept utility functions, reused in conditionally-explicit
>    // constructors.
> @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif // lib concepts
>  #endif // C++11
>
> +#if __glibcxx_tuple_like // >= C++23
> +  template<typename _Tp>
> +    inline constexpr bool __is_tuple_v = false;
> +
> +  template<typename... _Ts>
> +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> +
> +  // TODO: Reuse __is_tuple_like from <type_traits>?
> +  template<typename _Tp>
> +    inline constexpr bool __is_tuple_like_v = false;
> +
> +  template<typename... _Elements>
> +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> +
> +  template<typename _T1, typename _T2>
> +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> +
> +  template<typename _Tp, size_t _Nm>
> +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> +
> +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> +
> +  template<typename _Tp>
> +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> +
> +  template<typename _Tp>
> +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> +
> +  template<typename _Tp, typename _Tuple>
> +    concept __eligible_tuple_like
> +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> +
> +  template<typename _Tp, typename _Pair>
> +    concept __eligible_pair_like
> +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> +#endif // C++23
> +
>    template<typename _U1, typename _U2> class __pair_base
>    {
>  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>           return false;
>  #endif
>         }
> +
> +#if __glibcxx_tuple_like // >= C++23
> +      template<typename _UPair>
> +       static constexpr bool
> +       _S_constructible_from_pair_like()
> +       {
> +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> +       }
> +
> +      template<typename _UPair>
> +       static constexpr bool
> +       _S_convertible_from_pair_like()
> +       {
> +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> +       }
> +#endif // C++23
>        /// @endcond
>
>      public:
> @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         pair(const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>
> +#if __glibcxx_tuple_like // >= C++23
> +      template<__eligible_pair_like<pair> _UPair>
> +       requires (_S_constructible_from_pair_like<_UPair>())
> +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> +       pair(_UPair&& __p)
> +       : first(std::get<0>(std::forward<_UPair>(__p))),
> +         second(std::get<1>(std::forward<_UPair>(__p)))
> +       { }
> +#endif // C++23

I think this needs to be constrained with !_S_dangles<...>() and we
need a deleted overload with the same constraints, except for
_S_dangles being true.

And that should be covered by a test.



> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> index be92f1eb973..182f3cc5e6a 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -50,6 +50,7 @@
>  #define __glibcxx_want_apply
>  #define __glibcxx_want_make_from_tuple
>  #define __glibcxx_want_ranges_zip
> +#define __glibcxx_want_tuple_like
>  #include <bits/version.h>
>
>  namespace std _GLIBCXX_VISIBILITY(default)
> @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        _Head _M_head_impl;
>      };
>
> +#if __cpp_lib_tuple_like // >= C++23
> +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> +
> +  // Forward declared for use by the operator<=> overload for tuple-like types.
> +  template<typename _Cat, typename _Tp, typename _Up>
> +    constexpr _Cat
> +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> +
> +  template<typename _Cat, typename _Tp, typename _Up,
> +          size_t _Idx0, size_t... _Idxs>
> +    constexpr _Cat
> +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> +               index_sequence<_Idx0, _Idxs...>);
> +#endif // C++23
> +
>    /**
>     * Contains the actual implementation of the @c tuple template, stored
>     * as a recursive inheritance hierarchy from the first element (most
> @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         { }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple, size_t... _Is>
> +       constexpr
> +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> +       { }
> +#endif // C++23
> +
>        template<typename _Alloc>
>         _GLIBCXX20_CONSTEXPR
>         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         { }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> +       constexpr
> +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> +                   _UTuple&& __u, index_sequence<_Is...>)
> +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> +       { }
> +#endif // C++23
> +
>        template<typename... _UElements>
>         _GLIBCXX20_CONSTEXPR
>         void
> @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +       constexpr void
> +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> +       {
> +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> +       }
> +
> +      template<typename _UTuple>
> +       constexpr void
> +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> +       {
> +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> +       }
> +#endif // C++23
> +
>      protected:
>        _GLIBCXX20_CONSTEXPR
>        void
> @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         { }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +       constexpr
> +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> +       { }
> +#endif // C++23
> +
>        template<typename _Alloc>
>         _GLIBCXX20_CONSTEXPR
>         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         { }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, typename _UTuple>
> +       constexpr
> +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> +                   _UTuple&& __u, index_sequence<0>)
> +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> +       { }
> +#endif // C++23
> +
>        template<typename _UHead>
>         _GLIBCXX20_CONSTEXPR
>         void
> @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         }
>  #endif // C++23
>
> +#if __cpp_lib_tuple_like // >= C++23
> +    template<typename _UTuple>
> +      constexpr void
> +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> +
> +    template<typename _UTuple>
> +      constexpr void
> +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> +#endif // C++23
> +
>      protected:
>        _GLIBCXX20_CONSTEXPR
>        void
> @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif
>         }
>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +       static consteval bool
> +       __constructible_from_tuple_like()
> +       {
> +         return []<size_t... _Is>(index_sequence<_Is...>) {
> +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +         }(make_index_sequence<sizeof...(_Elements)>{});
> +       }
> +
> +      template<typename _UTuple>
> +       static consteval bool
> +       __convertible_from_tuple_like()
> +       {
> +         return []<size_t... _Is>(index_sequence<_Is...>) {
> +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +         }(make_index_sequence<sizeof...(_Elements)>{});

These new functions can use index_sequence_for<_Elements...>{} here,
so you don't need the sizeof....
That applies several times below as well.

I think it's semantically identical, just a little shorter. I don't
know if there's any compilation speed benefit either way. Maybe
sizeof...(_Elements) is cheaper than expanding the pack into the
index_sequence_for alias template?


> +       }
> +#endif // C++23
> +
>      public:
>        constexpr
>        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         tuple(const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>
> -#if 0 && __cpp_lib_tuple_like // >= C++23
> -      template<__tuple_like _UTuple>
> -       constexpr explicit(...)
> -       tuple(_UTuple&& __u);
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<__eligible_tuple_like<tuple> _UTuple>
> +       requires (__constructible_from_tuple_like<_UTuple>())
> +         && (!__use_other_ctor<_UTuple>())
> +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> +       tuple(_UTuple&& __u)
> +       : _Inherited(__tuple_like_tag_t{},
> +                    std::forward<_UTuple>(__u),
> +                    make_index_sequence<sizeof...(_Elements)>{})
> +       { }
>  #endif // C++23
>
>        // Allocator-extended constructors.
> @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>
> -#if 0 && __cpp_lib_tuple_like // >= C++23
> -      template<typename _Alloc, __tuple_like _UTuple>
> -       constexpr explicit(...)
> -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> +       requires (__constructible_from_tuple_like<_UTuple>())
> +         && (!__use_other_ctor<_UTuple>())
> +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> +       : _Inherited(__tuple_like_tag_t{},
> +                    __tag, __a, std::forward<_UTuple>(__u),
> +                    make_index_sequence<sizeof...(_Elements)>{})
> +       { }
>  #endif // C++23

For some reason these two new constructors aren't deleted if they
create dangling refs. I don't know why.


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 12:01   ` Jonathan Wakely
@ 2024-01-24 12:08     ` Jonathan Wakely
  2024-01-24 15:24     ` Patrick Palka
  1 sibling, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-24 12:08 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Wed, 24 Jan 2024 at 12:01, Jonathan Wakely wrote:
>
> On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > index b81b479ad43..a9b20fbe7ca 100644
> > --- a/libstdc++-v3/include/bits/stl_pair.h
> > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    /// @cond undocumented
> >
> >    // Forward declarations.
> > +  template<typename, typename>
> > +    struct pair;
>
> We have a compiler bug where a forward declaration without template
> parameter names causes bad diagnostics later. The compiler seems to
> try to use the parameter names from the first decl it sees, so we end
> up with things like <template-argument-1-1> even when there's a name
> available at the site of the actual error. So I think we should name
> these _T1 and _T2 here.

The bug I'm thinking of is https://gcc.gnu.org/PR54948


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 12:01   ` Jonathan Wakely
  2024-01-24 12:08     ` Jonathan Wakely
@ 2024-01-24 15:24     ` Patrick Palka
  2024-01-24 17:27       ` Jonathan Wakely
  1 sibling, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-24 15:24 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, gcc-patches, libstdc++

On Wed, 24 Jan 2024, Jonathan Wakely wrote:

> On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > index b81b479ad43..a9b20fbe7ca 100644
> > --- a/libstdc++-v3/include/bits/stl_pair.h
> > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    /// @cond undocumented
> >
> >    // Forward declarations.
> > +  template<typename, typename>
> > +    struct pair;
> 
> We have a compiler bug where a forward declaration without template
> parameter names causes bad diagnostics later. The compiler seems to
> try to use the parameter names from the first decl it sees, so we end
> up with things like <template-argument-1-1> even when there's a name
> available at the site of the actual error. So I think we should name
> these _T1 and _T2 here.

Will fix.

> 
> > +
> >    template<typename...>
> >      class tuple;
> >
> > +  // Declarations of std::array and its std::get overloads, so that
> > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > +  // We also declare the other std::get overloads here so that they're
> > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > +  template<typename _Tp, size_t _Nm>
> > +    struct array;
> > +
> >    template<size_t...>
> >      struct _Index_tuple;
> >
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(const tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(const tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&
> > +    get(array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&&
> > +    get(array<_Tp, _Nm>&&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&
> > +    get(const array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&&
> > +    get(const array<_Tp, _Nm>&&) noexcept;
> > +
> >  #if ! __cpp_lib_concepts
> >    // Concept utility functions, reused in conditionally-explicit
> >    // constructors.
> > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif // lib concepts
> >  #endif // C++11
> >
> > +#if __glibcxx_tuple_like // >= C++23
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_v = false;
> > +
> > +  template<typename... _Ts>
> > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > +
> > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_like_v = false;
> > +
> > +  template<typename... _Elements>
> > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > +
> > +  template<typename _T1, typename _T2>
> > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > +
> > +  template<typename _Tp, size_t _Nm>
> > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > +
> > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > +
> > +  template<typename _Tp>
> > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp>
> > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > +
> > +  template<typename _Tp, typename _Tuple>
> > +    concept __eligible_tuple_like
> > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp, typename _Pair>
> > +    concept __eligible_pair_like
> > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +#endif // C++23
> > +
> >    template<typename _U1, typename _U2> class __pair_base
> >    {
> >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >           return false;
> >  #endif
> >         }
> > +
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<typename _UPair>
> > +       static constexpr bool
> > +       _S_constructible_from_pair_like()
> > +       {
> > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > +       }
> > +
> > +      template<typename _UPair>
> > +       static constexpr bool
> > +       _S_convertible_from_pair_like()
> > +       {
> > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > +       }
> > +#endif // C++23
> >        /// @endcond
> >
> >      public:
> > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         pair(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<__eligible_pair_like<pair> _UPair>
> > +       requires (_S_constructible_from_pair_like<_UPair>())
> > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > +       pair(_UPair&& __p)
> > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > +       { }
> > +#endif // C++23
> 
> I think this needs to be constrained with !_S_dangles<...>() and we
> need a deleted overload with the same constraints, except for
> _S_dangles being true.
> 
> And that should be covered by a test.

Oops, will fix.  Should the deleted overloads carry over the
conditionally explicit specifier?  I noticed pair's deleted overloads
do, but tuple's overloads don't.

> 
> 
> 
> > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > index be92f1eb973..182f3cc5e6a 100644
> > --- a/libstdc++-v3/include/std/tuple
> > +++ b/libstdc++-v3/include/std/tuple
> > @@ -50,6 +50,7 @@
> >  #define __glibcxx_want_apply
> >  #define __glibcxx_want_make_from_tuple
> >  #define __glibcxx_want_ranges_zip
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >
> >  namespace std _GLIBCXX_VISIBILITY(default)
> > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        _Head _M_head_impl;
> >      };
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > +
> > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > +  template<typename _Cat, typename _Tp, typename _Up>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > +
> > +  template<typename _Cat, typename _Tp, typename _Up,
> > +          size_t _Idx0, size_t... _Idxs>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > +               index_sequence<_Idx0, _Idxs...>);
> > +#endif // C++23
> > +
> >    /**
> >     * Contains the actual implementation of the @c tuple template, stored
> >     * as a recursive inheritance hierarchy from the first element (most
> > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple, size_t... _Is>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >         _GLIBCXX20_CONSTEXPR
> >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > +                   _UTuple&& __u, index_sequence<_Is...>)
> > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename... _UElements>
> >         _GLIBCXX20_CONSTEXPR
> >         void
> > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       constexpr void
> > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > +       {
> > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +       }
> > +
> > +      template<typename _UTuple>
> > +       constexpr void
> > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > +       {
> > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +       }
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >         _GLIBCXX20_CONSTEXPR
> >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > +                   _UTuple&& __u, index_sequence<0>)
> > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _UHead>
> >         _GLIBCXX20_CONSTEXPR
> >         void
> > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif
> >         }
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       static consteval bool
> > +       __constructible_from_tuple_like()
> > +       {
> > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > +       }
> > +
> > +      template<typename _UTuple>
> > +       static consteval bool
> > +       __convertible_from_tuple_like()
> > +       {
> > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +         }(make_index_sequence<sizeof...(_Elements)>{});
> 
> These new functions can use index_sequence_for<_Elements...>{} here,
> so you don't need the sizeof....
> That applies several times below as well.
> 
> I think it's semantically identical, just a little shorter. I don't
> know if there's any compilation speed benefit either way. Maybe
> sizeof...(_Elements) is cheaper than expanding the pack into the
> index_sequence_for alias template?
> 
> 
> > +       }
> > +#endif // C++23
> > +
> >      public:
> >        constexpr
> >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         tuple(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<__tuple_like _UTuple>
> > -       constexpr explicit(...)
> > -       tuple(_UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +       requires (__constructible_from_tuple_like<_UTuple>())
> > +         && (!__use_other_ctor<_UTuple>())
> > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +       tuple(_UTuple&& __u)
> > +       : _Inherited(__tuple_like_tag_t{},
> > +                    std::forward<_UTuple>(__u),
> > +                    make_index_sequence<sizeof...(_Elements)>{})
> > +       { }
> >  #endif // C++23
> >
> >        // Allocator-extended constructors.
> > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<typename _Alloc, __tuple_like _UTuple>
> > -       constexpr explicit(...)
> > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > +       requires (__constructible_from_tuple_like<_UTuple>())
> > +         && (!__use_other_ctor<_UTuple>())
> > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > +       : _Inherited(__tuple_like_tag_t{},
> > +                    __tag, __a, std::forward<_UTuple>(__u),
> > +                    make_index_sequence<sizeof...(_Elements)>{})
> > +       { }
> >  #endif // C++23
> 
> For some reason these two new constructors aren't deleted if they
> create dangling refs. I don't know why.

Hmm, seems like an oversight.  Shall we proactively implement them?


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 15:24     ` Patrick Palka
@ 2024-01-24 17:27       ` Jonathan Wakely
  2024-01-24 18:57         ` Patrick Palka
  2024-01-24 21:16         ` Jonathan Wakely
  0 siblings, 2 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-24 17:27 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
>
> On Wed, 24 Jan 2024, Jonathan Wakely wrote:
>
> > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > > index b81b479ad43..a9b20fbe7ca 100644
> > > --- a/libstdc++-v3/include/bits/stl_pair.h
> > > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >    /// @cond undocumented
> > >
> > >    // Forward declarations.
> > > +  template<typename, typename>
> > > +    struct pair;
> >
> > We have a compiler bug where a forward declaration without template
> > parameter names causes bad diagnostics later. The compiler seems to
> > try to use the parameter names from the first decl it sees, so we end
> > up with things like <template-argument-1-1> even when there's a name
> > available at the site of the actual error. So I think we should name
> > these _T1 and _T2 here.
>
> Will fix.
>
> >
> > > +
> > >    template<typename...>
> > >      class tuple;
> > >
> > > +  // Declarations of std::array and its std::get overloads, so that
> > > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > > +  // We also declare the other std::get overloads here so that they're
> > > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > > +  template<typename _Tp, size_t _Nm>
> > > +    struct array;
> > > +
> > >    template<size_t...>
> > >      struct _Index_tuple;
> > >
> > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > > +
> > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > > +
> > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > > +
> > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > > +
> > > +  template<size_t __i, typename... _Elements>
> > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > > +    get(tuple<_Elements...>& __t) noexcept;
> > > +
> > > +  template<size_t __i, typename... _Elements>
> > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > > +    get(const tuple<_Elements...>& __t) noexcept;
> > > +
> > > +  template<size_t __i, typename... _Elements>
> > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > > +    get(tuple<_Elements...>&& __t) noexcept;
> > > +
> > > +  template<size_t __i, typename... _Elements>
> > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > > +    get(const tuple<_Elements...>&& __t) noexcept;
> > > +
> > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > +    constexpr _Tp&
> > > +    get(array<_Tp, _Nm>&) noexcept;
> > > +
> > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > +    constexpr _Tp&&
> > > +    get(array<_Tp, _Nm>&&) noexcept;
> > > +
> > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > +    constexpr const _Tp&
> > > +    get(const array<_Tp, _Nm>&) noexcept;
> > > +
> > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > +    constexpr const _Tp&&
> > > +    get(const array<_Tp, _Nm>&&) noexcept;
> > > +
> > >  #if ! __cpp_lib_concepts
> > >    // Concept utility functions, reused in conditionally-explicit
> > >    // constructors.
> > > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >  #endif // lib concepts
> > >  #endif // C++11
> > >
> > > +#if __glibcxx_tuple_like // >= C++23
> > > +  template<typename _Tp>
> > > +    inline constexpr bool __is_tuple_v = false;
> > > +
> > > +  template<typename... _Ts>
> > > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > > +
> > > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > > +  template<typename _Tp>
> > > +    inline constexpr bool __is_tuple_like_v = false;
> > > +
> > > +  template<typename... _Elements>
> > > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > > +
> > > +  template<typename _T1, typename _T2>
> > > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > > +
> > > +  template<typename _Tp, size_t _Nm>
> > > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > > +
> > > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > > +
> > > +  template<typename _Tp>
> > > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > > +
> > > +  template<typename _Tp>
> > > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > > +
> > > +  template<typename _Tp, typename _Tuple>
> > > +    concept __eligible_tuple_like
> > > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > +
> > > +  template<typename _Tp, typename _Pair>
> > > +    concept __eligible_pair_like
> > > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > +#endif // C++23
> > > +
> > >    template<typename _U1, typename _U2> class __pair_base
> > >    {
> > >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >           return false;
> > >  #endif
> > >         }
> > > +
> > > +#if __glibcxx_tuple_like // >= C++23
> > > +      template<typename _UPair>
> > > +       static constexpr bool
> > > +       _S_constructible_from_pair_like()
> > > +       {
> > > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > > +       }
> > > +
> > > +      template<typename _UPair>
> > > +       static constexpr bool
> > > +       _S_convertible_from_pair_like()
> > > +       {
> > > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > > +       }
> > > +#endif // C++23
> > >        /// @endcond
> > >
> > >      public:
> > > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         pair(const pair<_U1, _U2>&&) = delete;
> > >  #endif // C++23
> > >
> > > +#if __glibcxx_tuple_like // >= C++23
> > > +      template<__eligible_pair_like<pair> _UPair>
> > > +       requires (_S_constructible_from_pair_like<_UPair>())
> > > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > > +       pair(_UPair&& __p)
> > > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > > +       { }
> > > +#endif // C++23
> >
> > I think this needs to be constrained with !_S_dangles<...>() and we
> > need a deleted overload with the same constraints, except for
> > _S_dangles being true.
> >
> > And that should be covered by a test.
>
> Oops, will fix.  Should the deleted overloads carry over the
> conditionally explicit specifier?  I noticed pair's deleted overloads
> do, but tuple's overloads don't.

Huh, oops. Does an explicit ctor participate in overload resolution
and then get checked if it's usable, or is it remove from overload
resolution earlier?
It looks like I decided the answer was the latter for pair and the
former for tuple.

>
> >
> >
> >
> > > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > > index be92f1eb973..182f3cc5e6a 100644
> > > --- a/libstdc++-v3/include/std/tuple
> > > +++ b/libstdc++-v3/include/std/tuple
> > > @@ -50,6 +50,7 @@
> > >  #define __glibcxx_want_apply
> > >  #define __glibcxx_want_make_from_tuple
> > >  #define __glibcxx_want_ranges_zip
> > > +#define __glibcxx_want_tuple_like
> > >  #include <bits/version.h>
> > >
> > >  namespace std _GLIBCXX_VISIBILITY(default)
> > > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >        _Head _M_head_impl;
> > >      };
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > > +
> > > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > > +  template<typename _Cat, typename _Tp, typename _Up>
> > > +    constexpr _Cat
> > > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > > +
> > > +  template<typename _Cat, typename _Tp, typename _Up,
> > > +          size_t _Idx0, size_t... _Idxs>
> > > +    constexpr _Cat
> > > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > > +               index_sequence<_Idx0, _Idxs...>);
> > > +#endif // C++23
> > > +
> > >    /**
> > >     * Contains the actual implementation of the @c tuple template, stored
> > >     * as a recursive inheritance hierarchy from the first element (most
> > > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         { }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _UTuple, size_t... _Is>
> > > +       constexpr
> > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > +       { }
> > > +#endif // C++23
> > > +
> > >        template<typename _Alloc>
> > >         _GLIBCXX20_CONSTEXPR
> > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         { }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > > +       constexpr
> > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > +                   _UTuple&& __u, index_sequence<_Is...>)
> > > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > +       { }
> > > +#endif // C++23
> > > +
> > >        template<typename... _UElements>
> > >         _GLIBCXX20_CONSTEXPR
> > >         void
> > > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _UTuple>
> > > +       constexpr void
> > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > > +       {
> > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > +       }
> > > +
> > > +      template<typename _UTuple>
> > > +       constexpr void
> > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > > +       {
> > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > +       }
> > > +#endif // C++23
> > > +
> > >      protected:
> > >        _GLIBCXX20_CONSTEXPR
> > >        void
> > > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         { }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _UTuple>
> > > +       constexpr
> > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > > +       { }
> > > +#endif // C++23
> > > +
> > >        template<typename _Alloc>
> > >         _GLIBCXX20_CONSTEXPR
> > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         { }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _Alloc, typename _UTuple>
> > > +       constexpr
> > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > +                   _UTuple&& __u, index_sequence<0>)
> > > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > > +       { }
> > > +#endif // C++23
> > > +
> > >        template<typename _UHead>
> > >         _GLIBCXX20_CONSTEXPR
> > >         void
> > > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         }
> > >  #endif // C++23
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +    template<typename _UTuple>
> > > +      constexpr void
> > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > +
> > > +    template<typename _UTuple>
> > > +      constexpr void
> > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > +#endif // C++23
> > > +
> > >      protected:
> > >        _GLIBCXX20_CONSTEXPR
> > >        void
> > > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >  #endif
> > >         }
> > >
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _UTuple>
> > > +       static consteval bool
> > > +       __constructible_from_tuple_like()
> > > +       {
> > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > +       }
> > > +
> > > +      template<typename _UTuple>
> > > +       static consteval bool
> > > +       __convertible_from_tuple_like()
> > > +       {
> > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> >
> > These new functions can use index_sequence_for<_Elements...>{} here,
> > so you don't need the sizeof....
> > That applies several times below as well.
> >
> > I think it's semantically identical, just a little shorter. I don't
> > know if there's any compilation speed benefit either way. Maybe
> > sizeof...(_Elements) is cheaper than expanding the pack into the
> > index_sequence_for alias template?
> >
> >
> > > +       }
> > > +#endif // C++23
> > > +
> > >      public:
> > >        constexpr
> > >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         tuple(const pair<_U1, _U2>&&) = delete;
> > >  #endif // C++23
> > >
> > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > -      template<__tuple_like _UTuple>
> > > -       constexpr explicit(...)
> > > -       tuple(_UTuple&& __u);
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > +         && (!__use_other_ctor<_UTuple>())
> > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > +       tuple(_UTuple&& __u)
> > > +       : _Inherited(__tuple_like_tag_t{},
> > > +                    std::forward<_UTuple>(__u),
> > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > +       { }
> > >  #endif // C++23
> > >
> > >        // Allocator-extended constructors.
> > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > >  #endif // C++23
> > >
> > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > -       constexpr explicit(...)
> > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > +#if __cpp_lib_tuple_like // >= C++23
> > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > +         && (!__use_other_ctor<_UTuple>())
> > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > +       : _Inherited(__tuple_like_tag_t{},
> > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > +       { }
> > >  #endif // C++23
> >
> > For some reason these two new constructors aren't deleted if they
> > create dangling refs. I don't know why.
>
> Hmm, seems like an oversight.  Shall we proactively implement them?

Yes, I think so. I can't see why we would want to permit a dangling
reference there.

e.g.
std::array<long, 1> a{};
std::tuple<const int&> t(a);


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 17:27       ` Jonathan Wakely
@ 2024-01-24 18:57         ` Patrick Palka
  2024-01-24 19:47           ` Patrick Palka
  2024-01-24 21:16         ` Jonathan Wakely
  1 sibling, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-24 18:57 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, gcc-patches, libstdc++

On Wed, 24 Jan 2024, Jonathan Wakely wrote:

> On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
> >
> > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> >
> > > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > > > index b81b479ad43..a9b20fbe7ca 100644
> > > > --- a/libstdc++-v3/include/bits/stl_pair.h
> > > > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > > > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >    /// @cond undocumented
> > > >
> > > >    // Forward declarations.
> > > > +  template<typename, typename>
> > > > +    struct pair;
> > >
> > > We have a compiler bug where a forward declaration without template
> > > parameter names causes bad diagnostics later. The compiler seems to
> > > try to use the parameter names from the first decl it sees, so we end
> > > up with things like <template-argument-1-1> even when there's a name
> > > available at the site of the actual error. So I think we should name
> > > these _T1 and _T2 here.
> >
> > Will fix.
> >
> > >
> > > > +
> > > >    template<typename...>
> > > >      class tuple;
> > > >
> > > > +  // Declarations of std::array and its std::get overloads, so that
> > > > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > > > +  // We also declare the other std::get overloads here so that they're
> > > > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > > > +  template<typename _Tp, size_t _Nm>
> > > > +    struct array;
> > > > +
> > > >    template<size_t...>
> > > >      struct _Index_tuple;
> > > >
> > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > > > +
> > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > +
> > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > > > +
> > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > +
> > > > +  template<size_t __i, typename... _Elements>
> > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > > > +    get(tuple<_Elements...>& __t) noexcept;
> > > > +
> > > > +  template<size_t __i, typename... _Elements>
> > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > > > +    get(const tuple<_Elements...>& __t) noexcept;
> > > > +
> > > > +  template<size_t __i, typename... _Elements>
> > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > +    get(tuple<_Elements...>&& __t) noexcept;
> > > > +
> > > > +  template<size_t __i, typename... _Elements>
> > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > +    get(const tuple<_Elements...>&& __t) noexcept;
> > > > +
> > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > +    constexpr _Tp&
> > > > +    get(array<_Tp, _Nm>&) noexcept;
> > > > +
> > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > +    constexpr _Tp&&
> > > > +    get(array<_Tp, _Nm>&&) noexcept;
> > > > +
> > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > +    constexpr const _Tp&
> > > > +    get(const array<_Tp, _Nm>&) noexcept;
> > > > +
> > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > +    constexpr const _Tp&&
> > > > +    get(const array<_Tp, _Nm>&&) noexcept;
> > > > +
> > > >  #if ! __cpp_lib_concepts
> > > >    // Concept utility functions, reused in conditionally-explicit
> > > >    // constructors.
> > > > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >  #endif // lib concepts
> > > >  #endif // C++11
> > > >
> > > > +#if __glibcxx_tuple_like // >= C++23
> > > > +  template<typename _Tp>
> > > > +    inline constexpr bool __is_tuple_v = false;
> > > > +
> > > > +  template<typename... _Ts>
> > > > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > > > +
> > > > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > > > +  template<typename _Tp>
> > > > +    inline constexpr bool __is_tuple_like_v = false;
> > > > +
> > > > +  template<typename... _Elements>
> > > > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > > > +
> > > > +  template<typename _T1, typename _T2>
> > > > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > > > +
> > > > +  template<typename _Tp, size_t _Nm>
> > > > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > > > +
> > > > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > > > +
> > > > +  template<typename _Tp>
> > > > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > > > +
> > > > +  template<typename _Tp>
> > > > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > > > +
> > > > +  template<typename _Tp, typename _Tuple>
> > > > +    concept __eligible_tuple_like
> > > > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > > > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > +
> > > > +  template<typename _Tp, typename _Pair>
> > > > +    concept __eligible_pair_like
> > > > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > +#endif // C++23
> > > > +
> > > >    template<typename _U1, typename _U2> class __pair_base
> > > >    {
> > > >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > > > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >           return false;
> > > >  #endif
> > > >         }
> > > > +
> > > > +#if __glibcxx_tuple_like // >= C++23
> > > > +      template<typename _UPair>
> > > > +       static constexpr bool
> > > > +       _S_constructible_from_pair_like()
> > > > +       {
> > > > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > +       }
> > > > +
> > > > +      template<typename _UPair>
> > > > +       static constexpr bool
> > > > +       _S_convertible_from_pair_like()
> > > > +       {
> > > > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > +       }
> > > > +#endif // C++23
> > > >        /// @endcond
> > > >
> > > >      public:
> > > > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         pair(const pair<_U1, _U2>&&) = delete;
> > > >  #endif // C++23
> > > >
> > > > +#if __glibcxx_tuple_like // >= C++23
> > > > +      template<__eligible_pair_like<pair> _UPair>
> > > > +       requires (_S_constructible_from_pair_like<_UPair>())
> > > > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > > > +       pair(_UPair&& __p)
> > > > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > > > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > > > +       { }
> > > > +#endif // C++23
> > >
> > > I think this needs to be constrained with !_S_dangles<...>() and we
> > > need a deleted overload with the same constraints, except for
> > > _S_dangles being true.
> > >
> > > And that should be covered by a test.
> >
> > Oops, will fix.  Should the deleted overloads carry over the
> > conditionally explicit specifier?  I noticed pair's deleted overloads
> > do, but tuple's overloads don't.
> 
> Huh, oops. Does an explicit ctor participate in overload resolution
> and then get checked if it's usable, or is it remove from overload
> resolution earlier?
> It looks like I decided the answer was the latter for pair and the
> former for tuple.

AFAICT if we know the explicitness of the ctor early (either because the
ctor is a non-template or it has a non-dependent explicit-spec), then we
remove it from the overload set early, in which case it'd be useful to
give the deleted ctor the right explicit-spec to avoid unecessary
constraint checking etc.

Otherwise, for ctor templates with a dependent explicit-spec such as
these tuple/pair ones, we must wait until after deduction to check
explicitness which means constraints are checked first.  And by then
we already know that __dangles is true, so we presumably want overload
resolution to fail regardless.  Whether that's due to selecting a
(viable) deleted non-explicit ctor (if we omit the explicit-spec) or due
to there being no viable non-explicit ctor (if we carry over the
explicit-spec) shouldn't make a difference, I think?

So it seems unnecessary to give these deleted overloads an
explicit-spec; it wouldn't be considered unless overload resolution
is destined to fail anyway.

I'm not totally confident about this assessment though so I'll
just carry over the explicit-spec for now.

> 
> >
> > >
> > >
> > >
> > > > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > > > index be92f1eb973..182f3cc5e6a 100644
> > > > --- a/libstdc++-v3/include/std/tuple
> > > > +++ b/libstdc++-v3/include/std/tuple
> > > > @@ -50,6 +50,7 @@
> > > >  #define __glibcxx_want_apply
> > > >  #define __glibcxx_want_make_from_tuple
> > > >  #define __glibcxx_want_ranges_zip
> > > > +#define __glibcxx_want_tuple_like
> > > >  #include <bits/version.h>
> > > >
> > > >  namespace std _GLIBCXX_VISIBILITY(default)
> > > > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >        _Head _M_head_impl;
> > > >      };
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > > > +
> > > > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > > > +  template<typename _Cat, typename _Tp, typename _Up>
> > > > +    constexpr _Cat
> > > > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > > > +
> > > > +  template<typename _Cat, typename _Tp, typename _Up,
> > > > +          size_t _Idx0, size_t... _Idxs>
> > > > +    constexpr _Cat
> > > > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > > > +               index_sequence<_Idx0, _Idxs...>);
> > > > +#endif // C++23
> > > > +
> > > >    /**
> > > >     * Contains the actual implementation of the @c tuple template, stored
> > > >     * as a recursive inheritance hierarchy from the first element (most
> > > > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         { }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _UTuple, size_t... _Is>
> > > > +       constexpr
> > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > > > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > +       { }
> > > > +#endif // C++23
> > > > +
> > > >        template<typename _Alloc>
> > > >         _GLIBCXX20_CONSTEXPR
> > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         { }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > > > +       constexpr
> > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > +                   _UTuple&& __u, index_sequence<_Is...>)
> > > > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > +       { }
> > > > +#endif // C++23
> > > > +
> > > >        template<typename... _UElements>
> > > >         _GLIBCXX20_CONSTEXPR
> > > >         void
> > > > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _UTuple>
> > > > +       constexpr void
> > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > > > +       {
> > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > +       }
> > > > +
> > > > +      template<typename _UTuple>
> > > > +       constexpr void
> > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > > > +       {
> > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > +       }
> > > > +#endif // C++23
> > > > +
> > > >      protected:
> > > >        _GLIBCXX20_CONSTEXPR
> > > >        void
> > > > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         { }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _UTuple>
> > > > +       constexpr
> > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > > > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > > > +       { }
> > > > +#endif // C++23
> > > > +
> > > >        template<typename _Alloc>
> > > >         _GLIBCXX20_CONSTEXPR
> > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         { }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _Alloc, typename _UTuple>
> > > > +       constexpr
> > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > +                   _UTuple&& __u, index_sequence<0>)
> > > > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > > > +       { }
> > > > +#endif // C++23
> > > > +
> > > >        template<typename _UHead>
> > > >         _GLIBCXX20_CONSTEXPR
> > > >         void
> > > > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         }
> > > >  #endif // C++23
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +    template<typename _UTuple>
> > > > +      constexpr void
> > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > +
> > > > +    template<typename _UTuple>
> > > > +      constexpr void
> > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > +#endif // C++23
> > > > +
> > > >      protected:
> > > >        _GLIBCXX20_CONSTEXPR
> > > >        void
> > > > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >  #endif
> > > >         }
> > > >
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _UTuple>
> > > > +       static consteval bool
> > > > +       __constructible_from_tuple_like()
> > > > +       {
> > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > +       }
> > > > +
> > > > +      template<typename _UTuple>
> > > > +       static consteval bool
> > > > +       __convertible_from_tuple_like()
> > > > +       {
> > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > >
> > > These new functions can use index_sequence_for<_Elements...>{} here,
> > > so you don't need the sizeof....
> > > That applies several times below as well.
> > >
> > > I think it's semantically identical, just a little shorter. I don't
> > > know if there's any compilation speed benefit either way. Maybe
> > > sizeof...(_Elements) is cheaper than expanding the pack into the
> > > index_sequence_for alias template?
> > >
> > >
> > > > +       }
> > > > +#endif // C++23
> > > > +
> > > >      public:
> > > >        constexpr
> > > >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         tuple(const pair<_U1, _U2>&&) = delete;
> > > >  #endif // C++23
> > > >
> > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > -      template<__tuple_like _UTuple>
> > > > -       constexpr explicit(...)
> > > > -       tuple(_UTuple&& __u);
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > +         && (!__use_other_ctor<_UTuple>())
> > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > +       tuple(_UTuple&& __u)
> > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > +                    std::forward<_UTuple>(__u),
> > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > +       { }
> > > >  #endif // C++23
> > > >
> > > >        // Allocator-extended constructors.
> > > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > > >  #endif // C++23
> > > >
> > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > > -       constexpr explicit(...)
> > > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > +         && (!__use_other_ctor<_UTuple>())
> > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > +       { }
> > > >  #endif // C++23
> > >
> > > For some reason these two new constructors aren't deleted if they
> > > create dangling refs. I don't know why.
> >
> > Hmm, seems like an oversight.  Shall we proactively implement them?
> 
> Yes, I think so. I can't see why we would want to permit a dangling
> reference there.
> 
> e.g.
> std::array<long, 1> a{};
> std::tuple<const int&> t(a);

Sounds good.

> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 18:57         ` Patrick Palka
@ 2024-01-24 19:47           ` Patrick Palka
  2024-01-31 19:39             ` Patrick Palka
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-24 19:47 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, gcc-patches, libstdc++

On Wed, 24 Jan 2024, Patrick Palka wrote:

> On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> 
> > On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
> > >
> > > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> > >
> > > > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > > > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > > > > index b81b479ad43..a9b20fbe7ca 100644
> > > > > --- a/libstdc++-v3/include/bits/stl_pair.h
> > > > > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > > > > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >    /// @cond undocumented
> > > > >
> > > > >    // Forward declarations.
> > > > > +  template<typename, typename>
> > > > > +    struct pair;
> > > >
> > > > We have a compiler bug where a forward declaration without template
> > > > parameter names causes bad diagnostics later. The compiler seems to
> > > > try to use the parameter names from the first decl it sees, so we end
> > > > up with things like <template-argument-1-1> even when there's a name
> > > > available at the site of the actual error. So I think we should name
> > > > these _T1 and _T2 here.
> > >
> > > Will fix.
> > >
> > > >
> > > > > +
> > > > >    template<typename...>
> > > > >      class tuple;
> > > > >
> > > > > +  // Declarations of std::array and its std::get overloads, so that
> > > > > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > > > > +  // We also declare the other std::get overloads here so that they're
> > > > > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > > > > +  template<typename _Tp, size_t _Nm>
> > > > > +    struct array;
> > > > > +
> > > > >    template<size_t...>
> > > > >      struct _Index_tuple;
> > > > >
> > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > +
> > > > > +  template<size_t __i, typename... _Elements>
> > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > +    get(tuple<_Elements...>& __t) noexcept;
> > > > > +
> > > > > +  template<size_t __i, typename... _Elements>
> > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > +    get(const tuple<_Elements...>& __t) noexcept;
> > > > > +
> > > > > +  template<size_t __i, typename... _Elements>
> > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > +    get(tuple<_Elements...>&& __t) noexcept;
> > > > > +
> > > > > +  template<size_t __i, typename... _Elements>
> > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > +    get(const tuple<_Elements...>&& __t) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > +    constexpr _Tp&
> > > > > +    get(array<_Tp, _Nm>&) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > +    constexpr _Tp&&
> > > > > +    get(array<_Tp, _Nm>&&) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > +    constexpr const _Tp&
> > > > > +    get(const array<_Tp, _Nm>&) noexcept;
> > > > > +
> > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > +    constexpr const _Tp&&
> > > > > +    get(const array<_Tp, _Nm>&&) noexcept;
> > > > > +
> > > > >  #if ! __cpp_lib_concepts
> > > > >    // Concept utility functions, reused in conditionally-explicit
> > > > >    // constructors.
> > > > > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >  #endif // lib concepts
> > > > >  #endif // C++11
> > > > >
> > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > +  template<typename _Tp>
> > > > > +    inline constexpr bool __is_tuple_v = false;
> > > > > +
> > > > > +  template<typename... _Ts>
> > > > > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > > > > +
> > > > > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > > > > +  template<typename _Tp>
> > > > > +    inline constexpr bool __is_tuple_like_v = false;
> > > > > +
> > > > > +  template<typename... _Elements>
> > > > > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > > > > +
> > > > > +  template<typename _T1, typename _T2>
> > > > > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > > > > +
> > > > > +  template<typename _Tp, size_t _Nm>
> > > > > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > > > > +
> > > > > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > > > > +
> > > > > +  template<typename _Tp>
> > > > > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > > > > +
> > > > > +  template<typename _Tp>
> > > > > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > > > > +
> > > > > +  template<typename _Tp, typename _Tuple>
> > > > > +    concept __eligible_tuple_like
> > > > > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > > > > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > +
> > > > > +  template<typename _Tp, typename _Pair>
> > > > > +    concept __eligible_pair_like
> > > > > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > +#endif // C++23
> > > > > +
> > > > >    template<typename _U1, typename _U2> class __pair_base
> > > > >    {
> > > > >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > > > > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >           return false;
> > > > >  #endif
> > > > >         }
> > > > > +
> > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > +      template<typename _UPair>
> > > > > +       static constexpr bool
> > > > > +       _S_constructible_from_pair_like()
> > > > > +       {
> > > > > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > +       }
> > > > > +
> > > > > +      template<typename _UPair>
> > > > > +       static constexpr bool
> > > > > +       _S_convertible_from_pair_like()
> > > > > +       {
> > > > > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > +       }
> > > > > +#endif // C++23
> > > > >        /// @endcond
> > > > >
> > > > >      public:
> > > > > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         pair(const pair<_U1, _U2>&&) = delete;
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > +      template<__eligible_pair_like<pair> _UPair>
> > > > > +       requires (_S_constructible_from_pair_like<_UPair>())
> > > > > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > > > > +       pair(_UPair&& __p)
> > > > > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > > > > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > > > > +       { }
> > > > > +#endif // C++23
> > > >
> > > > I think this needs to be constrained with !_S_dangles<...>() and we
> > > > need a deleted overload with the same constraints, except for
> > > > _S_dangles being true.
> > > >
> > > > And that should be covered by a test.
> > >
> > > Oops, will fix.  Should the deleted overloads carry over the
> > > conditionally explicit specifier?  I noticed pair's deleted overloads
> > > do, but tuple's overloads don't.
> > 
> > Huh, oops. Does an explicit ctor participate in overload resolution
> > and then get checked if it's usable, or is it remove from overload
> > resolution earlier?
> > It looks like I decided the answer was the latter for pair and the
> > former for tuple.
> 
> AFAICT if we know the explicitness of the ctor early (either because the
> ctor is a non-template or it has a non-dependent explicit-spec), then we
> remove it from the overload set early, in which case it'd be useful to
> give the deleted ctor the right explicit-spec to avoid unecessary
> constraint checking etc.
> 
> Otherwise, for ctor templates with a dependent explicit-spec such as
> these tuple/pair ones, we must wait until after deduction to check
> explicitness which means constraints are checked first.  And by then
> we already know that __dangles is true, so we presumably want overload
> resolution to fail regardless.  Whether that's due to selecting a
> (viable) deleted non-explicit ctor (if we omit the explicit-spec) or due
> to there being no viable non-explicit ctor (if we carry over the
> explicit-spec) shouldn't make a difference, I think?
> 
> So it seems unnecessary to give these deleted overloads an
> explicit-spec; it wouldn't be considered unless overload resolution
> is destined to fail anyway.
> 
> I'm not totally confident about this assessment though so I'll
> just carry over the explicit-spec for now.

I ended up hedging my bets and including the explicit-spec in the
deleted ctors of pair and omitting it in those of tuple, so that
we continue to be locally consistent.

> 
> > 
> > >
> > > >
> > > >
> > > >
> > > > > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > > > > index be92f1eb973..182f3cc5e6a 100644
> > > > > --- a/libstdc++-v3/include/std/tuple
> > > > > +++ b/libstdc++-v3/include/std/tuple
> > > > > @@ -50,6 +50,7 @@
> > > > >  #define __glibcxx_want_apply
> > > > >  #define __glibcxx_want_make_from_tuple
> > > > >  #define __glibcxx_want_ranges_zip
> > > > > +#define __glibcxx_want_tuple_like
> > > > >  #include <bits/version.h>
> > > > >
> > > > >  namespace std _GLIBCXX_VISIBILITY(default)
> > > > > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >        _Head _M_head_impl;
> > > > >      };
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > > > > +
> > > > > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > > > > +  template<typename _Cat, typename _Tp, typename _Up>
> > > > > +    constexpr _Cat
> > > > > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > > > > +
> > > > > +  template<typename _Cat, typename _Tp, typename _Up,
> > > > > +          size_t _Idx0, size_t... _Idxs>
> > > > > +    constexpr _Cat
> > > > > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > > > > +               index_sequence<_Idx0, _Idxs...>);
> > > > > +#endif // C++23
> > > > > +
> > > > >    /**
> > > > >     * Contains the actual implementation of the @c tuple template, stored
> > > > >     * as a recursive inheritance hierarchy from the first element (most
> > > > > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         { }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _UTuple, size_t... _Is>
> > > > > +       constexpr
> > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > > > > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > +       { }
> > > > > +#endif // C++23
> > > > > +
> > > > >        template<typename _Alloc>
> > > > >         _GLIBCXX20_CONSTEXPR
> > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         { }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > > > > +       constexpr
> > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > +                   _UTuple&& __u, index_sequence<_Is...>)
> > > > > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > +       { }
> > > > > +#endif // C++23
> > > > > +
> > > > >        template<typename... _UElements>
> > > > >         _GLIBCXX20_CONSTEXPR
> > > > >         void
> > > > > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _UTuple>
> > > > > +       constexpr void
> > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > > > > +       {
> > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > +       }
> > > > > +
> > > > > +      template<typename _UTuple>
> > > > > +       constexpr void
> > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > > > > +       {
> > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > +       }
> > > > > +#endif // C++23
> > > > > +
> > > > >      protected:
> > > > >        _GLIBCXX20_CONSTEXPR
> > > > >        void
> > > > > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         { }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _UTuple>
> > > > > +       constexpr
> > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > > > > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > > > > +       { }
> > > > > +#endif // C++23
> > > > > +
> > > > >        template<typename _Alloc>
> > > > >         _GLIBCXX20_CONSTEXPR
> > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         { }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _Alloc, typename _UTuple>
> > > > > +       constexpr
> > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > +                   _UTuple&& __u, index_sequence<0>)
> > > > > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > > > > +       { }
> > > > > +#endif // C++23
> > > > > +
> > > > >        template<typename _UHead>
> > > > >         _GLIBCXX20_CONSTEXPR
> > > > >         void
> > > > > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         }
> > > > >  #endif // C++23
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +    template<typename _UTuple>
> > > > > +      constexpr void
> > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > +
> > > > > +    template<typename _UTuple>
> > > > > +      constexpr void
> > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > +#endif // C++23
> > > > > +
> > > > >      protected:
> > > > >        _GLIBCXX20_CONSTEXPR
> > > > >        void
> > > > > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >  #endif
> > > > >         }
> > > > >
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _UTuple>
> > > > > +       static consteval bool
> > > > > +       __constructible_from_tuple_like()
> > > > > +       {
> > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > > +       }
> > > > > +
> > > > > +      template<typename _UTuple>
> > > > > +       static consteval bool
> > > > > +       __convertible_from_tuple_like()
> > > > > +       {
> > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > >
> > > > These new functions can use index_sequence_for<_Elements...>{} here,
> > > > so you don't need the sizeof....
> > > > That applies several times below as well.
> > > >
> > > > I think it's semantically identical, just a little shorter. I don't
> > > > know if there's any compilation speed benefit either way. Maybe
> > > > sizeof...(_Elements) is cheaper than expanding the pack into the
> > > > index_sequence_for alias template?

It probably a little cheaper to use make_index_sequence directly, but I
just didn't know about index_sequence_for :) Consider that changed.

> > > >
> > > >
> > > > > +       }
> > > > > +#endif // C++23
> > > > > +
> > > > >      public:
> > > > >        constexpr
> > > > >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > > > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         tuple(const pair<_U1, _U2>&&) = delete;
> > > > >  #endif // C++23
> > > > >
> > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > -      template<__tuple_like _UTuple>
> > > > > -       constexpr explicit(...)
> > > > > -       tuple(_UTuple&& __u);
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > +       tuple(_UTuple&& __u)
> > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > +                    std::forward<_UTuple>(__u),
> > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > +       { }
> > > > >  #endif // C++23
> > > > >
> > > > >        // Allocator-extended constructors.
> > > > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > > > >  #endif // C++23
> > > > >
> > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > > > -       constexpr explicit(...)
> > > > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > +       { }
> > > > >  #endif // C++23
> > > >
> > > > For some reason these two new constructors aren't deleted if they
> > > > create dangling refs. I don't know why.
> > >
> > > Hmm, seems like an oversight.  Shall we proactively implement them?
> > 
> > Yes, I think so. I can't see why we would want to permit a dangling
> > reference there.
> > 
> > e.g.
> > std::array<long, 1> a{};
> > std::tuple<const int&> t(a);
> 
> Sounds good.

In v2:

* Named the template parameters of the forward declaration of pair.
* Added dangling checks for the new tuple and pair constructors
  and corresponding tests.
* Replaced make_index_sequence with index_sequence_for where applicable.

-- >8 --

Subject: [PATCH 2/2] libstdc++: Implement P2165R4 changes to
 std::pair/tuple/etc

libstdc++-v3/ChangeLog:

	* include/bits/ranges_util.h (__detail::__pair_like): Don't
	define in C++23 mode.
	(__detail::__pair_like_convertible_from): Adjust as per P2165R4.
	(__detail::__is_subrange<subrange>): Moved from <ranges>.
	(__detail::__is_tuple_like_v<subrange>): Likewise.
	* include/bits/stl_iterator.h: Include <bits/utility.h> for
	C++23.
	(__different_from): Move to <concepts>.
	(__iter_key_t): Adjust for C++23 as per P2165R4.
	(__iter_val_t): Likewise.
	* include/bits/stl_pair.h (pair, array): Forward declare.
	(get): Forward declare all overloads relevant to P2165R4
	tuple-like constructors.
	(__is_tuple_v): Define for C++23.
	(__is_tuple_like_v): Define for C++23.
	(__tuple_like): Define for C++23 as per P2165R4.
	(__pair_like): Define for C++23 as per P2165R4.
	(__eligibile_tuple_like): Define for C++23.
	(__eligibile_pair_like): Define for C++23.
	(pair::_S_constructible_from_pair_like): Define for C++23.
	(pair::_S_convertible_from_pair_like): Define for C++23.
	(pair::_S_dangles_from_pair_like): Define for C++23.
	(pair::pair): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	(pair::_S_assignable_from_tuple_like): Define for C++23.
	(pair::_S_const_assignable_from_tuple_like): Define for C++23.
	(pair::operator=): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	* include/bits/utility.h (ranges::__detail::__is_subrange):
	Moved from <ranges>.
	* include/bits/version.def (tuple_like): Define for C++23.
	* include/bits/version.h: Regenerate.
	* include/std/concepts (__different_from): Moved from
	<bits/stl_iterator.h>.
	(ranges::__swap::__adl_swap): Clarify which __detail namespace.
	* include/std/map (__cpp_lib_tuple_like): Define C++23.
	* include/std/ranges (__detail::__is_subrange): Moved to
	<bits/utility.h>.
	(__detail::__is_subrange<subrange>): Moved to <bits/ranges_util.h>
	(__detail::__has_tuple_element): Adjust for C++23 as per P2165R4.
	(__detail::__tuple_or_pair): Remove as per P2165R4.  Replace all
	uses with plain tuple as per P2165R4.
	* include/std/tuple (__cpp_lib_tuple_like): Define for C++23.
	(__tuple_like_tag_t): Define for C++23.
	(__tuple_cmp): Forward declare for C++23.
	(_Tuple_impl::_Tuple_impl): Define overloads taking
	__tuple_like_tag_t and a tuple-like type for C++23.
	(_Tuple_impl::_M_assign): Likewise.
	(tuple::__constructible_from_tuple_like): Define for C++23.
	(tuple::__convertible_from_tuple_like): Define for C++23.
	(tuple::__dangles_from_tuple_like): Define for C++23.
	(tuple::tuple): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	(tuple::__assignable_from_tuple_like): Define for C++23.
	(tuple::__const_assignable_from_tuple_like): Define for C++23.
	(tuple::operator=): Define overloads taking a tuple-like type
	for C++23 as per P2165R4.
	(tuple::__tuple_like_common_comparison_category): Define for C++23.
	(tuple::operator<=>): Define overload taking a tuple-like type
	for C++23 as per P2165R4.
	(array, get): Forward declarations moved to <bits/stl_pair.h>.
	(tuple_cat): Constrain with __tuple_like for C++23 as per P2165R4.
	(apply): Likewise.
	(make_from_tuple): Likewise.
	(__tuple_like_common_reference): Define for C++23.
	(basic_common_reference): Adjust as per P2165R4.
	(__tuple_like_common_type): Define for C++23.
	(common_type): Adjust as per P2165R4.
	* include/std/unordered_map (__cpp_lib_tuple_like): Define for
	C++23.
	* include/std/utility (__cpp_lib_tuple_like): Define for C++23.
	* testsuite/std/ranges/zip/1.cc (test01): Adjust to handle pair
	and 2-tuple interchangeably.
	* testsuite/20_util/pair/p2165r4.cc: New test.
	* testsuite/20_util/tuple/p2165r4.cc: New test.
---
 libstdc++-v3/include/bits/ranges_util.h       |  17 +-
 libstdc++-v3/include/bits/stl_iterator.h      |  16 +-
 libstdc++-v3/include/bits/stl_pair.h          | 182 ++++++++++
 libstdc++-v3/include/bits/utility.h           |   8 +
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  11 +
 libstdc++-v3/include/std/concepts             |  11 +-
 libstdc++-v3/include/std/map                  |   1 +
 libstdc++-v3/include/std/ranges               |  48 +--
 libstdc++-v3/include/std/tuple                | 323 ++++++++++++++---
 libstdc++-v3/include/std/unordered_map        |   1 +
 libstdc++-v3/include/std/utility              |   1 +
 .../testsuite/20_util/pair/p2165r4.cc         | 173 +++++++++
 .../testsuite/20_util/tuple/p2165r4.cc        | 335 ++++++++++++++++++
 libstdc++-v3/testsuite/std/ranges/zip/1.cc    |   4 +-
 15 files changed, 1056 insertions(+), 83 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc

diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
index bb04c49f044..9b79c3a229d 100644
--- a/libstdc++-v3/include/bits/ranges_util.h
+++ b/libstdc++-v3/include/bits/ranges_util.h
@@ -224,6 +224,10 @@ namespace ranges
 	&& !__uses_nonqualification_pointer_conversion<decay_t<_From>,
 						       decay_t<_To>>;
 
+#if __glibcxx_tuple_like // >= C++23
+    // P2165R4 version of __pair_like is defined in <bits/stl_pair.h>.
+#else
+    // C++20 version of __pair_like from P2321R2.
     template<typename _Tp>
       concept __pair_like
 	= !is_reference_v<_Tp> && requires(_Tp __t)
@@ -235,10 +239,11 @@ namespace ranges
 	  { get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
 	  { get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
 	};
+#endif
 
     template<typename _Tp, typename _Up, typename _Vp>
       concept __pair_like_convertible_from
-	= !range<_Tp> && __pair_like<_Tp>
+	= !range<_Tp> && !is_reference_v<_Vp> && __pair_like<_Tp>
 	&& constructible_from<_Tp, _Up, _Vp>
 	&& __convertible_to_non_slicing<_Up, tuple_element_t<0, _Tp>>
 	&& convertible_to<_Vp, tuple_element_t<1, _Tp>>;
@@ -463,8 +468,18 @@ namespace ranges
     using borrowed_subrange_t = __conditional_t<borrowed_range<_Range>,
 						subrange<iterator_t<_Range>>,
 						dangling>;
+
+  // __is_subrange is defined in <bits/utility.h>.
+  template<typename _Iter, typename _Sent, subrange_kind _Kind>
+    inline constexpr bool __detail::__is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
 } // namespace ranges
 
+#if __glibcxx_tuple_like // >= C++23
+  // __is_tuple_like_v is defined in <bits/stl_pair.h>.
+  template<typename _It, typename _Sent, ranges::subrange_kind _Kind>
+    inline constexpr bool __is_tuple_like_v<ranges::subrange<_It, _Sent, _Kind>> = true;
+#endif
+
 // The following ranges algorithms are used by <ranges>, and are defined here
 // so that <ranges> can avoid including all of <bits/ranges_algo.h>.
 namespace ranges
diff --git a/libstdc++-v3/include/bits/stl_iterator.h b/libstdc++-v3/include/bits/stl_iterator.h
index d71a793e10d..560a10a7abe 100644
--- a/libstdc++-v3/include/bits/stl_iterator.h
+++ b/libstdc++-v3/include/bits/stl_iterator.h
@@ -78,6 +78,10 @@
 # include <bits/stl_construct.h>
 #endif
 
+#if __glibcxx_tuple_like // >= C++23
+# include <bits/utility.h> // for tuple_element_t
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -95,10 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     template<typename _Cat, typename _Limit, typename _Otherwise = _Cat>
       using __clamp_iter_cat
 	= __conditional_t<derived_from<_Cat, _Limit>, _Limit, _Otherwise>;
-
-    template<typename _Tp, typename _Up>
-      concept __different_from
-	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
   }
 #endif
 
@@ -2983,11 +2983,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // of associative containers.
   template<typename _InputIterator>
     using __iter_key_t = remove_const_t<
+#if __glibcxx_tuple_like // >= C++23
+      tuple_element_t<0, typename iterator_traits<_InputIterator>::value_type>>;
+#else
       typename iterator_traits<_InputIterator>::value_type::first_type>;
+#endif
 
   template<typename _InputIterator>
     using __iter_val_t
+#if __glibcxx_tuple_like // >= C++23
+      = tuple_element_t<1, typename iterator_traits<_InputIterator>::value_type>;
+#else
       = typename iterator_traits<_InputIterator>::value_type::second_type;
+#endif
 
   template<typename _T1, typename _T2>
     struct pair;
diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index b81b479ad43..00ec53ebc33 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @cond undocumented
 
   // Forward declarations.
+  template<typename _T1, typename _T2>
+    struct pair;
+
   template<typename...>
     class tuple;
 
+  // Declarations of std::array and its std::get overloads, so that
+  // std::tuple_cat can use them if <tuple> is included before <array>.
+  // We also declare the other std::get overloads here so that they're
+  // visible to the P2165R4 tuple-like constructors of pair and tuple.
+  template<typename _Tp, size_t _Nm>
+    struct array;
+
   template<size_t...>
     struct _Index_tuple;
 
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(const pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
+    get(tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
+    get(const tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(const tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&
+    get(array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&&
+    get(array<_Tp, _Nm>&&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&
+    get(const array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&&
+    get(const array<_Tp, _Nm>&&) noexcept;
+
 #if ! __cpp_lib_concepts
   // Concept utility functions, reused in conditionally-explicit
   // constructors.
@@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // lib concepts
 #endif // C++11
 
+#if __glibcxx_tuple_like // >= C++23
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_v = false;
+
+  template<typename... _Ts>
+    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
+
+  // TODO: Reuse __is_tuple_like from <type_traits>?
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_like_v = false;
+
+  template<typename... _Elements>
+    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
+
+  template<typename _T1, typename _T2>
+    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
+
+  template<typename _Tp, size_t _Nm>
+    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
+
+  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
+
+  template<typename _Tp>
+    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp>
+    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
+
+  template<typename _Tp, typename _Tuple>
+    concept __eligible_tuple_like
+      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
+	&& (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp, typename _Pair>
+    concept __eligible_pair_like
+      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+#endif // C++23
+
   template<typename _U1, typename _U2> class __pair_base
   {
 #if __cplusplus >= 201103L && ! __cpp_lib_concepts
@@ -295,6 +393,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return false;
 #endif
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_constructible_from_pair_like()
+	{
+	  return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
+				  decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_convertible_from_pair_like()
+	{
+	  return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
+				decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_dangles_from_pair_like()
+	{
+	  return _S_dangles<decltype(std::get<0>(std::declval<_UPair>())),
+			    decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
     public:
@@ -393,6 +517,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	pair(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_constructible_from_pair_like<_UPair>())
+	  && (!_S_dangles_from_pair_like<_UPair>())
+	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
+	pair(_UPair&& __p)
+	: first(std::get<0>(std::forward<_UPair>(__p))),
+	  second(std::get<1>(std::forward<_UPair>(__p)))
+	{ }
+
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_constructible_from_pair_like<_UPair>())
+	  && (_S_dangles_from_pair_like<_UPair>())
+	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
+	pair(_UPair&&) = delete;
+#endif // C++23
+
   private:
       /// @cond undocumented
       template<typename _U1, typename _U2>
@@ -421,6 +562,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return is_nothrow_assignable_v<_T2&, _U2>;
 	  return false;
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_assignable_from_tuple_like()
+	{
+	  return _S_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+			       decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_const_assignable_from_tuple_like()
+	{
+	  return _S_const_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+				     decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
   public:
@@ -516,6 +675,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 #endif // C++23
+
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_assignable_from_tuple_like<_UPair>())
+	constexpr pair&
+	operator=(_UPair&& __p)
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_const_assignable_from_tuple_like<_UPair>())
+	constexpr const pair&
+	operator=(_UPair&& __p) const
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+#endif // C++23
+
 #else // !__cpp_lib_concepts
       // C++11/14/17 implementation using enable_if, partially constexpr.
 
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index d8a5fb960fe..2a741bf7000 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -266,6 +266,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #endif
 
+#if __glibcxx_ranges
+  namespace ranges::__detail
+  {
+    template<typename _Range>
+      inline constexpr bool __is_subrange = false;
+  } // namespace __detail
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 8fb8a2877ee..502961eb269 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1780,6 +1780,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = tuple_like;
+  values = {
+    v = 202207;
+    cxxmin = 23;
+  };
+};
+
 // Standard test specifications.
 stds[97] = ">= 199711L";
 stds[03] = ">= 199711L";
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9ba99deeda6..511030bde47 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2169,4 +2169,15 @@
 #endif /* !defined(__cpp_lib_generator) && defined(__glibcxx_want_generator) */
 #undef __glibcxx_want_generator
 
+// from version.def line 1774
+#if !defined(__cpp_lib_tuple_like)
+# if (__cplusplus >= 202100L)
+#  define __glibcxx_tuple_like 202207L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_tuple_like)
+#   define __cpp_lib_tuple_like 202207L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_tuple_like) && defined(__glibcxx_want_tuple_like) */
+#undef __glibcxx_want_tuple_like
+
 #undef __glibcxx_want_all
diff --git a/libstdc++-v3/include/std/concepts b/libstdc++-v3/include/std/concepts
index 66ed3714b25..4f3e059b051 100644
--- a/libstdc++-v3/include/std/concepts
+++ b/libstdc++-v3/include/std/concepts
@@ -62,6 +62,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     concept same_as
       = __detail::__same_as<_Tp, _Up> && __detail::__same_as<_Up, _Tp>;
 
+  namespace __detail
+  {
+    template<typename _Tp, typename _Up>
+      concept __different_from
+	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
+  } // namespace __detail
+
   /// [concept.derived], concept derived_from
   template<typename _Derived, typename _Base>
     concept derived_from = __is_base_of(_Base, _Derived)
@@ -185,8 +192,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       template<typename _Tp, typename _Up>
 	concept __adl_swap
-	  = (__detail::__class_or_enum<remove_reference_t<_Tp>>
-	    || __detail::__class_or_enum<remove_reference_t<_Up>>)
+	  = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
+	    || std::__detail::__class_or_enum<remove_reference_t<_Up>>)
 	  && requires(_Tp&& __t, _Up&& __u) {
 	    swap(static_cast<_Tp&&>(__t), static_cast<_Up&&>(__u));
 	  };
diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
index dcfd222d173..4a96e59a5bc 100644
--- a/libstdc++-v3/include/std/map
+++ b/libstdc++-v3/include/std/map
@@ -74,6 +74,7 @@
 #define __glibcxx_want_map_try_emplace
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index f2413badd9c..7d739852677 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -2389,11 +2389,7 @@ namespace views::__adaptor
 	inline constexpr bool __is_basic_string_view<basic_string_view<_CharT, _Traits>>
 	  = true;
 
-      template<typename _Range>
-	inline constexpr bool __is_subrange = false;
-
-      template<typename _Iter, typename _Sent, subrange_kind _Kind>
-	inline constexpr bool __is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
+      using ranges::__detail::__is_subrange;
 
       template<typename _Range>
 	inline constexpr bool __is_iota_view = false;
@@ -4166,6 +4162,10 @@ namespace views::__adaptor
 
   namespace __detail
   {
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _Tp, size_t _Nm>
+    concept __has_tuple_element = __tuple_like<_Tp> && _Nm < tuple_size_v<_Tp>;
+#else
     template<typename _Tp, size_t _Nm>
     concept __has_tuple_element = requires(_Tp __t)
       {
@@ -4175,6 +4175,7 @@ namespace views::__adaptor
 	{ std::get<_Nm>(__t) }
 	  -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
       };
+#endif
 
     template<typename _Tp, size_t _Nm>
       concept __returnable_element
@@ -4559,23 +4560,12 @@ namespace views::__adaptor
 	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
 	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
 
-    template<typename... _Ts>
-      struct __tuple_or_pair
-      { using type = std::tuple<_Ts...>; };
-
-    template<typename _Tp, typename _Up>
-      struct __tuple_or_pair<_Tp, _Up>
-      { using type = pair<_Tp, _Up>; };
-
-    template<typename... _Ts>
-      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
-
     template<typename _Fp, typename _Tuple>
       constexpr auto
       __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
       {
 	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
-	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
+	  return tuple<invoke_result_t<_Fp&, _Ts>...>
 	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
 	}, std::forward<_Tuple>(__tuple));
       }
@@ -4696,7 +4686,7 @@ namespace views::__adaptor
 #ifdef __clang__ // LLVM-61763 workaround
   public:
 #endif
-    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr explicit
     _Iterator(decltype(_M_current) __current)
@@ -4728,7 +4718,7 @@ namespace views::__adaptor
     // iterator_category defined in __zip_view_iter_cat
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
     using difference_type
       = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
 
@@ -4900,7 +4890,7 @@ namespace views::__adaptor
   template<bool _Const>
   class zip_view<_Vs...>::_Sentinel
   {
-    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
+    tuple<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
 
     constexpr explicit
     _Sentinel(decltype(_M_end) __end)
@@ -8325,8 +8315,7 @@ namespace views::__adaptor
 		    && __detail::__cartesian_product_is_common<_First, _Vs...>)
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<_First>,
-						 iterator_t<_Vs>...>;
+	using _Ret = tuple<iterator_t<_First>, iterator_t<_Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8342,8 +8331,7 @@ namespace views::__adaptor
     end() const requires __detail::__cartesian_product_is_common<const _First, const _Vs...>
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<const _First>,
-						 iterator_t<const _Vs>...>;
+	using _Ret = tuple<iterator_t<const _First>, iterator_t<const _Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8416,8 +8404,8 @@ namespace views::__adaptor
   {
     using _Parent = __maybe_const_t<_Const, cartesian_product_view>;
     _Parent* _M_parent = nullptr;
-    __detail::__tuple_or_pair_t<iterator_t<__maybe_const_t<_Const, _First>>,
-				iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__maybe_const_t<_Const, _First>>,
+	  iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr
     _Iterator(_Parent& __parent, decltype(_M_current) __current)
@@ -8444,11 +8432,11 @@ namespace views::__adaptor
     using iterator_category = input_iterator_tag;
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__maybe_const_t<_Const, _First>>,
-				    range_value_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__maybe_const_t<_Const, _First>>,
+	      range_value_t<__maybe_const_t<_Const, _Vs>>...>;
     using reference
-      = __detail::__tuple_or_pair_t<range_reference_t<__maybe_const_t<_Const, _First>>,
-				    range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_reference_t<__maybe_const_t<_Const, _First>>,
+	      range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
     using difference_type = decltype(cartesian_product_view::_S_difference_type());
 
     _Iterator() = default;
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index be92f1eb973..ba364d6a4f8 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -50,6 +50,7 @@
 #define __glibcxx_want_apply
 #define __glibcxx_want_make_from_tuple
 #define __glibcxx_want_ranges_zip
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -246,6 +247,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Head _M_head_impl;
     };
 
+#if __cpp_lib_tuple_like // >= C++23
+  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
+
+  // These forward declarations are used by the operator<=> overload for
+  // tuple-like types.
+  template<typename _Cat, typename _Tp, typename _Up>
+    constexpr _Cat
+    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
+
+  template<typename _Cat, typename _Tp, typename _Up,
+	   size_t _Idx0, size_t... _Idxs>
+    constexpr _Cat
+    __tuple_cmp(const _Tp& __t, const _Up& __u,
+		index_sequence<_Idx0, _Idxs...>);
+#endif // C++23
+
   /**
    * Contains the actual implementation of the @c tuple template, stored
    * as a recursive inheritance hierarchy from the first element (most
@@ -342,6 +359,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -428,6 +453,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename... _UElements>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -470,6 +504,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -563,6 +615,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -633,6 +693,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _UHead>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -667,6 +736,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -846,6 +927,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 	}
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__dangles_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __dangles<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__constructible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__convertible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+#endif // C++23
+
     public:
       constexpr
       explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
@@ -1016,10 +1126,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(_UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (!__dangles_from_tuple_like<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(_UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     std::forward<_UTuple>(__u),
+		     index_sequence_for<_Elements...>{})
+	{ }
+
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (__dangles_from_tuple_like<_UTuple>())
+	tuple(_UTuple&&) = delete;
 #endif // C++23
 
       // Allocator-extended constructors.
@@ -1202,10 +1325,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<typename _Alloc, __tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (!__dangles_from_tuple_like<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     __tag, __a, std::forward<_UTuple>(__u),
+		     index_sequence_for<_Elements...>{})
+	{ }
+
+      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (__dangles_from_tuple_like<_UTuple>())
+	tuple(allocator_arg_t, const _Alloc&, _UTuple&&) = delete;
 #endif // C++23
 
 #else // !(concepts && conditional_explicit)
@@ -1539,6 +1675,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__const_assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __const_assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+#endif // C++23
+
     public:
 
       tuple& operator=(const tuple& __u) = delete;
@@ -1661,14 +1817,59 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__assignable_from_tuple_like<_UTuple>())
 	constexpr tuple&
-	operator=(_UTuple&& __u);
+	operator=(_UTuple&& __u)
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
+
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__const_assignable_from_tuple_like<_UTuple>())
+	constexpr const tuple&
+	operator=(_UTuple&& __u) const
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
 
       template<__tuple_like _UTuple>
-	constexpr tuple&
-	operator=(_UTuple&& __u) const;
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr bool
+	operator==(const tuple& __t, const _UTuple& __u)
+	{
+	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
+	      "tuple objects can only be compared if they have equal sizes.");
+	  return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	    return (bool(std::get<_Is>(__t) == std::get<_Is>(__u))
+		    && ...);
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<__tuple_like _UTuple,
+	       typename = make_index_sequence<tuple_size_v<_UTuple>>>
+	struct __tuple_like_common_comparison_category;
+
+      template<__tuple_like _UTuple, size_t... _Is>
+	requires requires
+	  { typename void_t<__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>; }
+	struct __tuple_like_common_comparison_category<_UTuple, index_sequence<_Is...>>
+	{
+	  using type = common_comparison_category_t
+	    <__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>;
+	};
+
+      template<__tuple_like _UTuple>
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr typename __tuple_like_common_comparison_category<_UTuple>::type
+	operator<=>(const tuple& __t, const _UTuple& __u)
+	{
+	  using _Cat = typename __tuple_like_common_comparison_category<_UTuple>::type;
+	  return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Elements...>());
+	}
 #endif // C++23
 
 #else // ! (concepts && consteval)
@@ -2433,27 +2634,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     forward_as_tuple(_Elements&&... __args) noexcept
     { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }
 
-  // Declarations of std::array and its std::get overloads, so that
-  // std::tuple_cat can use them if <tuple> is included before <array>.
-
-  template<typename _Tp, size_t _Nm> struct array;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&
-    get(array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&&
-    get(array<_Tp, _Nm>&&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&
-    get(const array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&&
-    get(const array<_Tp, _Nm>&&) noexcept;
-
   /// @cond undocumented
   template<size_t, typename, typename, size_t>
     struct __make_tuple_impl;
@@ -2569,8 +2749,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @endcond
 
   /// Create a `tuple` containing all elements from multiple tuple-like objects
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like... _Tpls>
+#else
   template<typename... _Tpls, typename = typename
            enable_if<__and_<__is_tuple_like<_Tpls>...>::value>::type>
+#endif
     constexpr auto
     tuple_cat(_Tpls&&... __tpls)
     -> typename __tuple_cat_result<_Tpls...>::__type
@@ -2722,7 +2906,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
     }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Fn, __tuple_like _Tuple>
+#else
   template <typename _Fn, typename _Tuple>
+#endif
     constexpr decltype(auto)
     apply(_Fn&& __f, _Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_invocable, _Fn, _Tuple>)
@@ -2741,7 +2929,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     __make_from_tuple_impl(_Tuple&& __t, index_sequence<_Idx...>)
     { return _Tp(std::get<_Idx>(std::forward<_Tuple>(__t))...); }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Tp, __tuple_like _Tuple>
+#else
   template <typename _Tp, typename _Tuple>
+#endif
     constexpr _Tp
     make_from_tuple(_Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_constructible, _Tp, _Tuple>)
@@ -2759,17 +2951,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif
 
-#if __cpp_lib_ranges_zip // >= C++23
-  template<typename... _TTypes, typename... _UTypes,
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_reference;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   size_t... _Is>
+    requires requires
+      { typename tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>; }
+  struct __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual, index_sequence<_Is...>>
+  {
+    using type = tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
 	   template<typename> class _TQual, template<typename> class _UQual>
-    requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; }
-  struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual>
-  { using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; };
-
-  template<typename... _TTypes, typename... _UTypes>
-    requires requires { typename tuple<common_type_t<_TTypes, _UTypes>...>; }
-  struct common_type<tuple<_TTypes...>, tuple<_UTypes...>>
-  { using type = tuple<common_type_t<_TTypes, _UTypes>...>; };
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type; }
+  struct basic_common_reference<_TTuple, _UTuple, _TQual, _UQual>
+  {
+    using type = typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_type;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple, size_t... _Is>
+    requires requires
+      { typename tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				   tuple_element_t<_Is, _UTuple>>...>; }
+  struct __tuple_like_common_type<_TTuple, _UTuple, index_sequence<_Is...>>
+  {
+    using type = tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				     tuple_element_t<_Is, _UTuple>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple>
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_type<_TTuple, _UTuple>::type; }
+  struct common_type<_TTuple, _UTuple>
+  {
+    using type = typename __tuple_like_common_type<_TTuple, _UTuple>::type;
+  };
 #endif // C++23
 
   /// @}
diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/include/std/unordered_map
index efad0cef584..ea6129d6494 100644
--- a/libstdc++-v3/include/std/unordered_map
+++ b/libstdc++-v3/include/std/unordered_map
@@ -51,6 +51,7 @@
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
 #define __glibcxx_want_unordered_map_try_emplace
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
index f113d572e59..212513f6f48 100644
--- a/libstdc++-v3/include/std/utility
+++ b/libstdc++-v3/include/std/utility
@@ -92,6 +92,7 @@
 #define __glibcxx_want_tuple_element_t
 #define __glibcxx_want_tuples_by_type
 #define __glibcxx_want_unreachable
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
new file mode 100644
index 00000000000..ef06df1c53f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
@@ -0,0 +1,173 @@
+// Verify P2165R4 enhancements to std::pair.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+
+struct A { };
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(false) pair(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b = pair_like;
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(pair_like);
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::as_const(pair_like);
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(std::as_const(pair_like));
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  // Verify dangling checks.
+  static_assert( !std::is_constructible_v<pair<const int&, int>, pair_like_t<long>> );
+  static_assert( !std::is_constructible_v<pair<int, const int&>, pair_like_t<long>> );
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(true) pair(UPair&&);
+
+  static_assert( !std::is_convertible_v<pair_like_t<A>, pair<B, B>> );
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b{pair_like};
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(pair_like)};
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::as_const(pair_like)};
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(std::as_const(pair_like))};
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr pair& operator=(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr const pair& operator=(UPair&&) const;
+
+  pair_like_t<A> pair_like;
+
+  const pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<typename T>
+using pair_like_array = array<T, 2>;
+
+template<typename T>
+using pair_like_tuple = tuple<T, T>;
+
+int
+main()
+{
+  static_assert( test01<pair_like_array>() );
+  static_assert( test02<pair_like_array>() );
+  static_assert( test03<pair_like_array>() );
+  static_assert( test04<pair_like_array>() );
+
+  static_assert( test01<pair_like_tuple>() );
+  static_assert( test02<pair_like_tuple>() );
+  static_assert( test03<pair_like_tuple>() );
+  static_assert( test04<pair_like_tuple>() );
+}
diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
new file mode 100644
index 00000000000..e2437c469b6
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
@@ -0,0 +1,335 @@
+// Verify P2165R4 enhancements to std::tuple.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <memory>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+using std::allocator;
+using std::allocator_arg_t;
+using std::allocator_arg;
+
+namespace alloc {
+  struct B01;
+  struct B02;
+}
+
+template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
+
+struct A { };
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(false) tuple(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b = tuple_like;
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::as_const(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(std::as_const(tuple_like));
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  // Verify dangling checks.
+  static_assert( !std::is_constructible_v<tuple<const int&, int, int>, tuple_like_t<long>> );
+  static_assert( !std::is_constructible_v<tuple<int, const int&, int>, tuple_like_t<long>> );
+  static_assert( !std::is_constructible_v<tuple<int, int, const int&>, tuple_like_t<long>> );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B01 {
+    int m;
+    B01(A&);
+    B01(A&&);
+    B01(const A&);
+    B01(const A&&);
+    constexpr B01(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    constexpr B01(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test01()
+  {
+    using B = B01;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(false) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+  // Verify dangling checks.
+    static_assert( !std::is_constructible_v<tuple<const int&, int, int>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+    static_assert( !std::is_constructible_v<tuple<int, const int&, int>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+    static_assert( !std::is_constructible_v<tuple<int, int, const int&>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+
+    return true;
+  }
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(true) tuple(UTuple&&);
+
+  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b{tuple_like};
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::as_const(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(std::as_const(tuple_like))};
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B02 {
+    int m;
+    explicit B02(A&);
+    explicit B02(A&&);
+    explicit B02(const A&);
+    explicit B02(const A&&);
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test02()
+  {
+    using B = B02;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(true) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+    static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+    return true;
+  }
+}
+
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr tuple& operator=(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr const tuple& operator=(UTuple&&) const;
+
+  tuple_like_t<A> tuple_like;
+
+  const tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test05()
+{
+  // template<tuple-like UTuple>
+  //   constexpr bool operator==(const tuple&, const UTuple&);
+
+  static_assert( tuple{1, 2, 3} == tuple_like_t{1, 2, 3} );
+  static_assert( tuple{1, 2, 4} != tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} == tuple{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} != tuple{1, 2, 4} );
+
+  // template<tuple-like UTuple>
+  //   constexpr bool operator<=>const tuple&, const UTuple&);
+
+  static_assert( (tuple{1, 2, 3} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple{1, 2, 4} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::greater );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 4}) == std::strong_ordering::less  );
+
+  static_assert( tuple{1, 2, 4} > tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} < tuple{1, 2, 4} );
+
+  // template<tuple-like TTuple, tuple-like UTuple, ...>
+  //   struct basic_common_reference<TTuple, UTuple, ...>;
+
+  static_assert( std::same_as<std::common_reference_t<tuple_like_t<int>,
+						      tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_reference_t<tuple<int, long, int>,
+						      tuple_like_t<int>>,
+			      tuple<int, long, int>> );
+
+  // template<tuple-like TTuple, tuple-like UTuple>
+  //   struct common_type<TTuple, UTuple>;
+
+  static_assert( std::same_as<std::common_type_t<tuple_like_t<const int&>,
+						 tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_type_t<tuple<int, long, int>,
+						 tuple_like_t<const int&>>,
+			      tuple<int, long, int>> );
+
+  return true;
+}
+
+template<typename T>
+using tuple_like_array = array<T, 3>;
+
+int
+main()
+{
+  static_assert( test01<tuple_like_array>() );
+  static_assert( alloc::test01<tuple_like_array>() );
+  static_assert( test02<tuple_like_array>() );
+  static_assert( alloc::test02<tuple_like_array>() );
+  static_assert( test03<tuple_like_array>() );
+  static_assert( test04<tuple_like_array>() );
+  static_assert( test05<tuple_like_array>() );
+}
diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
index b7717aed92c..672a8c356d9 100644
--- a/libstdc++-v3/testsuite/std/ranges/zip/1.cc
+++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
@@ -41,8 +41,8 @@ test01()
   VERIFY( i2 == z2.end() );
   VERIFY( ranges::size(z2) == 2 );
   VERIFY( ranges::size(std::as_const(z2)) == 2 );
-  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
-  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
+  VERIFY( std::get<0>(z2[0]) == 1 && std::get<1>(z2[0]) == 3 );
+  VERIFY( std::get<0>(z2[1]) == 2 && std::get<1>(z2[1]) == 4 );
   for (const auto [x, y] : z2)
     {
       VERIFY( y - x == 2 );
-- 
2.43.0.386.ge02ecfcc53


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 17:27       ` Jonathan Wakely
  2024-01-24 18:57         ` Patrick Palka
@ 2024-01-24 21:16         ` Jonathan Wakely
  1 sibling, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-24 21:16 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Wed, 24 Jan 2024 at 17:27, Jonathan Wakely <jwakely@redhat.com> wrote:
>
> On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
> >
> > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> >
> > > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         tuple(const pair<_U1, _U2>&&) = delete;
> > > >  #endif // C++23
> > > >
> > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > -      template<__tuple_like _UTuple>
> > > > -       constexpr explicit(...)
> > > > -       tuple(_UTuple&& __u);
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > +         && (!__use_other_ctor<_UTuple>())
> > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > +       tuple(_UTuple&& __u)
> > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > +                    std::forward<_UTuple>(__u),
> > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > +       { }
> > > >  #endif // C++23
> > > >
> > > >        // Allocator-extended constructors.
> > > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > > >  #endif // C++23
> > > >
> > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > > -       constexpr explicit(...)
> > > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > +         && (!__use_other_ctor<_UTuple>())
> > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > +       { }
> > > >  #endif // C++23
> > >
> > > For some reason these two new constructors aren't deleted if they
> > > create dangling refs. I don't know why.
> >
> > Hmm, seems like an oversight.  Shall we proactively implement them?
>
> Yes, I think so. I can't see why we would want to permit a dangling
> reference there.
>
> e.g.
> std::array<long, 1> a{};
> std::tuple<const int&> t(a);

This is now https://cplusplus.github.io/LWG/issue4045


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-24 19:47           ` Patrick Palka
@ 2024-01-31 19:39             ` Patrick Palka
  2024-01-31 19:41               ` Patrick Palka
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-31 19:39 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, gcc-patches, libstdc++

On Wed, 24 Jan 2024, Patrick Palka wrote:

> On Wed, 24 Jan 2024, Patrick Palka wrote:
> 
> > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> > 
> > > On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
> > > >
> > > > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> > > >
> > > > > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > > > > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > > > > > index b81b479ad43..a9b20fbe7ca 100644
> > > > > > --- a/libstdc++-v3/include/bits/stl_pair.h
> > > > > > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > > > > > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >    /// @cond undocumented
> > > > > >
> > > > > >    // Forward declarations.
> > > > > > +  template<typename, typename>
> > > > > > +    struct pair;
> > > > >
> > > > > We have a compiler bug where a forward declaration without template
> > > > > parameter names causes bad diagnostics later. The compiler seems to
> > > > > try to use the parameter names from the first decl it sees, so we end
> > > > > up with things like <template-argument-1-1> even when there's a name
> > > > > available at the site of the actual error. So I think we should name
> > > > > these _T1 and _T2 here.
> > > >
> > > > Will fix.
> > > >
> > > > >
> > > > > > +
> > > > > >    template<typename...>
> > > > > >      class tuple;
> > > > > >
> > > > > > +  // Declarations of std::array and its std::get overloads, so that
> > > > > > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > > > > > +  // We also declare the other std::get overloads here so that they're
> > > > > > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > > > > > +  template<typename _Tp, size_t _Nm>
> > > > > > +    struct array;
> > > > > > +
> > > > > >    template<size_t...>
> > > > > >      struct _Index_tuple;
> > > > > >
> > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > > +
> > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > > +    get(tuple<_Elements...>& __t) noexcept;
> > > > > > +
> > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > > +    get(const tuple<_Elements...>& __t) noexcept;
> > > > > > +
> > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > > +    get(tuple<_Elements...>&& __t) noexcept;
> > > > > > +
> > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > > +    get(const tuple<_Elements...>&& __t) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > +    constexpr _Tp&
> > > > > > +    get(array<_Tp, _Nm>&) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > +    constexpr _Tp&&
> > > > > > +    get(array<_Tp, _Nm>&&) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > +    constexpr const _Tp&
> > > > > > +    get(const array<_Tp, _Nm>&) noexcept;
> > > > > > +
> > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > +    constexpr const _Tp&&
> > > > > > +    get(const array<_Tp, _Nm>&&) noexcept;
> > > > > > +
> > > > > >  #if ! __cpp_lib_concepts
> > > > > >    // Concept utility functions, reused in conditionally-explicit
> > > > > >    // constructors.
> > > > > > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >  #endif // lib concepts
> > > > > >  #endif // C++11
> > > > > >
> > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > +  template<typename _Tp>
> > > > > > +    inline constexpr bool __is_tuple_v = false;
> > > > > > +
> > > > > > +  template<typename... _Ts>
> > > > > > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > > > > > +
> > > > > > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > > > > > +  template<typename _Tp>
> > > > > > +    inline constexpr bool __is_tuple_like_v = false;
> > > > > > +
> > > > > > +  template<typename... _Elements>
> > > > > > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > > > > > +
> > > > > > +  template<typename _T1, typename _T2>
> > > > > > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > > > > > +
> > > > > > +  template<typename _Tp, size_t _Nm>
> > > > > > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > > > > > +
> > > > > > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > > > > > +
> > > > > > +  template<typename _Tp>
> > > > > > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > > > > > +
> > > > > > +  template<typename _Tp>
> > > > > > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > > > > > +
> > > > > > +  template<typename _Tp, typename _Tuple>
> > > > > > +    concept __eligible_tuple_like
> > > > > > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > > > > > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > > +
> > > > > > +  template<typename _Tp, typename _Pair>
> > > > > > +    concept __eligible_pair_like
> > > > > > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >    template<typename _U1, typename _U2> class __pair_base
> > > > > >    {
> > > > > >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > > > > > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >           return false;
> > > > > >  #endif
> > > > > >         }
> > > > > > +
> > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > +      template<typename _UPair>
> > > > > > +       static constexpr bool
> > > > > > +       _S_constructible_from_pair_like()
> > > > > > +       {
> > > > > > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > > +       }
> > > > > > +
> > > > > > +      template<typename _UPair>
> > > > > > +       static constexpr bool
> > > > > > +       _S_convertible_from_pair_like()
> > > > > > +       {
> > > > > > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > > +       }
> > > > > > +#endif // C++23
> > > > > >        /// @endcond
> > > > > >
> > > > > >      public:
> > > > > > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         pair(const pair<_U1, _U2>&&) = delete;
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > +      template<__eligible_pair_like<pair> _UPair>
> > > > > > +       requires (_S_constructible_from_pair_like<_UPair>())
> > > > > > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > > > > > +       pair(_UPair&& __p)
> > > > > > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > > > > > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > > > > > +       { }
> > > > > > +#endif // C++23
> > > > >
> > > > > I think this needs to be constrained with !_S_dangles<...>() and we
> > > > > need a deleted overload with the same constraints, except for
> > > > > _S_dangles being true.
> > > > >
> > > > > And that should be covered by a test.
> > > >
> > > > Oops, will fix.  Should the deleted overloads carry over the
> > > > conditionally explicit specifier?  I noticed pair's deleted overloads
> > > > do, but tuple's overloads don't.
> > > 
> > > Huh, oops. Does an explicit ctor participate in overload resolution
> > > and then get checked if it's usable, or is it remove from overload
> > > resolution earlier?
> > > It looks like I decided the answer was the latter for pair and the
> > > former for tuple.
> > 
> > AFAICT if we know the explicitness of the ctor early (either because the
> > ctor is a non-template or it has a non-dependent explicit-spec), then we
> > remove it from the overload set early, in which case it'd be useful to
> > give the deleted ctor the right explicit-spec to avoid unecessary
> > constraint checking etc.
> > 
> > Otherwise, for ctor templates with a dependent explicit-spec such as
> > these tuple/pair ones, we must wait until after deduction to check
> > explicitness which means constraints are checked first.  And by then
> > we already know that __dangles is true, so we presumably want overload
> > resolution to fail regardless.  Whether that's due to selecting a
> > (viable) deleted non-explicit ctor (if we omit the explicit-spec) or due
> > to there being no viable non-explicit ctor (if we carry over the
> > explicit-spec) shouldn't make a difference, I think?
> > 
> > So it seems unnecessary to give these deleted overloads an
> > explicit-spec; it wouldn't be considered unless overload resolution
> > is destined to fail anyway.
> > 
> > I'm not totally confident about this assessment though so I'll
> > just carry over the explicit-spec for now.
> 
> I ended up hedging my bets and including the explicit-spec in the
> deleted ctors of pair and omitting it in those of tuple, so that
> we continue to be locally consistent.
> 
> > 
> > > 
> > > >
> > > > >
> > > > >
> > > > >
> > > > > > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > > > > > index be92f1eb973..182f3cc5e6a 100644
> > > > > > --- a/libstdc++-v3/include/std/tuple
> > > > > > +++ b/libstdc++-v3/include/std/tuple
> > > > > > @@ -50,6 +50,7 @@
> > > > > >  #define __glibcxx_want_apply
> > > > > >  #define __glibcxx_want_make_from_tuple
> > > > > >  #define __glibcxx_want_ranges_zip
> > > > > > +#define __glibcxx_want_tuple_like
> > > > > >  #include <bits/version.h>
> > > > > >
> > > > > >  namespace std _GLIBCXX_VISIBILITY(default)
> > > > > > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >        _Head _M_head_impl;
> > > > > >      };
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > > > > > +
> > > > > > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > > > > > +  template<typename _Cat, typename _Tp, typename _Up>
> > > > > > +    constexpr _Cat
> > > > > > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > > > > > +
> > > > > > +  template<typename _Cat, typename _Tp, typename _Up,
> > > > > > +          size_t _Idx0, size_t... _Idxs>
> > > > > > +    constexpr _Cat
> > > > > > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > > > > > +               index_sequence<_Idx0, _Idxs...>);
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >    /**
> > > > > >     * Contains the actual implementation of the @c tuple template, stored
> > > > > >     * as a recursive inheritance hierarchy from the first element (most
> > > > > > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         { }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _UTuple, size_t... _Is>
> > > > > > +       constexpr
> > > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > > > > > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > > +       { }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >        template<typename _Alloc>
> > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         { }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > > > > > +       constexpr
> > > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > > +                   _UTuple&& __u, index_sequence<_Is...>)
> > > > > > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > > +       { }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >        template<typename... _UElements>
> > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > >         void
> > > > > > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _UTuple>
> > > > > > +       constexpr void
> > > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > > > > > +       {
> > > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > > +       }
> > > > > > +
> > > > > > +      template<typename _UTuple>
> > > > > > +       constexpr void
> > > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > > > > > +       {
> > > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > > +       }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >      protected:
> > > > > >        _GLIBCXX20_CONSTEXPR
> > > > > >        void
> > > > > > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         { }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _UTuple>
> > > > > > +       constexpr
> > > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > > > > > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > > > > > +       { }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >        template<typename _Alloc>
> > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         { }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _Alloc, typename _UTuple>
> > > > > > +       constexpr
> > > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > > +                   _UTuple&& __u, index_sequence<0>)
> > > > > > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > > > > > +       { }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >        template<typename _UHead>
> > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > >         void
> > > > > > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         }
> > > > > >  #endif // C++23
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +    template<typename _UTuple>
> > > > > > +      constexpr void
> > > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > > +
> > > > > > +    template<typename _UTuple>
> > > > > > +      constexpr void
> > > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >      protected:
> > > > > >        _GLIBCXX20_CONSTEXPR
> > > > > >        void
> > > > > > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >  #endif
> > > > > >         }
> > > > > >
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _UTuple>
> > > > > > +       static consteval bool
> > > > > > +       __constructible_from_tuple_like()
> > > > > > +       {
> > > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > > > +       }
> > > > > > +
> > > > > > +      template<typename _UTuple>
> > > > > > +       static consteval bool
> > > > > > +       __convertible_from_tuple_like()
> > > > > > +       {
> > > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > >
> > > > > These new functions can use index_sequence_for<_Elements...>{} here,
> > > > > so you don't need the sizeof....
> > > > > That applies several times below as well.
> > > > >
> > > > > I think it's semantically identical, just a little shorter. I don't
> > > > > know if there's any compilation speed benefit either way. Maybe
> > > > > sizeof...(_Elements) is cheaper than expanding the pack into the
> > > > > index_sequence_for alias template?
> 
> It probably a little cheaper to use make_index_sequence directly, but I
> just didn't know about index_sequence_for :) Consider that changed.
> 
> > > > >
> > > > >
> > > > > > +       }
> > > > > > +#endif // C++23
> > > > > > +
> > > > > >      public:
> > > > > >        constexpr
> > > > > >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > > > > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         tuple(const pair<_U1, _U2>&&) = delete;
> > > > > >  #endif // C++23
> > > > > >
> > > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > > -      template<__tuple_like _UTuple>
> > > > > > -       constexpr explicit(...)
> > > > > > -       tuple(_UTuple&& __u);
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > > +       tuple(_UTuple&& __u)
> > > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > > +                    std::forward<_UTuple>(__u),
> > > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > > +       { }
> > > > > >  #endif // C++23
> > > > > >
> > > > > >        // Allocator-extended constructors.
> > > > > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > > > > >  #endif // C++23
> > > > > >
> > > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > > > > -       constexpr explicit(...)
> > > > > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > > +       { }
> > > > > >  #endif // C++23
> > > > >
> > > > > For some reason these two new constructors aren't deleted if they
> > > > > create dangling refs. I don't know why.
> > > >
> > > > Hmm, seems like an oversight.  Shall we proactively implement them?
> > > 
> > > Yes, I think so. I can't see why we would want to permit a dangling
> > > reference there.
> > > 
> > > e.g.
> > > std::array<long, 1> a{};
> > > std::tuple<const int&> t(a);
> > 
> > Sounds good.
> 
> In v2:
> 
> * Named the template parameters of the forward declaration of pair.
> * Added dangling checks for the new tuple and pair constructors
>   and corresponding tests.
> * Replaced make_index_sequence with index_sequence_for where applicable.

Ping.

> 
> -- >8 --
> 
> Subject: [PATCH 2/2] libstdc++: Implement P2165R4 changes to
>  std::pair/tuple/etc
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/ranges_util.h (__detail::__pair_like): Don't
> 	define in C++23 mode.
> 	(__detail::__pair_like_convertible_from): Adjust as per P2165R4.
> 	(__detail::__is_subrange<subrange>): Moved from <ranges>.
> 	(__detail::__is_tuple_like_v<subrange>): Likewise.
> 	* include/bits/stl_iterator.h: Include <bits/utility.h> for
> 	C++23.
> 	(__different_from): Move to <concepts>.
> 	(__iter_key_t): Adjust for C++23 as per P2165R4.
> 	(__iter_val_t): Likewise.
> 	* include/bits/stl_pair.h (pair, array): Forward declare.
> 	(get): Forward declare all overloads relevant to P2165R4
> 	tuple-like constructors.
> 	(__is_tuple_v): Define for C++23.
> 	(__is_tuple_like_v): Define for C++23.
> 	(__tuple_like): Define for C++23 as per P2165R4.
> 	(__pair_like): Define for C++23 as per P2165R4.
> 	(__eligibile_tuple_like): Define for C++23.
> 	(__eligibile_pair_like): Define for C++23.
> 	(pair::_S_constructible_from_pair_like): Define for C++23.
> 	(pair::_S_convertible_from_pair_like): Define for C++23.
> 	(pair::_S_dangles_from_pair_like): Define for C++23.
> 	(pair::pair): Define overloads taking a tuple-like type for
> 	C++23 as per P2165R4.
> 	(pair::_S_assignable_from_tuple_like): Define for C++23.
> 	(pair::_S_const_assignable_from_tuple_like): Define for C++23.
> 	(pair::operator=): Define overloads taking a tuple-like type for
> 	C++23 as per P2165R4.
> 	* include/bits/utility.h (ranges::__detail::__is_subrange):
> 	Moved from <ranges>.
> 	* include/bits/version.def (tuple_like): Define for C++23.
> 	* include/bits/version.h: Regenerate.
> 	* include/std/concepts (__different_from): Moved from
> 	<bits/stl_iterator.h>.
> 	(ranges::__swap::__adl_swap): Clarify which __detail namespace.
> 	* include/std/map (__cpp_lib_tuple_like): Define C++23.
> 	* include/std/ranges (__detail::__is_subrange): Moved to
> 	<bits/utility.h>.
> 	(__detail::__is_subrange<subrange>): Moved to <bits/ranges_util.h>
> 	(__detail::__has_tuple_element): Adjust for C++23 as per P2165R4.
> 	(__detail::__tuple_or_pair): Remove as per P2165R4.  Replace all
> 	uses with plain tuple as per P2165R4.
> 	* include/std/tuple (__cpp_lib_tuple_like): Define for C++23.
> 	(__tuple_like_tag_t): Define for C++23.
> 	(__tuple_cmp): Forward declare for C++23.
> 	(_Tuple_impl::_Tuple_impl): Define overloads taking
> 	__tuple_like_tag_t and a tuple-like type for C++23.
> 	(_Tuple_impl::_M_assign): Likewise.
> 	(tuple::__constructible_from_tuple_like): Define for C++23.
> 	(tuple::__convertible_from_tuple_like): Define for C++23.
> 	(tuple::__dangles_from_tuple_like): Define for C++23.
> 	(tuple::tuple): Define overloads taking a tuple-like type for
> 	C++23 as per P2165R4.
> 	(tuple::__assignable_from_tuple_like): Define for C++23.
> 	(tuple::__const_assignable_from_tuple_like): Define for C++23.
> 	(tuple::operator=): Define overloads taking a tuple-like type
> 	for C++23 as per P2165R4.
> 	(tuple::__tuple_like_common_comparison_category): Define for C++23.
> 	(tuple::operator<=>): Define overload taking a tuple-like type
> 	for C++23 as per P2165R4.
> 	(array, get): Forward declarations moved to <bits/stl_pair.h>.
> 	(tuple_cat): Constrain with __tuple_like for C++23 as per P2165R4.
> 	(apply): Likewise.
> 	(make_from_tuple): Likewise.
> 	(__tuple_like_common_reference): Define for C++23.
> 	(basic_common_reference): Adjust as per P2165R4.
> 	(__tuple_like_common_type): Define for C++23.
> 	(common_type): Adjust as per P2165R4.
> 	* include/std/unordered_map (__cpp_lib_tuple_like): Define for
> 	C++23.
> 	* include/std/utility (__cpp_lib_tuple_like): Define for C++23.
> 	* testsuite/std/ranges/zip/1.cc (test01): Adjust to handle pair
> 	and 2-tuple interchangeably.
> 	* testsuite/20_util/pair/p2165r4.cc: New test.
> 	* testsuite/20_util/tuple/p2165r4.cc: New test.
> ---
>  libstdc++-v3/include/bits/ranges_util.h       |  17 +-
>  libstdc++-v3/include/bits/stl_iterator.h      |  16 +-
>  libstdc++-v3/include/bits/stl_pair.h          | 182 ++++++++++
>  libstdc++-v3/include/bits/utility.h           |   8 +
>  libstdc++-v3/include/bits/version.def         |   8 +
>  libstdc++-v3/include/bits/version.h           |  11 +
>  libstdc++-v3/include/std/concepts             |  11 +-
>  libstdc++-v3/include/std/map                  |   1 +
>  libstdc++-v3/include/std/ranges               |  48 +--
>  libstdc++-v3/include/std/tuple                | 323 ++++++++++++++---
>  libstdc++-v3/include/std/unordered_map        |   1 +
>  libstdc++-v3/include/std/utility              |   1 +
>  .../testsuite/20_util/pair/p2165r4.cc         | 173 +++++++++
>  .../testsuite/20_util/tuple/p2165r4.cc        | 335 ++++++++++++++++++
>  libstdc++-v3/testsuite/std/ranges/zip/1.cc    |   4 +-
>  15 files changed, 1056 insertions(+), 83 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> 
> diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
> index bb04c49f044..9b79c3a229d 100644
> --- a/libstdc++-v3/include/bits/ranges_util.h
> +++ b/libstdc++-v3/include/bits/ranges_util.h
> @@ -224,6 +224,10 @@ namespace ranges
>  	&& !__uses_nonqualification_pointer_conversion<decay_t<_From>,
>  						       decay_t<_To>>;
>  
> +#if __glibcxx_tuple_like // >= C++23
> +    // P2165R4 version of __pair_like is defined in <bits/stl_pair.h>.
> +#else
> +    // C++20 version of __pair_like from P2321R2.
>      template<typename _Tp>
>        concept __pair_like
>  	= !is_reference_v<_Tp> && requires(_Tp __t)
> @@ -235,10 +239,11 @@ namespace ranges
>  	  { get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
>  	  { get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
>  	};
> +#endif
>  
>      template<typename _Tp, typename _Up, typename _Vp>
>        concept __pair_like_convertible_from
> -	= !range<_Tp> && __pair_like<_Tp>
> +	= !range<_Tp> && !is_reference_v<_Vp> && __pair_like<_Tp>
>  	&& constructible_from<_Tp, _Up, _Vp>
>  	&& __convertible_to_non_slicing<_Up, tuple_element_t<0, _Tp>>
>  	&& convertible_to<_Vp, tuple_element_t<1, _Tp>>;
> @@ -463,8 +468,18 @@ namespace ranges
>      using borrowed_subrange_t = __conditional_t<borrowed_range<_Range>,
>  						subrange<iterator_t<_Range>>,
>  						dangling>;
> +
> +  // __is_subrange is defined in <bits/utility.h>.
> +  template<typename _Iter, typename _Sent, subrange_kind _Kind>
> +    inline constexpr bool __detail::__is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
>  } // namespace ranges
>  
> +#if __glibcxx_tuple_like // >= C++23
> +  // __is_tuple_like_v is defined in <bits/stl_pair.h>.
> +  template<typename _It, typename _Sent, ranges::subrange_kind _Kind>
> +    inline constexpr bool __is_tuple_like_v<ranges::subrange<_It, _Sent, _Kind>> = true;
> +#endif
> +
>  // The following ranges algorithms are used by <ranges>, and are defined here
>  // so that <ranges> can avoid including all of <bits/ranges_algo.h>.
>  namespace ranges
> diff --git a/libstdc++-v3/include/bits/stl_iterator.h b/libstdc++-v3/include/bits/stl_iterator.h
> index d71a793e10d..560a10a7abe 100644
> --- a/libstdc++-v3/include/bits/stl_iterator.h
> +++ b/libstdc++-v3/include/bits/stl_iterator.h
> @@ -78,6 +78,10 @@
>  # include <bits/stl_construct.h>
>  #endif
>  
> +#if __glibcxx_tuple_like // >= C++23
> +# include <bits/utility.h> // for tuple_element_t
> +#endif
> +
>  namespace std _GLIBCXX_VISIBILITY(default)
>  {
>  _GLIBCXX_BEGIN_NAMESPACE_VERSION
> @@ -95,10 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      template<typename _Cat, typename _Limit, typename _Otherwise = _Cat>
>        using __clamp_iter_cat
>  	= __conditional_t<derived_from<_Cat, _Limit>, _Limit, _Otherwise>;
> -
> -    template<typename _Tp, typename _Up>
> -      concept __different_from
> -	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
>    }
>  #endif
>  
> @@ -2983,11 +2983,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    // of associative containers.
>    template<typename _InputIterator>
>      using __iter_key_t = remove_const_t<
> +#if __glibcxx_tuple_like // >= C++23
> +      tuple_element_t<0, typename iterator_traits<_InputIterator>::value_type>>;
> +#else
>        typename iterator_traits<_InputIterator>::value_type::first_type>;
> +#endif
>  
>    template<typename _InputIterator>
>      using __iter_val_t
> +#if __glibcxx_tuple_like // >= C++23
> +      = tuple_element_t<1, typename iterator_traits<_InputIterator>::value_type>;
> +#else
>        = typename iterator_traits<_InputIterator>::value_type::second_type;
> +#endif
>  
>    template<typename _T1, typename _T2>
>      struct pair;
> diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> index b81b479ad43..00ec53ebc33 100644
> --- a/libstdc++-v3/include/bits/stl_pair.h
> +++ b/libstdc++-v3/include/bits/stl_pair.h
> @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    /// @cond undocumented
>  
>    // Forward declarations.
> +  template<typename _T1, typename _T2>
> +    struct pair;
> +
>    template<typename...>
>      class tuple;
>  
> +  // Declarations of std::array and its std::get overloads, so that
> +  // std::tuple_cat can use them if <tuple> is included before <array>.
> +  // We also declare the other std::get overloads here so that they're
> +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> +  template<typename _Tp, size_t _Nm>
> +    struct array;
> +
>    template<size_t...>
>      struct _Index_tuple;
>  
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> +
> +  template<size_t _Int, class _Tp1, class _Tp2>
> +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> +    get(tuple<_Elements...>& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> +    get(const tuple<_Elements...>& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> +    get(tuple<_Elements...>&& __t) noexcept;
> +
> +  template<size_t __i, typename... _Elements>
> +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> +    get(const tuple<_Elements...>&& __t) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr _Tp&
> +    get(array<_Tp, _Nm>&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr _Tp&&
> +    get(array<_Tp, _Nm>&&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr const _Tp&
> +    get(const array<_Tp, _Nm>&) noexcept;
> +
> +  template<size_t _Int, typename _Tp, size_t _Nm>
> +    constexpr const _Tp&&
> +    get(const array<_Tp, _Nm>&&) noexcept;
> +
>  #if ! __cpp_lib_concepts
>    // Concept utility functions, reused in conditionally-explicit
>    // constructors.
> @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif // lib concepts
>  #endif // C++11
>  
> +#if __glibcxx_tuple_like // >= C++23
> +  template<typename _Tp>
> +    inline constexpr bool __is_tuple_v = false;
> +
> +  template<typename... _Ts>
> +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> +
> +  // TODO: Reuse __is_tuple_like from <type_traits>?
> +  template<typename _Tp>
> +    inline constexpr bool __is_tuple_like_v = false;
> +
> +  template<typename... _Elements>
> +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> +
> +  template<typename _T1, typename _T2>
> +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> +
> +  template<typename _Tp, size_t _Nm>
> +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> +
> +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> +
> +  template<typename _Tp>
> +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> +
> +  template<typename _Tp>
> +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> +
> +  template<typename _Tp, typename _Tuple>
> +    concept __eligible_tuple_like
> +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> +	&& (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> +	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> +
> +  template<typename _Tp, typename _Pair>
> +    concept __eligible_pair_like
> +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> +	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> +#endif // C++23
> +
>    template<typename _U1, typename _U2> class __pair_base
>    {
>  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> @@ -295,6 +393,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	  return false;
>  #endif
>  	}
> +
> +#if __glibcxx_tuple_like // >= C++23
> +      template<typename _UPair>
> +	static constexpr bool
> +	_S_constructible_from_pair_like()
> +	{
> +	  return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> +				  decltype(std::get<1>(std::declval<_UPair>()))>();
> +	}
> +
> +      template<typename _UPair>
> +	static constexpr bool
> +	_S_convertible_from_pair_like()
> +	{
> +	  return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> +				decltype(std::get<1>(std::declval<_UPair>()))>();
> +	}
> +
> +      template<typename _UPair>
> +	static constexpr bool
> +	_S_dangles_from_pair_like()
> +	{
> +	  return _S_dangles<decltype(std::get<0>(std::declval<_UPair>())),
> +			    decltype(std::get<1>(std::declval<_UPair>()))>();
> +	}
> +#endif // C++23
>        /// @endcond
>  
>      public:
> @@ -393,6 +517,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	pair(const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>  
> +#if __glibcxx_tuple_like // >= C++23
> +      template<__eligible_pair_like<pair> _UPair>
> +	requires (_S_constructible_from_pair_like<_UPair>())
> +	  && (!_S_dangles_from_pair_like<_UPair>())
> +	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> +	pair(_UPair&& __p)
> +	: first(std::get<0>(std::forward<_UPair>(__p))),
> +	  second(std::get<1>(std::forward<_UPair>(__p)))
> +	{ }
> +
> +      template<__eligible_pair_like<pair> _UPair>
> +	requires (_S_constructible_from_pair_like<_UPair>())
> +	  && (_S_dangles_from_pair_like<_UPair>())
> +	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> +	pair(_UPair&&) = delete;
> +#endif // C++23
> +
>    private:
>        /// @cond undocumented
>        template<typename _U1, typename _U2>
> @@ -421,6 +562,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	    return is_nothrow_assignable_v<_T2&, _U2>;
>  	  return false;
>  	}
> +
> +#if __glibcxx_tuple_like // >= C++23
> +      template<typename _UPair>
> +	static constexpr bool
> +	_S_assignable_from_tuple_like()
> +	{
> +	  return _S_assignable<decltype(std::get<0>(std::declval<_UPair>())),
> +			       decltype(std::get<1>(std::declval<_UPair>()))>();
> +	}
> +
> +      template<typename _UPair>
> +	static constexpr bool
> +	_S_const_assignable_from_tuple_like()
> +	{
> +	  return _S_const_assignable<decltype(std::get<0>(std::declval<_UPair>())),
> +				     decltype(std::get<1>(std::declval<_UPair>()))>();
> +	}
> +#endif // C++23
>        /// @endcond
>  
>    public:
> @@ -516,6 +675,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	  return *this;
>  	}
>  #endif // C++23
> +
> +#if __glibcxx_tuple_like // >= C++23
> +      template<__eligible_pair_like<pair> _UPair>
> +	requires (_S_assignable_from_tuple_like<_UPair>())
> +	constexpr pair&
> +	operator=(_UPair&& __p)
> +	{
> +	  first = std::get<0>(std::forward<_UPair>(__p));
> +	  second = std::get<1>(std::forward<_UPair>(__p));
> +	  return *this;
> +	}
> +
> +      template<__eligible_pair_like<pair> _UPair>
> +	requires (_S_const_assignable_from_tuple_like<_UPair>())
> +	constexpr const pair&
> +	operator=(_UPair&& __p) const
> +	{
> +	  first = std::get<0>(std::forward<_UPair>(__p));
> +	  second = std::get<1>(std::forward<_UPair>(__p));
> +	  return *this;
> +	}
> +#endif // C++23
> +
>  #else // !__cpp_lib_concepts
>        // C++11/14/17 implementation using enable_if, partially constexpr.
>  
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index d8a5fb960fe..2a741bf7000 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -266,6 +266,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif
>  #endif
>  
> +#if __glibcxx_ranges
> +  namespace ranges::__detail
> +  {
> +    template<typename _Range>
> +      inline constexpr bool __is_subrange = false;
> +  } // namespace __detail
> +#endif
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace
>  
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 8fb8a2877ee..502961eb269 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -1780,6 +1780,14 @@ ftms = {
>    };
>  };
>  
> +ftms = {
> +  name = tuple_like;
> +  values = {
> +    v = 202207;
> +    cxxmin = 23;
> +  };
> +};
> +
>  // Standard test specifications.
>  stds[97] = ">= 199711L";
>  stds[03] = ">= 199711L";
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index 9ba99deeda6..511030bde47 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2169,4 +2169,15 @@
>  #endif /* !defined(__cpp_lib_generator) && defined(__glibcxx_want_generator) */
>  #undef __glibcxx_want_generator
>  
> +// from version.def line 1774
> +#if !defined(__cpp_lib_tuple_like)
> +# if (__cplusplus >= 202100L)
> +#  define __glibcxx_tuple_like 202207L
> +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_tuple_like)
> +#   define __cpp_lib_tuple_like 202207L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_tuple_like) && defined(__glibcxx_want_tuple_like) */
> +#undef __glibcxx_want_tuple_like
> +
>  #undef __glibcxx_want_all
> diff --git a/libstdc++-v3/include/std/concepts b/libstdc++-v3/include/std/concepts
> index 66ed3714b25..4f3e059b051 100644
> --- a/libstdc++-v3/include/std/concepts
> +++ b/libstdc++-v3/include/std/concepts
> @@ -62,6 +62,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      concept same_as
>        = __detail::__same_as<_Tp, _Up> && __detail::__same_as<_Up, _Tp>;
>  
> +  namespace __detail
> +  {
> +    template<typename _Tp, typename _Up>
> +      concept __different_from
> +	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
> +  } // namespace __detail
> +
>    /// [concept.derived], concept derived_from
>    template<typename _Derived, typename _Base>
>      concept derived_from = __is_base_of(_Base, _Derived)
> @@ -185,8 +192,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  
>        template<typename _Tp, typename _Up>
>  	concept __adl_swap
> -	  = (__detail::__class_or_enum<remove_reference_t<_Tp>>
> -	    || __detail::__class_or_enum<remove_reference_t<_Up>>)
> +	  = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
> +	    || std::__detail::__class_or_enum<remove_reference_t<_Up>>)
>  	  && requires(_Tp&& __t, _Up&& __u) {
>  	    swap(static_cast<_Tp&&>(__t), static_cast<_Up&&>(__u));
>  	  };
> diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
> index dcfd222d173..4a96e59a5bc 100644
> --- a/libstdc++-v3/include/std/map
> +++ b/libstdc++-v3/include/std/map
> @@ -74,6 +74,7 @@
>  #define __glibcxx_want_map_try_emplace
>  #define __glibcxx_want_node_extract
>  #define __glibcxx_want_nonmember_container_access
> +#define __glibcxx_want_tuple_like
>  #include <bits/version.h>
>  
>  #if __cplusplus >= 201703L
> diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> index f2413badd9c..7d739852677 100644
> --- a/libstdc++-v3/include/std/ranges
> +++ b/libstdc++-v3/include/std/ranges
> @@ -2389,11 +2389,7 @@ namespace views::__adaptor
>  	inline constexpr bool __is_basic_string_view<basic_string_view<_CharT, _Traits>>
>  	  = true;
>  
> -      template<typename _Range>
> -	inline constexpr bool __is_subrange = false;
> -
> -      template<typename _Iter, typename _Sent, subrange_kind _Kind>
> -	inline constexpr bool __is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
> +      using ranges::__detail::__is_subrange;
>  
>        template<typename _Range>
>  	inline constexpr bool __is_iota_view = false;
> @@ -4166,6 +4162,10 @@ namespace views::__adaptor
>  
>    namespace __detail
>    {
> +#if __cpp_lib_tuple_like // >= C++23
> +    template<typename _Tp, size_t _Nm>
> +    concept __has_tuple_element = __tuple_like<_Tp> && _Nm < tuple_size_v<_Tp>;
> +#else
>      template<typename _Tp, size_t _Nm>
>      concept __has_tuple_element = requires(_Tp __t)
>        {
> @@ -4175,6 +4175,7 @@ namespace views::__adaptor
>  	{ std::get<_Nm>(__t) }
>  	  -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
>        };
> +#endif
>  
>      template<typename _Tp, size_t _Nm>
>        concept __returnable_element
> @@ -4559,23 +4560,12 @@ namespace views::__adaptor
>  	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
>  	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
>  
> -    template<typename... _Ts>
> -      struct __tuple_or_pair
> -      { using type = std::tuple<_Ts...>; };
> -
> -    template<typename _Tp, typename _Up>
> -      struct __tuple_or_pair<_Tp, _Up>
> -      { using type = pair<_Tp, _Up>; };
> -
> -    template<typename... _Ts>
> -      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
> -
>      template<typename _Fp, typename _Tuple>
>        constexpr auto
>        __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
>        {
>  	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> -	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
> +	  return tuple<invoke_result_t<_Fp&, _Ts>...>
>  	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
>  	}, std::forward<_Tuple>(__tuple));
>        }
> @@ -4696,7 +4686,7 @@ namespace views::__adaptor
>  #ifdef __clang__ // LLVM-61763 workaround
>    public:
>  #endif
> -    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
> +    tuple<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
>  
>      constexpr explicit
>      _Iterator(decltype(_M_current) __current)
> @@ -4728,7 +4718,7 @@ namespace views::__adaptor
>      // iterator_category defined in __zip_view_iter_cat
>      using iterator_concept = decltype(_S_iter_concept());
>      using value_type
> -      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
> +      = tuple<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
>      using difference_type
>        = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
>  
> @@ -4900,7 +4890,7 @@ namespace views::__adaptor
>    template<bool _Const>
>    class zip_view<_Vs...>::_Sentinel
>    {
> -    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
> +    tuple<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
>  
>      constexpr explicit
>      _Sentinel(decltype(_M_end) __end)
> @@ -8325,8 +8315,7 @@ namespace views::__adaptor
>  		    && __detail::__cartesian_product_is_common<_First, _Vs...>)
>      {
>        auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
> -	using _Ret = __detail::__tuple_or_pair_t<iterator_t<_First>,
> -						 iterator_t<_Vs>...>;
> +	using _Ret = tuple<iterator_t<_First>, iterator_t<_Vs>...>;
>  	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
>  	auto& __first = std::get<0>(_M_bases);
>  	return _Ret{(__empty_tail
> @@ -8342,8 +8331,7 @@ namespace views::__adaptor
>      end() const requires __detail::__cartesian_product_is_common<const _First, const _Vs...>
>      {
>        auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
> -	using _Ret = __detail::__tuple_or_pair_t<iterator_t<const _First>,
> -						 iterator_t<const _Vs>...>;
> +	using _Ret = tuple<iterator_t<const _First>, iterator_t<const _Vs>...>;
>  	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
>  	auto& __first = std::get<0>(_M_bases);
>  	return _Ret{(__empty_tail
> @@ -8416,8 +8404,8 @@ namespace views::__adaptor
>    {
>      using _Parent = __maybe_const_t<_Const, cartesian_product_view>;
>      _Parent* _M_parent = nullptr;
> -    __detail::__tuple_or_pair_t<iterator_t<__maybe_const_t<_Const, _First>>,
> -				iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
> +    tuple<iterator_t<__maybe_const_t<_Const, _First>>,
> +	  iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
>  
>      constexpr
>      _Iterator(_Parent& __parent, decltype(_M_current) __current)
> @@ -8444,11 +8432,11 @@ namespace views::__adaptor
>      using iterator_category = input_iterator_tag;
>      using iterator_concept = decltype(_S_iter_concept());
>      using value_type
> -      = __detail::__tuple_or_pair_t<range_value_t<__maybe_const_t<_Const, _First>>,
> -				    range_value_t<__maybe_const_t<_Const, _Vs>>...>;
> +      = tuple<range_value_t<__maybe_const_t<_Const, _First>>,
> +	      range_value_t<__maybe_const_t<_Const, _Vs>>...>;
>      using reference
> -      = __detail::__tuple_or_pair_t<range_reference_t<__maybe_const_t<_Const, _First>>,
> -				    range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
> +      = tuple<range_reference_t<__maybe_const_t<_Const, _First>>,
> +	      range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
>      using difference_type = decltype(cartesian_product_view::_S_difference_type());
>  
>      _Iterator() = default;
> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> index be92f1eb973..ba364d6a4f8 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -50,6 +50,7 @@
>  #define __glibcxx_want_apply
>  #define __glibcxx_want_make_from_tuple
>  #define __glibcxx_want_ranges_zip
> +#define __glibcxx_want_tuple_like
>  #include <bits/version.h>
>  
>  namespace std _GLIBCXX_VISIBILITY(default)
> @@ -246,6 +247,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        _Head _M_head_impl;
>      };
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> +
> +  // These forward declarations are used by the operator<=> overload for
> +  // tuple-like types.
> +  template<typename _Cat, typename _Tp, typename _Up>
> +    constexpr _Cat
> +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> +
> +  template<typename _Cat, typename _Tp, typename _Up,
> +	   size_t _Idx0, size_t... _Idxs>
> +    constexpr _Cat
> +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> +		index_sequence<_Idx0, _Idxs...>);
> +#endif // C++23
> +
>    /**
>     * Contains the actual implementation of the @c tuple template, stored
>     * as a recursive inheritance hierarchy from the first element (most
> @@ -342,6 +359,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	{ }
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple, size_t... _Is>
> +	constexpr
> +	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> +	: _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> +	{ }
> +#endif // C++23
> +
>        template<typename _Alloc>
>  	_GLIBCXX20_CONSTEXPR
>  	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> @@ -428,6 +453,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	{ }
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> +	constexpr
> +	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> +		    _UTuple&& __u, index_sequence<_Is...>)
> +	: _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> +	{ }
> +#endif // C++23
> +
>        template<typename... _UElements>
>  	_GLIBCXX20_CONSTEXPR
>  	void
> @@ -470,6 +504,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	}
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +	constexpr void
> +	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> +	{
> +	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> +	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> +	}
> +
> +      template<typename _UTuple>
> +	constexpr void
> +	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> +	{
> +	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> +	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> +	}
> +#endif // C++23
> +
>      protected:
>        _GLIBCXX20_CONSTEXPR
>        void
> @@ -563,6 +615,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	{ }
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +	constexpr
> +	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> +	: _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> +	{ }
> +#endif // C++23
> +
>        template<typename _Alloc>
>  	_GLIBCXX20_CONSTEXPR
>  	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> @@ -633,6 +693,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	{ }
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, typename _UTuple>
> +	constexpr
> +	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> +		    _UTuple&& __u, index_sequence<0>)
> +	: _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> +	{ }
> +#endif // C++23
> +
>        template<typename _UHead>
>  	_GLIBCXX20_CONSTEXPR
>  	void
> @@ -667,6 +736,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	}
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +    template<typename _UTuple>
> +      constexpr void
> +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> +
> +    template<typename _UTuple>
> +      constexpr void
> +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> +#endif // C++23
> +
>      protected:
>        _GLIBCXX20_CONSTEXPR
>        void
> @@ -846,6 +927,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif
>  	}
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +	static consteval bool
> +	__dangles_from_tuple_like()
> +	{
> +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> +	    return __dangles<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +
> +      template<typename _UTuple>
> +	static consteval bool
> +	__constructible_from_tuple_like()
> +	{
> +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> +	    return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +
> +      template<typename _UTuple>
> +	static consteval bool
> +	__convertible_from_tuple_like()
> +	{
> +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> +	    return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +#endif // C++23
> +
>      public:
>        constexpr
>        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> @@ -1016,10 +1126,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	tuple(const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>  
> -#if 0 && __cpp_lib_tuple_like // >= C++23
> -      template<__tuple_like _UTuple>
> -	constexpr explicit(...)
> -	tuple(_UTuple&& __u);
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<__eligible_tuple_like<tuple> _UTuple>
> +	requires (__constructible_from_tuple_like<_UTuple>())
> +	  && (!__use_other_ctor<_UTuple>())
> +	  && (!__dangles_from_tuple_like<_UTuple>())
> +	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> +	tuple(_UTuple&& __u)
> +	: _Inherited(__tuple_like_tag_t{},
> +		     std::forward<_UTuple>(__u),
> +		     index_sequence_for<_Elements...>{})
> +	{ }
> +
> +      template<__eligible_tuple_like<tuple> _UTuple>
> +	requires (__constructible_from_tuple_like<_UTuple>())
> +	  && (!__use_other_ctor<_UTuple>())
> +	  && (__dangles_from_tuple_like<_UTuple>())
> +	tuple(_UTuple&&) = delete;
>  #endif // C++23
>  
>        // Allocator-extended constructors.
> @@ -1202,10 +1325,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
>  #endif // C++23
>  
> -#if 0 && __cpp_lib_tuple_like // >= C++23
> -      template<typename _Alloc, __tuple_like _UTuple>
> -	constexpr explicit(...)
> -	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> +	requires (__constructible_from_tuple_like<_UTuple>())
> +	  && (!__use_other_ctor<_UTuple>())
> +	  && (!__dangles_from_tuple_like<_UTuple>())
> +	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> +	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> +	: _Inherited(__tuple_like_tag_t{},
> +		     __tag, __a, std::forward<_UTuple>(__u),
> +		     index_sequence_for<_Elements...>{})
> +	{ }
> +
> +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> +	requires (__constructible_from_tuple_like<_UTuple>())
> +	  && (!__use_other_ctor<_UTuple>())
> +	  && (__dangles_from_tuple_like<_UTuple>())
> +	tuple(allocator_arg_t, const _Alloc&, _UTuple&&) = delete;
>  #endif // C++23
>  
>  #else // !(concepts && conditional_explicit)
> @@ -1539,6 +1675,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	}
>  #endif // C++23
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<typename _UTuple>
> +	static consteval bool
> +	__assignable_from_tuple_like()
> +	{
> +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> +	    return __assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +
> +      template<typename _UTuple>
> +	static consteval bool
> +	__const_assignable_from_tuple_like()
> +	{
> +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> +	    return __const_assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +#endif // C++23
> +
>      public:
>  
>        tuple& operator=(const tuple& __u) = delete;
> @@ -1661,14 +1817,59 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	}
>  #endif // C++23
>  
> -#if 0 && __cpp_lib_tuple_like // >= C++23
> -      template<__tuple_like _UTuple>
> +#if __cpp_lib_tuple_like // >= C++23
> +      template<__eligible_tuple_like<tuple> _UTuple>
> +	requires (__assignable_from_tuple_like<_UTuple>())
>  	constexpr tuple&
> -	operator=(_UTuple&& __u);
> +	operator=(_UTuple&& __u)
> +	{
> +	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
> +	  return *this;
> +	}
> +
> +      template<__eligible_tuple_like<tuple> _UTuple>
> +	requires (__const_assignable_from_tuple_like<_UTuple>())
> +	constexpr const tuple&
> +	operator=(_UTuple&& __u) const
> +	{
> +	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
> +	  return *this;
> +	}
>  
>        template<__tuple_like _UTuple>
> -	constexpr tuple&
> -	operator=(_UTuple&& __u) const;
> +	requires (!__is_tuple_v<_UTuple>)
> +	friend constexpr bool
> +	operator==(const tuple& __t, const _UTuple& __u)
> +	{
> +	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
> +	      "tuple objects can only be compared if they have equal sizes.");
> +	  return [&]<size_t... _Is>(index_sequence<_Is...>) {
> +	    return (bool(std::get<_Is>(__t) == std::get<_Is>(__u))
> +		    && ...);
> +	  }(index_sequence_for<_Elements...>{});
> +	}
> +
> +      template<__tuple_like _UTuple,
> +	       typename = make_index_sequence<tuple_size_v<_UTuple>>>
> +	struct __tuple_like_common_comparison_category;
> +
> +      template<__tuple_like _UTuple, size_t... _Is>
> +	requires requires
> +	  { typename void_t<__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>; }
> +	struct __tuple_like_common_comparison_category<_UTuple, index_sequence<_Is...>>
> +	{
> +	  using type = common_comparison_category_t
> +	    <__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>;
> +	};
> +
> +      template<__tuple_like _UTuple>
> +	requires (!__is_tuple_v<_UTuple>)
> +	friend constexpr typename __tuple_like_common_comparison_category<_UTuple>::type
> +	operator<=>(const tuple& __t, const _UTuple& __u)
> +	{
> +	  using _Cat = typename __tuple_like_common_comparison_category<_UTuple>::type;
> +	  return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Elements...>());
> +	}
>  #endif // C++23
>  
>  #else // ! (concepts && consteval)
> @@ -2433,27 +2634,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      forward_as_tuple(_Elements&&... __args) noexcept
>      { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }
>  
> -  // Declarations of std::array and its std::get overloads, so that
> -  // std::tuple_cat can use them if <tuple> is included before <array>.
> -
> -  template<typename _Tp, size_t _Nm> struct array;
> -
> -  template<size_t _Int, typename _Tp, size_t _Nm>
> -    constexpr _Tp&
> -    get(array<_Tp, _Nm>&) noexcept;
> -
> -  template<size_t _Int, typename _Tp, size_t _Nm>
> -    constexpr _Tp&&
> -    get(array<_Tp, _Nm>&&) noexcept;
> -
> -  template<size_t _Int, typename _Tp, size_t _Nm>
> -    constexpr const _Tp&
> -    get(const array<_Tp, _Nm>&) noexcept;
> -
> -  template<size_t _Int, typename _Tp, size_t _Nm>
> -    constexpr const _Tp&&
> -    get(const array<_Tp, _Nm>&&) noexcept;
> -
>    /// @cond undocumented
>    template<size_t, typename, typename, size_t>
>      struct __make_tuple_impl;
> @@ -2569,8 +2749,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    /// @endcond
>  
>    /// Create a `tuple` containing all elements from multiple tuple-like objects
> +#if __cpp_lib_tuple_like // >= C++23
> +  template<__tuple_like... _Tpls>
> +#else
>    template<typename... _Tpls, typename = typename
>             enable_if<__and_<__is_tuple_like<_Tpls>...>::value>::type>
> +#endif
>      constexpr auto
>      tuple_cat(_Tpls&&... __tpls)
>      -> typename __tuple_cat_result<_Tpls...>::__type
> @@ -2722,7 +2906,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
>      }
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +  template <typename _Fn, __tuple_like _Tuple>
> +#else
>    template <typename _Fn, typename _Tuple>
> +#endif
>      constexpr decltype(auto)
>      apply(_Fn&& __f, _Tuple&& __t)
>      noexcept(__unpack_std_tuple<is_nothrow_invocable, _Fn, _Tuple>)
> @@ -2741,7 +2929,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      __make_from_tuple_impl(_Tuple&& __t, index_sequence<_Idx...>)
>      { return _Tp(std::get<_Idx>(std::forward<_Tuple>(__t))...); }
>  
> +#if __cpp_lib_tuple_like // >= C++23
> +  template <typename _Tp, __tuple_like _Tuple>
> +#else
>    template <typename _Tp, typename _Tuple>
> +#endif
>      constexpr _Tp
>      make_from_tuple(_Tuple&& __t)
>      noexcept(__unpack_std_tuple<is_nothrow_constructible, _Tp, _Tuple>)
> @@ -2759,17 +2951,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      }
>  #endif
>  
> -#if __cpp_lib_ranges_zip // >= C++23
> -  template<typename... _TTypes, typename... _UTypes,
> +#if __cpp_lib_tuple_like // >= C++23
> +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> +	   template<typename> class _TQual, template<typename> class _UQual,
> +	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
> +  struct __tuple_like_common_reference;
> +
> +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> +	   template<typename> class _TQual, template<typename> class _UQual,
> +	   size_t... _Is>
> +    requires requires
> +      { typename tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
> +					  _UQual<tuple_element_t<_Is, _UTuple>>>...>; }
> +  struct __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual, index_sequence<_Is...>>
> +  {
> +    using type = tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
> +					  _UQual<tuple_element_t<_Is, _UTuple>>>...>;
> +  };
> +
> +  template<__tuple_like _TTuple, __tuple_like _UTuple,
>  	   template<typename> class _TQual, template<typename> class _UQual>
> -    requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; }
> -  struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual>
> -  { using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; };
> -
> -  template<typename... _TTypes, typename... _UTypes>
> -    requires requires { typename tuple<common_type_t<_TTypes, _UTypes>...>; }
> -  struct common_type<tuple<_TTypes...>, tuple<_UTypes...>>
> -  { using type = tuple<common_type_t<_TTypes, _UTypes>...>; };
> +    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
> +      && is_same_v<_TTuple, decay_t<_TTuple>>
> +      && is_same_v<_UTuple, decay_t<_UTuple>>
> +      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
> +      && requires { typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type; }
> +  struct basic_common_reference<_TTuple, _UTuple, _TQual, _UQual>
> +  {
> +    using type = typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type;
> +  };
> +
> +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> +	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
> +  struct __tuple_like_common_type;
> +
> +  template<__tuple_like _TTuple, __tuple_like _UTuple, size_t... _Is>
> +    requires requires
> +      { typename tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
> +				   tuple_element_t<_Is, _UTuple>>...>; }
> +  struct __tuple_like_common_type<_TTuple, _UTuple, index_sequence<_Is...>>
> +  {
> +    using type = tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
> +				     tuple_element_t<_Is, _UTuple>>...>;
> +  };
> +
> +  template<__tuple_like _TTuple, __tuple_like _UTuple>
> +    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
> +      && is_same_v<_TTuple, decay_t<_TTuple>>
> +      && is_same_v<_UTuple, decay_t<_UTuple>>
> +      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
> +      && requires { typename __tuple_like_common_type<_TTuple, _UTuple>::type; }
> +  struct common_type<_TTuple, _UTuple>
> +  {
> +    using type = typename __tuple_like_common_type<_TTuple, _UTuple>::type;
> +  };
>  #endif // C++23
>  
>    /// @}
> diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/include/std/unordered_map
> index efad0cef584..ea6129d6494 100644
> --- a/libstdc++-v3/include/std/unordered_map
> +++ b/libstdc++-v3/include/std/unordered_map
> @@ -51,6 +51,7 @@
>  #define __glibcxx_want_node_extract
>  #define __glibcxx_want_nonmember_container_access
>  #define __glibcxx_want_unordered_map_try_emplace
> +#define __glibcxx_want_tuple_like
>  #include <bits/version.h>
>  
>  #if __cplusplus >= 201703L
> diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
> index f113d572e59..212513f6f48 100644
> --- a/libstdc++-v3/include/std/utility
> +++ b/libstdc++-v3/include/std/utility
> @@ -92,6 +92,7 @@
>  #define __glibcxx_want_tuple_element_t
>  #define __glibcxx_want_tuples_by_type
>  #define __glibcxx_want_unreachable
> +#define __glibcxx_want_tuple_like
>  #include <bits/version.h>
>  
>  namespace std _GLIBCXX_VISIBILITY(default)
> diff --git a/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
> new file mode 100644
> index 00000000000..ef06df1c53f
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
> @@ -0,0 +1,173 @@
> +// Verify P2165R4 enhancements to std::pair.
> +// { dg-do run { target c++23 } }
> +
> +#include <array>
> +#include <tuple>
> +#include <utility>
> +#include <testsuite_hooks.h>
> +
> +using std::array;
> +using std::pair;
> +using std::tuple;
> +
> +struct A { };
> +
> +template<template<typename> class pair_like_t>
> +constexpr bool
> +test01()
> +{
> +  struct B {
> +    int m;
> +    constexpr B(A&) : m(0) { }
> +    constexpr B(A&&) : m(1) { }
> +    constexpr B(const A&) : m(2) { }
> +    constexpr B(const A&&) : m(3) { }
> +  };
> +
> +  // template<pair-like UPair>
> +  //   constexpr explicit(false) pair(UPair&&);
> +
> +  pair_like_t<A> pair_like;
> +
> +  [&] {
> +    pair<B, B> p2b = pair_like;
> +    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b = std::move(pair_like);
> +    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b = std::as_const(pair_like);
> +    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b = std::move(std::as_const(pair_like));
> +    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> +  }();
> +
> +  // Verify dangling checks.
> +  static_assert( !std::is_constructible_v<pair<const int&, int>, pair_like_t<long>> );
> +  static_assert( !std::is_constructible_v<pair<int, const int&>, pair_like_t<long>> );
> +
> +  return true;
> +}
> +
> +template<template<typename> class pair_like_t>
> +constexpr bool
> +test02()
> +{
> +  struct B {
> +    int m;
> +    constexpr explicit B(A&) : m(0) { }
> +    constexpr explicit B(A&&) : m(1) { }
> +    constexpr explicit B(const A&) : m(2) { }
> +    constexpr explicit B(const A&&) : m(3) { }
> +  };
> +
> +  // template<pair-like UPair>
> +  //   constexpr explicit(true) pair(UPair&&);
> +
> +  static_assert( !std::is_convertible_v<pair_like_t<A>, pair<B, B>> );
> +
> +  pair_like_t<A> pair_like;
> +
> +  [&] {
> +    pair<B, B> p2b{pair_like};
> +    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b{std::move(pair_like)};
> +    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b{std::as_const(pair_like)};
> +    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> +  }();
> +  [&] {
> +    pair<B, B> p2b{std::move(std::as_const(pair_like))};
> +    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> +  }();
> +
> +  return true;
> +}
> +
> +template<template<typename> class pair_like_t>
> +constexpr bool
> +test03()
> +{
> +  struct B {
> +    int m;
> +    constexpr B& operator=(A&) { m = 0; return *this; }
> +    constexpr B& operator=(A&&) { m = 1; return *this; }
> +    constexpr B& operator=(const A&) { m = 2; return *this; }
> +    constexpr B& operator=(const A&&) { m = 3; return *this; }
> +  };
> +
> +  // template<pair-like UPair>
> +  //   constexpr pair& operator=(UPair&&);
> +
> +  pair_like_t<A> pair_like;
> +
> +  pair<B, B> p2b;
> +  p2b = pair_like;
> +  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> +  p2b = std::move(pair_like);
> +  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> +  p2b = std::as_const(pair_like);
> +  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> +  p2b = std::move(std::as_const(pair_like));
> +  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> +
> +  return true;
> +}
> +
> +template<template<typename> class pair_like_t>
> +constexpr bool
> +test04()
> +{
> +  struct B {
> +    mutable int m;
> +    constexpr const B& operator=(A&) const { m = 0; return *this; }
> +    constexpr const B& operator=(A&&) const { m = 1; return *this; }
> +    constexpr const B& operator=(const A&) const { m = 2; return *this; }
> +    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
> +  };
> +
> +  // template<pair-like UPair>
> +  //   constexpr const pair& operator=(UPair&&) const;
> +
> +  pair_like_t<A> pair_like;
> +
> +  const pair<B, B> p2b;
> +  p2b = pair_like;
> +  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> +  p2b = std::move(pair_like);
> +  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> +  p2b = std::as_const(pair_like);
> +  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> +  p2b = std::move(std::as_const(pair_like));
> +  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> +
> +  return true;
> +}
> +
> +template<typename T>
> +using pair_like_array = array<T, 2>;
> +
> +template<typename T>
> +using pair_like_tuple = tuple<T, T>;
> +
> +int
> +main()
> +{
> +  static_assert( test01<pair_like_array>() );
> +  static_assert( test02<pair_like_array>() );
> +  static_assert( test03<pair_like_array>() );
> +  static_assert( test04<pair_like_array>() );
> +
> +  static_assert( test01<pair_like_tuple>() );
> +  static_assert( test02<pair_like_tuple>() );
> +  static_assert( test03<pair_like_tuple>() );
> +  static_assert( test04<pair_like_tuple>() );
> +}
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> new file mode 100644
> index 00000000000..e2437c469b6
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> @@ -0,0 +1,335 @@
> +// Verify P2165R4 enhancements to std::tuple.
> +// { dg-do run { target c++23 } }
> +
> +#include <array>
> +#include <tuple>
> +#include <utility>
> +#include <memory>
> +#include <testsuite_hooks.h>
> +
> +using std::array;
> +using std::pair;
> +using std::tuple;
> +using std::allocator;
> +using std::allocator_arg_t;
> +using std::allocator_arg;
> +
> +namespace alloc {
> +  struct B01;
> +  struct B02;
> +}
> +
> +template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
> +template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
> +
> +struct A { };
> +
> +template<template<typename> class tuple_like_t>
> +constexpr bool
> +test01()
> +{
> +  struct B {
> +    int m;
> +    constexpr B(A&) : m(0) { }
> +    constexpr B(A&&) : m(1) { }
> +    constexpr B(const A&) : m(2) { }
> +    constexpr B(const A&&) : m(3) { }
> +  };
> +
> +  // template<tuple-like UTuple>
> +  //   constexpr explicit(false) tuple(UTuple&&);
> +
> +  tuple_like_t<A> tuple_like;
> +
> +  [&] {
> +    tuple<B, B, B> t3b = tuple_like;
> +    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b = std::move(tuple_like);
> +    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b = std::as_const(tuple_like);
> +    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b = std::move(std::as_const(tuple_like));
> +    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +  }();
> +
> +  // Verify dangling checks.
> +  static_assert( !std::is_constructible_v<tuple<const int&, int, int>, tuple_like_t<long>> );
> +  static_assert( !std::is_constructible_v<tuple<int, const int&, int>, tuple_like_t<long>> );
> +  static_assert( !std::is_constructible_v<tuple<int, int, const int&>, tuple_like_t<long>> );
> +
> +  return true;
> +}
> +
> +namespace alloc
> +{
> +  struct B01 {
> +    int m;
> +    B01(A&);
> +    B01(A&&);
> +    B01(const A&);
> +    B01(const A&&);
> +    constexpr B01(allocator_arg_t, allocator<int>, A&) : m(0) { }
> +    constexpr B01(allocator_arg_t, allocator<int>, A&&) : m(1) { }
> +    constexpr B01(allocator_arg_t, allocator<int>, const A&) : m(2) { }
> +    constexpr B01(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
> +  };
> +
> +  template<template<typename> class tuple_like_t>
> +  constexpr bool
> +  test01()
> +  {
> +    using B = B01;
> +
> +    // template<tuple-like UTuple>
> +    //   constexpr explicit(false) tuple(allocator_arg_t, const Alloc&, UTuple&&);
> +
> +    tuple_like_t<A> tuple_like;
> +
> +    [&] {
> +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, tuple_like};
> +      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(tuple_like)};
> +      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
> +      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
> +      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +    }();
> +
> +  // Verify dangling checks.
> +    static_assert( !std::is_constructible_v<tuple<const int&, int, int>,
> +					    allocator_arg_t, allocator<int>,
> +					    tuple_like_t<long>> );
> +    static_assert( !std::is_constructible_v<tuple<int, const int&, int>,
> +					    allocator_arg_t, allocator<int>,
> +					    tuple_like_t<long>> );
> +    static_assert( !std::is_constructible_v<tuple<int, int, const int&>,
> +					    allocator_arg_t, allocator<int>,
> +					    tuple_like_t<long>> );
> +
> +    return true;
> +  }
> +}
> +
> +template<template<typename> class tuple_like_t>
> +constexpr bool
> +test02()
> +{
> +  struct B {
> +    int m;
> +    constexpr explicit B(A&) : m(0) { }
> +    constexpr explicit B(A&&) : m(1) { }
> +    constexpr explicit B(const A&) : m(2) { }
> +    constexpr explicit B(const A&&) : m(3) { }
> +  };
> +
> +  // template<tuple-like UTuple>
> +  //   constexpr explicit(true) tuple(UTuple&&);
> +
> +  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
> +
> +  tuple_like_t<A> tuple_like;
> +
> +  [&] {
> +    tuple<B, B, B> t3b{tuple_like};
> +    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b{std::move(tuple_like)};
> +    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b{std::as_const(tuple_like)};
> +    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +  }();
> +  [&] {
> +    tuple<B, B, B> t3b{std::move(std::as_const(tuple_like))};
> +    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +  }();
> +
> +  return true;
> +}
> +
> +namespace alloc
> +{
> +  struct B02 {
> +    int m;
> +    explicit B02(A&);
> +    explicit B02(A&&);
> +    explicit B02(const A&);
> +    explicit B02(const A&&);
> +    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : m(0) { }
> +    explicit constexpr B02(allocator_arg_t, allocator<int>, A&&) : m(1) { }
> +    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&) : m(2) { }
> +    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
> +  };
> +
> +  template<template<typename> class tuple_like_t>
> +  constexpr bool
> +  test02()
> +  {
> +    using B = B02;
> +
> +    // template<tuple-like UTuple>
> +    //   constexpr explicit(true) tuple(allocator_arg_t, const Alloc&, UTuple&&);
> +
> +    static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
> +
> +    tuple_like_t<A> tuple_like;
> +
> +    [&] {
> +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, tuple_like};
> +      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(tuple_like)};
> +      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
> +      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +    }();
> +    [&] {
> +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
> +      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +    }();
> +
> +    return true;
> +  }
> +}
> +
> +
> +template<template<typename> class tuple_like_t>
> +constexpr bool
> +test03()
> +{
> +  struct B {
> +    int m;
> +    constexpr B& operator=(A&) { m = 0; return *this; }
> +    constexpr B& operator=(A&&) { m = 1; return *this; }
> +    constexpr B& operator=(const A&) { m = 2; return *this; }
> +    constexpr B& operator=(const A&&) { m = 3; return *this; }
> +  };
> +
> +  // template<tuple-like UTuple>
> +  //   constexpr tuple& operator=(UTuple&&);
> +
> +  tuple_like_t<A> tuple_like;
> +
> +  tuple<B, B, B> t3b;
> +  t3b = tuple_like;
> +  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +  t3b = std::move(tuple_like);
> +  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +  t3b = std::as_const(tuple_like);
> +  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +  t3b = std::move(std::as_const(tuple_like));
> +  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +
> +  return true;
> +}
> +
> +template<template<typename> class tuple_like_t>
> +constexpr bool
> +test04()
> +{
> +  struct B {
> +    mutable int m;
> +    constexpr const B& operator=(A&) const { m = 0; return *this; }
> +    constexpr const B& operator=(A&&) const { m = 1; return *this; }
> +    constexpr const B& operator=(const A&) const { m = 2; return *this; }
> +    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
> +  };
> +
> +  // template<tuple-like UTuple>
> +  //   constexpr const tuple& operator=(UTuple&&) const;
> +
> +  tuple_like_t<A> tuple_like;
> +
> +  const tuple<B, B, B> t3b;
> +  t3b = tuple_like;
> +  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> +  t3b = std::move(tuple_like);
> +  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> +  t3b = std::as_const(tuple_like);
> +  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> +  t3b = std::move(std::as_const(tuple_like));
> +  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> +
> +  return true;
> +}
> +
> +template<template<typename> class tuple_like_t>
> +constexpr bool
> +test05()
> +{
> +  // template<tuple-like UTuple>
> +  //   constexpr bool operator==(const tuple&, const UTuple&);
> +
> +  static_assert( tuple{1, 2, 3} == tuple_like_t{1, 2, 3} );
> +  static_assert( tuple{1, 2, 4} != tuple_like_t{1, 2, 3} );
> +  static_assert( tuple_like_t{1, 2, 3} == tuple{1, 2, 3} );
> +  static_assert( tuple_like_t{1, 2, 3} != tuple{1, 2, 4} );
> +
> +  // template<tuple-like UTuple>
> +  //   constexpr bool operator<=>const tuple&, const UTuple&);
> +
> +  static_assert( (tuple{1, 2, 3} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::equal );
> +  static_assert( (tuple{1, 2, 4} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::greater );
> +  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 3}) == std::strong_ordering::equal );
> +  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 4}) == std::strong_ordering::less  );
> +
> +  static_assert( tuple{1, 2, 4} > tuple_like_t{1, 2, 3} );
> +  static_assert( tuple_like_t{1, 2, 3} < tuple{1, 2, 4} );
> +
> +  // template<tuple-like TTuple, tuple-like UTuple, ...>
> +  //   struct basic_common_reference<TTuple, UTuple, ...>;
> +
> +  static_assert( std::same_as<std::common_reference_t<tuple_like_t<int>,
> +						      tuple<int, long, int>>,
> +			      tuple<int, long, int>> );
> +
> +  static_assert( std::same_as<std::common_reference_t<tuple<int, long, int>,
> +						      tuple_like_t<int>>,
> +			      tuple<int, long, int>> );
> +
> +  // template<tuple-like TTuple, tuple-like UTuple>
> +  //   struct common_type<TTuple, UTuple>;
> +
> +  static_assert( std::same_as<std::common_type_t<tuple_like_t<const int&>,
> +						 tuple<int, long, int>>,
> +			      tuple<int, long, int>> );
> +
> +  static_assert( std::same_as<std::common_type_t<tuple<int, long, int>,
> +						 tuple_like_t<const int&>>,
> +			      tuple<int, long, int>> );
> +
> +  return true;
> +}
> +
> +template<typename T>
> +using tuple_like_array = array<T, 3>;
> +
> +int
> +main()
> +{
> +  static_assert( test01<tuple_like_array>() );
> +  static_assert( alloc::test01<tuple_like_array>() );
> +  static_assert( test02<tuple_like_array>() );
> +  static_assert( alloc::test02<tuple_like_array>() );
> +  static_assert( test03<tuple_like_array>() );
> +  static_assert( test04<tuple_like_array>() );
> +  static_assert( test05<tuple_like_array>() );
> +}
> diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> index b7717aed92c..672a8c356d9 100644
> --- a/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> +++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> @@ -41,8 +41,8 @@ test01()
>    VERIFY( i2 == z2.end() );
>    VERIFY( ranges::size(z2) == 2 );
>    VERIFY( ranges::size(std::as_const(z2)) == 2 );
> -  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
> -  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
> +  VERIFY( std::get<0>(z2[0]) == 1 && std::get<1>(z2[0]) == 3 );
> +  VERIFY( std::get<0>(z2[1]) == 2 && std::get<1>(z2[1]) == 4 );
>    for (const auto [x, y] : z2)
>      {
>        VERIFY( y - x == 2 );
> -- 
> 2.43.0.386.ge02ecfcc53
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-31 19:39             ` Patrick Palka
@ 2024-01-31 19:41               ` Patrick Palka
  2024-01-31 21:55                 ` Jonathan Wakely
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2024-01-31 19:41 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, gcc-patches, libstdc++

[-- Attachment #1: Type: text/plain, Size: 88392 bytes --]

On Wed, 31 Jan 2024, Patrick Palka wrote:

> On Wed, 24 Jan 2024, Patrick Palka wrote:
> 
> > On Wed, 24 Jan 2024, Patrick Palka wrote:
> > 
> > > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> > > 
> > > > On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppalka@redhat.com> wrote:
> > > > >
> > > > > On Wed, 24 Jan 2024, Jonathan Wakely wrote:
> > > > >
> > > > > > On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > > > > > > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > > > > > > index b81b479ad43..a9b20fbe7ca 100644
> > > > > > > --- a/libstdc++-v3/include/bits/stl_pair.h
> > > > > > > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > > > > > > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >    /// @cond undocumented
> > > > > > >
> > > > > > >    // Forward declarations.
> > > > > > > +  template<typename, typename>
> > > > > > > +    struct pair;
> > > > > >
> > > > > > We have a compiler bug where a forward declaration without template
> > > > > > parameter names causes bad diagnostics later. The compiler seems to
> > > > > > try to use the parameter names from the first decl it sees, so we end
> > > > > > up with things like <template-argument-1-1> even when there's a name
> > > > > > available at the site of the actual error. So I think we should name
> > > > > > these _T1 and _T2 here.
> > > > >
> > > > > Will fix.
> > > > >
> > > > > >
> > > > > > > +
> > > > > > >    template<typename...>
> > > > > > >      class tuple;
> > > > > > >
> > > > > > > +  // Declarations of std::array and its std::get overloads, so that
> > > > > > > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > > > > > > +  // We also declare the other std::get overloads here so that they're
> > > > > > > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > > > > > > +  template<typename _Tp, size_t _Nm>
> > > > > > > +    struct array;
> > > > > > > +
> > > > > > >    template<size_t...>
> > > > > > >      struct _Index_tuple;
> > > > > > >
> > > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > > > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > > > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > > > > > > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, class _Tp1, class _Tp2>
> > > > > > > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > > > > > > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > > > +    get(tuple<_Elements...>& __t) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > > > > > > +    get(const tuple<_Elements...>& __t) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > > > +    get(tuple<_Elements...>&& __t) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t __i, typename... _Elements>
> > > > > > > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > > > > > > +    get(const tuple<_Elements...>&& __t) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > > +    constexpr _Tp&
> > > > > > > +    get(array<_Tp, _Nm>&) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > > +    constexpr _Tp&&
> > > > > > > +    get(array<_Tp, _Nm>&&) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > > +    constexpr const _Tp&
> > > > > > > +    get(const array<_Tp, _Nm>&) noexcept;
> > > > > > > +
> > > > > > > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > > > > > > +    constexpr const _Tp&&
> > > > > > > +    get(const array<_Tp, _Nm>&&) noexcept;
> > > > > > > +
> > > > > > >  #if ! __cpp_lib_concepts
> > > > > > >    // Concept utility functions, reused in conditionally-explicit
> > > > > > >    // constructors.
> > > > > > > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >  #endif // lib concepts
> > > > > > >  #endif // C++11
> > > > > > >
> > > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > > +  template<typename _Tp>
> > > > > > > +    inline constexpr bool __is_tuple_v = false;
> > > > > > > +
> > > > > > > +  template<typename... _Ts>
> > > > > > > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > > > > > > +
> > > > > > > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > > > > > > +  template<typename _Tp>
> > > > > > > +    inline constexpr bool __is_tuple_like_v = false;
> > > > > > > +
> > > > > > > +  template<typename... _Elements>
> > > > > > > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > > > > > > +
> > > > > > > +  template<typename _T1, typename _T2>
> > > > > > > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > > > > > > +
> > > > > > > +  template<typename _Tp, size_t _Nm>
> > > > > > > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > > > > > > +
> > > > > > > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > > > > > > +
> > > > > > > +  template<typename _Tp>
> > > > > > > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > > > > > > +
> > > > > > > +  template<typename _Tp>
> > > > > > > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > > > > > > +
> > > > > > > +  template<typename _Tp, typename _Tuple>
> > > > > > > +    concept __eligible_tuple_like
> > > > > > > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > > > > > > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > > > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > > > +
> > > > > > > +  template<typename _Tp, typename _Pair>
> > > > > > > +    concept __eligible_pair_like
> > > > > > > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > > > > > > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >    template<typename _U1, typename _U2> class __pair_base
> > > > > > >    {
> > > > > > >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > > > > > > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >           return false;
> > > > > > >  #endif
> > > > > > >         }
> > > > > > > +
> > > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > > +      template<typename _UPair>
> > > > > > > +       static constexpr bool
> > > > > > > +       _S_constructible_from_pair_like()
> > > > > > > +       {
> > > > > > > +         return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > > > +                                 decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > > > +       }
> > > > > > > +
> > > > > > > +      template<typename _UPair>
> > > > > > > +       static constexpr bool
> > > > > > > +       _S_convertible_from_pair_like()
> > > > > > > +       {
> > > > > > > +         return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > > > > > > +                               decltype(std::get<1>(std::declval<_UPair>()))>();
> > > > > > > +       }
> > > > > > > +#endif // C++23
> > > > > > >        /// @endcond
> > > > > > >
> > > > > > >      public:
> > > > > > > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         pair(const pair<_U1, _U2>&&) = delete;
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __glibcxx_tuple_like // >= C++23
> > > > > > > +      template<__eligible_pair_like<pair> _UPair>
> > > > > > > +       requires (_S_constructible_from_pair_like<_UPair>())
> > > > > > > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > > > > > > +       pair(_UPair&& __p)
> > > > > > > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > > > > > > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > > > > > > +       { }
> > > > > > > +#endif // C++23
> > > > > >
> > > > > > I think this needs to be constrained with !_S_dangles<...>() and we
> > > > > > need a deleted overload with the same constraints, except for
> > > > > > _S_dangles being true.
> > > > > >
> > > > > > And that should be covered by a test.
> > > > >
> > > > > Oops, will fix.  Should the deleted overloads carry over the
> > > > > conditionally explicit specifier?  I noticed pair's deleted overloads
> > > > > do, but tuple's overloads don't.
> > > > 
> > > > Huh, oops. Does an explicit ctor participate in overload resolution
> > > > and then get checked if it's usable, or is it remove from overload
> > > > resolution earlier?
> > > > It looks like I decided the answer was the latter for pair and the
> > > > former for tuple.
> > > 
> > > AFAICT if we know the explicitness of the ctor early (either because the
> > > ctor is a non-template or it has a non-dependent explicit-spec), then we
> > > remove it from the overload set early, in which case it'd be useful to
> > > give the deleted ctor the right explicit-spec to avoid unecessary
> > > constraint checking etc.
> > > 
> > > Otherwise, for ctor templates with a dependent explicit-spec such as
> > > these tuple/pair ones, we must wait until after deduction to check
> > > explicitness which means constraints are checked first.  And by then
> > > we already know that __dangles is true, so we presumably want overload
> > > resolution to fail regardless.  Whether that's due to selecting a
> > > (viable) deleted non-explicit ctor (if we omit the explicit-spec) or due
> > > to there being no viable non-explicit ctor (if we carry over the
> > > explicit-spec) shouldn't make a difference, I think?
> > > 
> > > So it seems unnecessary to give these deleted overloads an
> > > explicit-spec; it wouldn't be considered unless overload resolution
> > > is destined to fail anyway.
> > > 
> > > I'm not totally confident about this assessment though so I'll
> > > just carry over the explicit-spec for now.
> > 
> > I ended up hedging my bets and including the explicit-spec in the
> > deleted ctors of pair and omitting it in those of tuple, so that
> > we continue to be locally consistent.
> > 
> > > 
> > > > 
> > > > >
> > > > > >
> > > > > >
> > > > > >
> > > > > > > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > > > > > > index be92f1eb973..182f3cc5e6a 100644
> > > > > > > --- a/libstdc++-v3/include/std/tuple
> > > > > > > +++ b/libstdc++-v3/include/std/tuple
> > > > > > > @@ -50,6 +50,7 @@
> > > > > > >  #define __glibcxx_want_apply
> > > > > > >  #define __glibcxx_want_make_from_tuple
> > > > > > >  #define __glibcxx_want_ranges_zip
> > > > > > > +#define __glibcxx_want_tuple_like
> > > > > > >  #include <bits/version.h>
> > > > > > >
> > > > > > >  namespace std _GLIBCXX_VISIBILITY(default)
> > > > > > > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >        _Head _M_head_impl;
> > > > > > >      };
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > > > > > > +
> > > > > > > +  // Forward declared for use by the operator<=> overload for tuple-like types.
> > > > > > > +  template<typename _Cat, typename _Tp, typename _Up>
> > > > > > > +    constexpr _Cat
> > > > > > > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > > > > > > +
> > > > > > > +  template<typename _Cat, typename _Tp, typename _Up,
> > > > > > > +          size_t _Idx0, size_t... _Idxs>
> > > > > > > +    constexpr _Cat
> > > > > > > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > > > > > > +               index_sequence<_Idx0, _Idxs...>);
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >    /**
> > > > > > >     * Contains the actual implementation of the @c tuple template, stored
> > > > > > >     * as a recursive inheritance hierarchy from the first element (most
> > > > > > > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         { }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _UTuple, size_t... _Is>
> > > > > > > +       constexpr
> > > > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > > > > > > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > > > +       { }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >        template<typename _Alloc>
> > > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > > > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         { }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > > > > > > +       constexpr
> > > > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > > > +                   _UTuple&& __u, index_sequence<_Is...>)
> > > > > > > +       : _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > > > > > > +       { }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >        template<typename... _UElements>
> > > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > > >         void
> > > > > > > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _UTuple>
> > > > > > > +       constexpr void
> > > > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > > > > > > +       {
> > > > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > > > +       }
> > > > > > > +
> > > > > > > +      template<typename _UTuple>
> > > > > > > +       constexpr void
> > > > > > > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > > > > > > +       {
> > > > > > > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > > > > > > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > > > > > > +       }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >      protected:
> > > > > > >        _GLIBCXX20_CONSTEXPR
> > > > > > >        void
> > > > > > > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         { }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _UTuple>
> > > > > > > +       constexpr
> > > > > > > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > > > > > > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > > > > > > +       { }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >        template<typename _Alloc>
> > > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > > >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > > > > > > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         { }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _Alloc, typename _UTuple>
> > > > > > > +       constexpr
> > > > > > > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > > > > > > +                   _UTuple&& __u, index_sequence<0>)
> > > > > > > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > > > > > > +       { }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >        template<typename _UHead>
> > > > > > >         _GLIBCXX20_CONSTEXPR
> > > > > > >         void
> > > > > > > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +    template<typename _UTuple>
> > > > > > > +      constexpr void
> > > > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > > > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > > > +
> > > > > > > +    template<typename _UTuple>
> > > > > > > +      constexpr void
> > > > > > > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > > > > > > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >      protected:
> > > > > > >        _GLIBCXX20_CONSTEXPR
> > > > > > >        void
> > > > > > > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >  #endif
> > > > > > >         }
> > > > > > >
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _UTuple>
> > > > > > > +       static consteval bool
> > > > > > > +       __constructible_from_tuple_like()
> > > > > > > +       {
> > > > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > > > +           return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > > > > +       }
> > > > > > > +
> > > > > > > +      template<typename _UTuple>
> > > > > > > +       static consteval bool
> > > > > > > +       __convertible_from_tuple_like()
> > > > > > > +       {
> > > > > > > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > > > > > > +           return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > > > > > > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > > > > >
> > > > > > These new functions can use index_sequence_for<_Elements...>{} here,
> > > > > > so you don't need the sizeof....
> > > > > > That applies several times below as well.
> > > > > >
> > > > > > I think it's semantically identical, just a little shorter. I don't
> > > > > > know if there's any compilation speed benefit either way. Maybe
> > > > > > sizeof...(_Elements) is cheaper than expanding the pack into the
> > > > > > index_sequence_for alias template?
> > 
> > It probably a little cheaper to use make_index_sequence directly, but I
> > just didn't know about index_sequence_for :) Consider that changed.
> > 
> > > > > >
> > > > > >
> > > > > > > +       }
> > > > > > > +#endif // C++23
> > > > > > > +
> > > > > > >      public:
> > > > > > >        constexpr
> > > > > > >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > > > > > > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         tuple(const pair<_U1, _U2>&&) = delete;
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > > > -      template<__tuple_like _UTuple>
> > > > > > > -       constexpr explicit(...)
> > > > > > > -       tuple(_UTuple&& __u);
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<__eligible_tuple_like<tuple> _UTuple>
> > > > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > > > +       tuple(_UTuple&& __u)
> > > > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > > > +                    std::forward<_UTuple>(__u),
> > > > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > > > +       { }
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > >        // Allocator-extended constructors.
> > > > > > > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > > > > >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> > > > > > >  #endif // C++23
> > > > > > >
> > > > > > > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > > > > > > -      template<typename _Alloc, __tuple_like _UTuple>
> > > > > > > -       constexpr explicit(...)
> > > > > > > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > > > > > > +#if __cpp_lib_tuple_like // >= C++23
> > > > > > > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > > > > > > +       requires (__constructible_from_tuple_like<_UTuple>())
> > > > > > > +         && (!__use_other_ctor<_UTuple>())
> > > > > > > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > > > > > > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > > > > > > +       : _Inherited(__tuple_like_tag_t{},
> > > > > > > +                    __tag, __a, std::forward<_UTuple>(__u),
> > > > > > > +                    make_index_sequence<sizeof...(_Elements)>{})
> > > > > > > +       { }
> > > > > > >  #endif // C++23
> > > > > >
> > > > > > For some reason these two new constructors aren't deleted if they
> > > > > > create dangling refs. I don't know why.
> > > > >
> > > > > Hmm, seems like an oversight.  Shall we proactively implement them?
> > > > 
> > > > Yes, I think so. I can't see why we would want to permit a dangling
> > > > reference there.
> > > > 
> > > > e.g.
> > > > std::array<long, 1> a{};
> > > > std::tuple<const int&> t(a);
> > > 
> > > Sounds good.
> > 
> > In v2:
> > 
> > * Named the template parameters of the forward declaration of pair.
> > * Added dangling checks for the new tuple and pair constructors
> >   and corresponding tests.
> > * Replaced make_index_sequence with index_sequence_for where applicable.
> 
> Ping.

... now also as an attachment since it seems Gmail doesn't like my
inline patch.

> 
> > 
> > -- >8 --
> > 
> > Subject: [PATCH 2/2] libstdc++: Implement P2165R4 changes to
> >  std::pair/tuple/etc
> > 
> > libstdc++-v3/ChangeLog:
> > 
> > 	* include/bits/ranges_util.h (__detail::__pair_like): Don't
> > 	define in C++23 mode.
> > 	(__detail::__pair_like_convertible_from): Adjust as per P2165R4.
> > 	(__detail::__is_subrange<subrange>): Moved from <ranges>.
> > 	(__detail::__is_tuple_like_v<subrange>): Likewise.
> > 	* include/bits/stl_iterator.h: Include <bits/utility.h> for
> > 	C++23.
> > 	(__different_from): Move to <concepts>.
> > 	(__iter_key_t): Adjust for C++23 as per P2165R4.
> > 	(__iter_val_t): Likewise.
> > 	* include/bits/stl_pair.h (pair, array): Forward declare.
> > 	(get): Forward declare all overloads relevant to P2165R4
> > 	tuple-like constructors.
> > 	(__is_tuple_v): Define for C++23.
> > 	(__is_tuple_like_v): Define for C++23.
> > 	(__tuple_like): Define for C++23 as per P2165R4.
> > 	(__pair_like): Define for C++23 as per P2165R4.
> > 	(__eligibile_tuple_like): Define for C++23.
> > 	(__eligibile_pair_like): Define for C++23.
> > 	(pair::_S_constructible_from_pair_like): Define for C++23.
> > 	(pair::_S_convertible_from_pair_like): Define for C++23.
> > 	(pair::_S_dangles_from_pair_like): Define for C++23.
> > 	(pair::pair): Define overloads taking a tuple-like type for
> > 	C++23 as per P2165R4.
> > 	(pair::_S_assignable_from_tuple_like): Define for C++23.
> > 	(pair::_S_const_assignable_from_tuple_like): Define for C++23.
> > 	(pair::operator=): Define overloads taking a tuple-like type for
> > 	C++23 as per P2165R4.
> > 	* include/bits/utility.h (ranges::__detail::__is_subrange):
> > 	Moved from <ranges>.
> > 	* include/bits/version.def (tuple_like): Define for C++23.
> > 	* include/bits/version.h: Regenerate.
> > 	* include/std/concepts (__different_from): Moved from
> > 	<bits/stl_iterator.h>.
> > 	(ranges::__swap::__adl_swap): Clarify which __detail namespace.
> > 	* include/std/map (__cpp_lib_tuple_like): Define C++23.
> > 	* include/std/ranges (__detail::__is_subrange): Moved to
> > 	<bits/utility.h>.
> > 	(__detail::__is_subrange<subrange>): Moved to <bits/ranges_util.h>
> > 	(__detail::__has_tuple_element): Adjust for C++23 as per P2165R4.
> > 	(__detail::__tuple_or_pair): Remove as per P2165R4.  Replace all
> > 	uses with plain tuple as per P2165R4.
> > 	* include/std/tuple (__cpp_lib_tuple_like): Define for C++23.
> > 	(__tuple_like_tag_t): Define for C++23.
> > 	(__tuple_cmp): Forward declare for C++23.
> > 	(_Tuple_impl::_Tuple_impl): Define overloads taking
> > 	__tuple_like_tag_t and a tuple-like type for C++23.
> > 	(_Tuple_impl::_M_assign): Likewise.
> > 	(tuple::__constructible_from_tuple_like): Define for C++23.
> > 	(tuple::__convertible_from_tuple_like): Define for C++23.
> > 	(tuple::__dangles_from_tuple_like): Define for C++23.
> > 	(tuple::tuple): Define overloads taking a tuple-like type for
> > 	C++23 as per P2165R4.
> > 	(tuple::__assignable_from_tuple_like): Define for C++23.
> > 	(tuple::__const_assignable_from_tuple_like): Define for C++23.
> > 	(tuple::operator=): Define overloads taking a tuple-like type
> > 	for C++23 as per P2165R4.
> > 	(tuple::__tuple_like_common_comparison_category): Define for C++23.
> > 	(tuple::operator<=>): Define overload taking a tuple-like type
> > 	for C++23 as per P2165R4.
> > 	(array, get): Forward declarations moved to <bits/stl_pair.h>.
> > 	(tuple_cat): Constrain with __tuple_like for C++23 as per P2165R4.
> > 	(apply): Likewise.
> > 	(make_from_tuple): Likewise.
> > 	(__tuple_like_common_reference): Define for C++23.
> > 	(basic_common_reference): Adjust as per P2165R4.
> > 	(__tuple_like_common_type): Define for C++23.
> > 	(common_type): Adjust as per P2165R4.
> > 	* include/std/unordered_map (__cpp_lib_tuple_like): Define for
> > 	C++23.
> > 	* include/std/utility (__cpp_lib_tuple_like): Define for C++23.
> > 	* testsuite/std/ranges/zip/1.cc (test01): Adjust to handle pair
> > 	and 2-tuple interchangeably.
> > 	* testsuite/20_util/pair/p2165r4.cc: New test.
> > 	* testsuite/20_util/tuple/p2165r4.cc: New test.
> > ---
> >  libstdc++-v3/include/bits/ranges_util.h       |  17 +-
> >  libstdc++-v3/include/bits/stl_iterator.h      |  16 +-
> >  libstdc++-v3/include/bits/stl_pair.h          | 182 ++++++++++
> >  libstdc++-v3/include/bits/utility.h           |   8 +
> >  libstdc++-v3/include/bits/version.def         |   8 +
> >  libstdc++-v3/include/bits/version.h           |  11 +
> >  libstdc++-v3/include/std/concepts             |  11 +-
> >  libstdc++-v3/include/std/map                  |   1 +
> >  libstdc++-v3/include/std/ranges               |  48 +--
> >  libstdc++-v3/include/std/tuple                | 323 ++++++++++++++---
> >  libstdc++-v3/include/std/unordered_map        |   1 +
> >  libstdc++-v3/include/std/utility              |   1 +
> >  .../testsuite/20_util/pair/p2165r4.cc         | 173 +++++++++
> >  .../testsuite/20_util/tuple/p2165r4.cc        | 335 ++++++++++++++++++
> >  libstdc++-v3/testsuite/std/ranges/zip/1.cc    |   4 +-
> >  15 files changed, 1056 insertions(+), 83 deletions(-)
> >  create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
> >  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> > 
> > diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
> > index bb04c49f044..9b79c3a229d 100644
> > --- a/libstdc++-v3/include/bits/ranges_util.h
> > +++ b/libstdc++-v3/include/bits/ranges_util.h
> > @@ -224,6 +224,10 @@ namespace ranges
> >  	&& !__uses_nonqualification_pointer_conversion<decay_t<_From>,
> >  						       decay_t<_To>>;
> >  
> > +#if __glibcxx_tuple_like // >= C++23
> > +    // P2165R4 version of __pair_like is defined in <bits/stl_pair.h>.
> > +#else
> > +    // C++20 version of __pair_like from P2321R2.
> >      template<typename _Tp>
> >        concept __pair_like
> >  	= !is_reference_v<_Tp> && requires(_Tp __t)
> > @@ -235,10 +239,11 @@ namespace ranges
> >  	  { get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
> >  	  { get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
> >  	};
> > +#endif
> >  
> >      template<typename _Tp, typename _Up, typename _Vp>
> >        concept __pair_like_convertible_from
> > -	= !range<_Tp> && __pair_like<_Tp>
> > +	= !range<_Tp> && !is_reference_v<_Vp> && __pair_like<_Tp>
> >  	&& constructible_from<_Tp, _Up, _Vp>
> >  	&& __convertible_to_non_slicing<_Up, tuple_element_t<0, _Tp>>
> >  	&& convertible_to<_Vp, tuple_element_t<1, _Tp>>;
> > @@ -463,8 +468,18 @@ namespace ranges
> >      using borrowed_subrange_t = __conditional_t<borrowed_range<_Range>,
> >  						subrange<iterator_t<_Range>>,
> >  						dangling>;
> > +
> > +  // __is_subrange is defined in <bits/utility.h>.
> > +  template<typename _Iter, typename _Sent, subrange_kind _Kind>
> > +    inline constexpr bool __detail::__is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
> >  } // namespace ranges
> >  
> > +#if __glibcxx_tuple_like // >= C++23
> > +  // __is_tuple_like_v is defined in <bits/stl_pair.h>.
> > +  template<typename _It, typename _Sent, ranges::subrange_kind _Kind>
> > +    inline constexpr bool __is_tuple_like_v<ranges::subrange<_It, _Sent, _Kind>> = true;
> > +#endif
> > +
> >  // The following ranges algorithms are used by <ranges>, and are defined here
> >  // so that <ranges> can avoid including all of <bits/ranges_algo.h>.
> >  namespace ranges
> > diff --git a/libstdc++-v3/include/bits/stl_iterator.h b/libstdc++-v3/include/bits/stl_iterator.h
> > index d71a793e10d..560a10a7abe 100644
> > --- a/libstdc++-v3/include/bits/stl_iterator.h
> > +++ b/libstdc++-v3/include/bits/stl_iterator.h
> > @@ -78,6 +78,10 @@
> >  # include <bits/stl_construct.h>
> >  #endif
> >  
> > +#if __glibcxx_tuple_like // >= C++23
> > +# include <bits/utility.h> // for tuple_element_t
> > +#endif
> > +
> >  namespace std _GLIBCXX_VISIBILITY(default)
> >  {
> >  _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > @@ -95,10 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      template<typename _Cat, typename _Limit, typename _Otherwise = _Cat>
> >        using __clamp_iter_cat
> >  	= __conditional_t<derived_from<_Cat, _Limit>, _Limit, _Otherwise>;
> > -
> > -    template<typename _Tp, typename _Up>
> > -      concept __different_from
> > -	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
> >    }
> >  #endif
> >  
> > @@ -2983,11 +2983,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    // of associative containers.
> >    template<typename _InputIterator>
> >      using __iter_key_t = remove_const_t<
> > +#if __glibcxx_tuple_like // >= C++23
> > +      tuple_element_t<0, typename iterator_traits<_InputIterator>::value_type>>;
> > +#else
> >        typename iterator_traits<_InputIterator>::value_type::first_type>;
> > +#endif
> >  
> >    template<typename _InputIterator>
> >      using __iter_val_t
> > +#if __glibcxx_tuple_like // >= C++23
> > +      = tuple_element_t<1, typename iterator_traits<_InputIterator>::value_type>;
> > +#else
> >        = typename iterator_traits<_InputIterator>::value_type::second_type;
> > +#endif
> >  
> >    template<typename _T1, typename _T2>
> >      struct pair;
> > diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
> > index b81b479ad43..00ec53ebc33 100644
> > --- a/libstdc++-v3/include/bits/stl_pair.h
> > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    /// @cond undocumented
> >  
> >    // Forward declarations.
> > +  template<typename _T1, typename _T2>
> > +    struct pair;
> > +
> >    template<typename...>
> >      class tuple;
> >  
> > +  // Declarations of std::array and its std::get overloads, so that
> > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > +  // We also declare the other std::get overloads here so that they're
> > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > +  template<typename _Tp, size_t _Nm>
> > +    struct array;
> > +
> >    template<size_t...>
> >      struct _Index_tuple;
> >  
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(const tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(const tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&
> > +    get(array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&&
> > +    get(array<_Tp, _Nm>&&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&
> > +    get(const array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&&
> > +    get(const array<_Tp, _Nm>&&) noexcept;
> > +
> >  #if ! __cpp_lib_concepts
> >    // Concept utility functions, reused in conditionally-explicit
> >    // constructors.
> > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif // lib concepts
> >  #endif // C++11
> >  
> > +#if __glibcxx_tuple_like // >= C++23
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_v = false;
> > +
> > +  template<typename... _Ts>
> > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > +
> > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_like_v = false;
> > +
> > +  template<typename... _Elements>
> > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > +
> > +  template<typename _T1, typename _T2>
> > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > +
> > +  template<typename _Tp, size_t _Nm>
> > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > +
> > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > +
> > +  template<typename _Tp>
> > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp>
> > +    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > +
> > +  template<typename _Tp, typename _Tuple>
> > +    concept __eligible_tuple_like
> > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > +	&& (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > +	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp, typename _Pair>
> > +    concept __eligible_pair_like
> > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > +	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +#endif // C++23
> > +
> >    template<typename _U1, typename _U2> class __pair_base
> >    {
> >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > @@ -295,6 +393,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	  return false;
> >  #endif
> >  	}
> > +
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<typename _UPair>
> > +	static constexpr bool
> > +	_S_constructible_from_pair_like()
> > +	{
> > +	  return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > +				  decltype(std::get<1>(std::declval<_UPair>()))>();
> > +	}
> > +
> > +      template<typename _UPair>
> > +	static constexpr bool
> > +	_S_convertible_from_pair_like()
> > +	{
> > +	  return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > +				decltype(std::get<1>(std::declval<_UPair>()))>();
> > +	}
> > +
> > +      template<typename _UPair>
> > +	static constexpr bool
> > +	_S_dangles_from_pair_like()
> > +	{
> > +	  return _S_dangles<decltype(std::get<0>(std::declval<_UPair>())),
> > +			    decltype(std::get<1>(std::declval<_UPair>()))>();
> > +	}
> > +#endif // C++23
> >        /// @endcond
> >  
> >      public:
> > @@ -393,6 +517,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	pair(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >  
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<__eligible_pair_like<pair> _UPair>
> > +	requires (_S_constructible_from_pair_like<_UPair>())
> > +	  && (!_S_dangles_from_pair_like<_UPair>())
> > +	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > +	pair(_UPair&& __p)
> > +	: first(std::get<0>(std::forward<_UPair>(__p))),
> > +	  second(std::get<1>(std::forward<_UPair>(__p)))
> > +	{ }
> > +
> > +      template<__eligible_pair_like<pair> _UPair>
> > +	requires (_S_constructible_from_pair_like<_UPair>())
> > +	  && (_S_dangles_from_pair_like<_UPair>())
> > +	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > +	pair(_UPair&&) = delete;
> > +#endif // C++23
> > +
> >    private:
> >        /// @cond undocumented
> >        template<typename _U1, typename _U2>
> > @@ -421,6 +562,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	    return is_nothrow_assignable_v<_T2&, _U2>;
> >  	  return false;
> >  	}
> > +
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<typename _UPair>
> > +	static constexpr bool
> > +	_S_assignable_from_tuple_like()
> > +	{
> > +	  return _S_assignable<decltype(std::get<0>(std::declval<_UPair>())),
> > +			       decltype(std::get<1>(std::declval<_UPair>()))>();
> > +	}
> > +
> > +      template<typename _UPair>
> > +	static constexpr bool
> > +	_S_const_assignable_from_tuple_like()
> > +	{
> > +	  return _S_const_assignable<decltype(std::get<0>(std::declval<_UPair>())),
> > +				     decltype(std::get<1>(std::declval<_UPair>()))>();
> > +	}
> > +#endif // C++23
> >        /// @endcond
> >  
> >    public:
> > @@ -516,6 +675,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	  return *this;
> >  	}
> >  #endif // C++23
> > +
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<__eligible_pair_like<pair> _UPair>
> > +	requires (_S_assignable_from_tuple_like<_UPair>())
> > +	constexpr pair&
> > +	operator=(_UPair&& __p)
> > +	{
> > +	  first = std::get<0>(std::forward<_UPair>(__p));
> > +	  second = std::get<1>(std::forward<_UPair>(__p));
> > +	  return *this;
> > +	}
> > +
> > +      template<__eligible_pair_like<pair> _UPair>
> > +	requires (_S_const_assignable_from_tuple_like<_UPair>())
> > +	constexpr const pair&
> > +	operator=(_UPair&& __p) const
> > +	{
> > +	  first = std::get<0>(std::forward<_UPair>(__p));
> > +	  second = std::get<1>(std::forward<_UPair>(__p));
> > +	  return *this;
> > +	}
> > +#endif // C++23
> > +
> >  #else // !__cpp_lib_concepts
> >        // C++11/14/17 implementation using enable_if, partially constexpr.
> >  
> > diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> > index d8a5fb960fe..2a741bf7000 100644
> > --- a/libstdc++-v3/include/bits/utility.h
> > +++ b/libstdc++-v3/include/bits/utility.h
> > @@ -266,6 +266,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif
> >  #endif
> >  
> > +#if __glibcxx_ranges
> > +  namespace ranges::__detail
> > +  {
> > +    template<typename _Range>
> > +      inline constexpr bool __is_subrange = false;
> > +  } // namespace __detail
> > +#endif
> > +
> >  _GLIBCXX_END_NAMESPACE_VERSION
> >  } // namespace
> >  
> > diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> > index 8fb8a2877ee..502961eb269 100644
> > --- a/libstdc++-v3/include/bits/version.def
> > +++ b/libstdc++-v3/include/bits/version.def
> > @@ -1780,6 +1780,14 @@ ftms = {
> >    };
> >  };
> >  
> > +ftms = {
> > +  name = tuple_like;
> > +  values = {
> > +    v = 202207;
> > +    cxxmin = 23;
> > +  };
> > +};
> > +
> >  // Standard test specifications.
> >  stds[97] = ">= 199711L";
> >  stds[03] = ">= 199711L";
> > diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> > index 9ba99deeda6..511030bde47 100644
> > --- a/libstdc++-v3/include/bits/version.h
> > +++ b/libstdc++-v3/include/bits/version.h
> > @@ -2169,4 +2169,15 @@
> >  #endif /* !defined(__cpp_lib_generator) && defined(__glibcxx_want_generator) */
> >  #undef __glibcxx_want_generator
> >  
> > +// from version.def line 1774
> > +#if !defined(__cpp_lib_tuple_like)
> > +# if (__cplusplus >= 202100L)
> > +#  define __glibcxx_tuple_like 202207L
> > +#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_tuple_like)
> > +#   define __cpp_lib_tuple_like 202207L
> > +#  endif
> > +# endif
> > +#endif /* !defined(__cpp_lib_tuple_like) && defined(__glibcxx_want_tuple_like) */
> > +#undef __glibcxx_want_tuple_like
> > +
> >  #undef __glibcxx_want_all
> > diff --git a/libstdc++-v3/include/std/concepts b/libstdc++-v3/include/std/concepts
> > index 66ed3714b25..4f3e059b051 100644
> > --- a/libstdc++-v3/include/std/concepts
> > +++ b/libstdc++-v3/include/std/concepts
> > @@ -62,6 +62,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      concept same_as
> >        = __detail::__same_as<_Tp, _Up> && __detail::__same_as<_Up, _Tp>;
> >  
> > +  namespace __detail
> > +  {
> > +    template<typename _Tp, typename _Up>
> > +      concept __different_from
> > +	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
> > +  } // namespace __detail
> > +
> >    /// [concept.derived], concept derived_from
> >    template<typename _Derived, typename _Base>
> >      concept derived_from = __is_base_of(_Base, _Derived)
> > @@ -185,8 +192,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  
> >        template<typename _Tp, typename _Up>
> >  	concept __adl_swap
> > -	  = (__detail::__class_or_enum<remove_reference_t<_Tp>>
> > -	    || __detail::__class_or_enum<remove_reference_t<_Up>>)
> > +	  = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
> > +	    || std::__detail::__class_or_enum<remove_reference_t<_Up>>)
> >  	  && requires(_Tp&& __t, _Up&& __u) {
> >  	    swap(static_cast<_Tp&&>(__t), static_cast<_Up&&>(__u));
> >  	  };
> > diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
> > index dcfd222d173..4a96e59a5bc 100644
> > --- a/libstdc++-v3/include/std/map
> > +++ b/libstdc++-v3/include/std/map
> > @@ -74,6 +74,7 @@
> >  #define __glibcxx_want_map_try_emplace
> >  #define __glibcxx_want_node_extract
> >  #define __glibcxx_want_nonmember_container_access
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >  
> >  #if __cplusplus >= 201703L
> > diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
> > index f2413badd9c..7d739852677 100644
> > --- a/libstdc++-v3/include/std/ranges
> > +++ b/libstdc++-v3/include/std/ranges
> > @@ -2389,11 +2389,7 @@ namespace views::__adaptor
> >  	inline constexpr bool __is_basic_string_view<basic_string_view<_CharT, _Traits>>
> >  	  = true;
> >  
> > -      template<typename _Range>
> > -	inline constexpr bool __is_subrange = false;
> > -
> > -      template<typename _Iter, typename _Sent, subrange_kind _Kind>
> > -	inline constexpr bool __is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
> > +      using ranges::__detail::__is_subrange;
> >  
> >        template<typename _Range>
> >  	inline constexpr bool __is_iota_view = false;
> > @@ -4166,6 +4162,10 @@ namespace views::__adaptor
> >  
> >    namespace __detail
> >    {
> > +#if __cpp_lib_tuple_like // >= C++23
> > +    template<typename _Tp, size_t _Nm>
> > +    concept __has_tuple_element = __tuple_like<_Tp> && _Nm < tuple_size_v<_Tp>;
> > +#else
> >      template<typename _Tp, size_t _Nm>
> >      concept __has_tuple_element = requires(_Tp __t)
> >        {
> > @@ -4175,6 +4175,7 @@ namespace views::__adaptor
> >  	{ std::get<_Nm>(__t) }
> >  	  -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
> >        };
> > +#endif
> >  
> >      template<typename _Tp, size_t _Nm>
> >        concept __returnable_element
> > @@ -4559,23 +4560,12 @@ namespace views::__adaptor
> >  	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
> >  	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
> >  
> > -    template<typename... _Ts>
> > -      struct __tuple_or_pair
> > -      { using type = std::tuple<_Ts...>; };
> > -
> > -    template<typename _Tp, typename _Up>
> > -      struct __tuple_or_pair<_Tp, _Up>
> > -      { using type = pair<_Tp, _Up>; };
> > -
> > -    template<typename... _Ts>
> > -      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
> > -
> >      template<typename _Fp, typename _Tuple>
> >        constexpr auto
> >        __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
> >        {
> >  	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> > -	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
> > +	  return tuple<invoke_result_t<_Fp&, _Ts>...>
> >  	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
> >  	}, std::forward<_Tuple>(__tuple));
> >        }
> > @@ -4696,7 +4686,7 @@ namespace views::__adaptor
> >  #ifdef __clang__ // LLVM-61763 workaround
> >    public:
> >  #endif
> > -    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
> > +    tuple<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
> >  
> >      constexpr explicit
> >      _Iterator(decltype(_M_current) __current)
> > @@ -4728,7 +4718,7 @@ namespace views::__adaptor
> >      // iterator_category defined in __zip_view_iter_cat
> >      using iterator_concept = decltype(_S_iter_concept());
> >      using value_type
> > -      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
> > +      = tuple<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
> >      using difference_type
> >        = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
> >  
> > @@ -4900,7 +4890,7 @@ namespace views::__adaptor
> >    template<bool _Const>
> >    class zip_view<_Vs...>::_Sentinel
> >    {
> > -    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
> > +    tuple<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
> >  
> >      constexpr explicit
> >      _Sentinel(decltype(_M_end) __end)
> > @@ -8325,8 +8315,7 @@ namespace views::__adaptor
> >  		    && __detail::__cartesian_product_is_common<_First, _Vs...>)
> >      {
> >        auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
> > -	using _Ret = __detail::__tuple_or_pair_t<iterator_t<_First>,
> > -						 iterator_t<_Vs>...>;
> > +	using _Ret = tuple<iterator_t<_First>, iterator_t<_Vs>...>;
> >  	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
> >  	auto& __first = std::get<0>(_M_bases);
> >  	return _Ret{(__empty_tail
> > @@ -8342,8 +8331,7 @@ namespace views::__adaptor
> >      end() const requires __detail::__cartesian_product_is_common<const _First, const _Vs...>
> >      {
> >        auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
> > -	using _Ret = __detail::__tuple_or_pair_t<iterator_t<const _First>,
> > -						 iterator_t<const _Vs>...>;
> > +	using _Ret = tuple<iterator_t<const _First>, iterator_t<const _Vs>...>;
> >  	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
> >  	auto& __first = std::get<0>(_M_bases);
> >  	return _Ret{(__empty_tail
> > @@ -8416,8 +8404,8 @@ namespace views::__adaptor
> >    {
> >      using _Parent = __maybe_const_t<_Const, cartesian_product_view>;
> >      _Parent* _M_parent = nullptr;
> > -    __detail::__tuple_or_pair_t<iterator_t<__maybe_const_t<_Const, _First>>,
> > -				iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
> > +    tuple<iterator_t<__maybe_const_t<_Const, _First>>,
> > +	  iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
> >  
> >      constexpr
> >      _Iterator(_Parent& __parent, decltype(_M_current) __current)
> > @@ -8444,11 +8432,11 @@ namespace views::__adaptor
> >      using iterator_category = input_iterator_tag;
> >      using iterator_concept = decltype(_S_iter_concept());
> >      using value_type
> > -      = __detail::__tuple_or_pair_t<range_value_t<__maybe_const_t<_Const, _First>>,
> > -				    range_value_t<__maybe_const_t<_Const, _Vs>>...>;
> > +      = tuple<range_value_t<__maybe_const_t<_Const, _First>>,
> > +	      range_value_t<__maybe_const_t<_Const, _Vs>>...>;
> >      using reference
> > -      = __detail::__tuple_or_pair_t<range_reference_t<__maybe_const_t<_Const, _First>>,
> > -				    range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
> > +      = tuple<range_reference_t<__maybe_const_t<_Const, _First>>,
> > +	      range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
> >      using difference_type = decltype(cartesian_product_view::_S_difference_type());
> >  
> >      _Iterator() = default;
> > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > index be92f1eb973..ba364d6a4f8 100644
> > --- a/libstdc++-v3/include/std/tuple
> > +++ b/libstdc++-v3/include/std/tuple
> > @@ -50,6 +50,7 @@
> >  #define __glibcxx_want_apply
> >  #define __glibcxx_want_make_from_tuple
> >  #define __glibcxx_want_ranges_zip
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >  
> >  namespace std _GLIBCXX_VISIBILITY(default)
> > @@ -246,6 +247,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        _Head _M_head_impl;
> >      };
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > +
> > +  // These forward declarations are used by the operator<=> overload for
> > +  // tuple-like types.
> > +  template<typename _Cat, typename _Tp, typename _Up>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > +
> > +  template<typename _Cat, typename _Tp, typename _Up,
> > +	   size_t _Idx0, size_t... _Idxs>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > +		index_sequence<_Idx0, _Idxs...>);
> > +#endif // C++23
> > +
> >    /**
> >     * Contains the actual implementation of the @c tuple template, stored
> >     * as a recursive inheritance hierarchy from the first element (most
> > @@ -342,6 +359,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	{ }
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple, size_t... _Is>
> > +	constexpr
> > +	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
> > +	: _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +	{ }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >  	_GLIBCXX20_CONSTEXPR
> >  	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -428,6 +453,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	{ }
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > +	constexpr
> > +	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > +		    _UTuple&& __u, index_sequence<_Is...>)
> > +	: _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +	{ }
> > +#endif // C++23
> > +
> >        template<typename... _UElements>
> >  	_GLIBCXX20_CONSTEXPR
> >  	void
> > @@ -470,6 +504,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	}
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +	constexpr void
> > +	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > +	{
> > +	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +	}
> > +
> > +      template<typename _UTuple>
> > +	constexpr void
> > +	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > +	{
> > +	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +	}
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -563,6 +615,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	{ }
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +	constexpr
> > +	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > +	: _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > +	{ }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >  	_GLIBCXX20_CONSTEXPR
> >  	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -633,6 +693,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	{ }
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple>
> > +	constexpr
> > +	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
> > +		    _UTuple&& __u, index_sequence<0>)
> > +	: _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > +	{ }
> > +#endif // C++23
> > +
> >        template<typename _UHead>
> >  	_GLIBCXX20_CONSTEXPR
> >  	void
> > @@ -667,6 +736,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	}
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -846,6 +927,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif
> >  	}
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +	static consteval bool
> > +	__dangles_from_tuple_like()
> > +	{
> > +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return __dangles<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +
> > +      template<typename _UTuple>
> > +	static consteval bool
> > +	__constructible_from_tuple_like()
> > +	{
> > +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +
> > +      template<typename _UTuple>
> > +	static consteval bool
> > +	__convertible_from_tuple_like()
> > +	{
> > +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +#endif // C++23
> > +
> >      public:
> >        constexpr
> >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
> > @@ -1016,10 +1126,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	tuple(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >  
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<__tuple_like _UTuple>
> > -	constexpr explicit(...)
> > -	tuple(_UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +	requires (__constructible_from_tuple_like<_UTuple>())
> > +	  && (!__use_other_ctor<_UTuple>())
> > +	  && (!__dangles_from_tuple_like<_UTuple>())
> > +	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +	tuple(_UTuple&& __u)
> > +	: _Inherited(__tuple_like_tag_t{},
> > +		     std::forward<_UTuple>(__u),
> > +		     index_sequence_for<_Elements...>{})
> > +	{ }
> > +
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +	requires (__constructible_from_tuple_like<_UTuple>())
> > +	  && (!__use_other_ctor<_UTuple>())
> > +	  && (__dangles_from_tuple_like<_UTuple>())
> > +	tuple(_UTuple&&) = delete;
> >  #endif // C++23
> >  
> >        // Allocator-extended constructors.
> > @@ -1202,10 +1325,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >  
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<typename _Alloc, __tuple_like _UTuple>
> > -	constexpr explicit(...)
> > -	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > +	requires (__constructible_from_tuple_like<_UTuple>())
> > +	  && (!__use_other_ctor<_UTuple>())
> > +	  && (!__dangles_from_tuple_like<_UTuple>())
> > +	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > +	: _Inherited(__tuple_like_tag_t{},
> > +		     __tag, __a, std::forward<_UTuple>(__u),
> > +		     index_sequence_for<_Elements...>{})
> > +	{ }
> > +
> > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > +	requires (__constructible_from_tuple_like<_UTuple>())
> > +	  && (!__use_other_ctor<_UTuple>())
> > +	  && (__dangles_from_tuple_like<_UTuple>())
> > +	tuple(allocator_arg_t, const _Alloc&, _UTuple&&) = delete;
> >  #endif // C++23
> >  
> >  #else // !(concepts && conditional_explicit)
> > @@ -1539,6 +1675,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	}
> >  #endif // C++23
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +	static consteval bool
> > +	__assignable_from_tuple_like()
> > +	{
> > +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return __assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +
> > +      template<typename _UTuple>
> > +	static consteval bool
> > +	__const_assignable_from_tuple_like()
> > +	{
> > +	  return []<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return __const_assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +#endif // C++23
> > +
> >      public:
> >  
> >        tuple& operator=(const tuple& __u) = delete;
> > @@ -1661,14 +1817,59 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  	}
> >  #endif // C++23
> >  
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<__tuple_like _UTuple>
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +	requires (__assignable_from_tuple_like<_UTuple>())
> >  	constexpr tuple&
> > -	operator=(_UTuple&& __u);
> > +	operator=(_UTuple&& __u)
> > +	{
> > +	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
> > +	  return *this;
> > +	}
> > +
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +	requires (__const_assignable_from_tuple_like<_UTuple>())
> > +	constexpr const tuple&
> > +	operator=(_UTuple&& __u) const
> > +	{
> > +	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
> > +	  return *this;
> > +	}
> >  
> >        template<__tuple_like _UTuple>
> > -	constexpr tuple&
> > -	operator=(_UTuple&& __u) const;
> > +	requires (!__is_tuple_v<_UTuple>)
> > +	friend constexpr bool
> > +	operator==(const tuple& __t, const _UTuple& __u)
> > +	{
> > +	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
> > +	      "tuple objects can only be compared if they have equal sizes.");
> > +	  return [&]<size_t... _Is>(index_sequence<_Is...>) {
> > +	    return (bool(std::get<_Is>(__t) == std::get<_Is>(__u))
> > +		    && ...);
> > +	  }(index_sequence_for<_Elements...>{});
> > +	}
> > +
> > +      template<__tuple_like _UTuple,
> > +	       typename = make_index_sequence<tuple_size_v<_UTuple>>>
> > +	struct __tuple_like_common_comparison_category;
> > +
> > +      template<__tuple_like _UTuple, size_t... _Is>
> > +	requires requires
> > +	  { typename void_t<__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>; }
> > +	struct __tuple_like_common_comparison_category<_UTuple, index_sequence<_Is...>>
> > +	{
> > +	  using type = common_comparison_category_t
> > +	    <__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>;
> > +	};
> > +
> > +      template<__tuple_like _UTuple>
> > +	requires (!__is_tuple_v<_UTuple>)
> > +	friend constexpr typename __tuple_like_common_comparison_category<_UTuple>::type
> > +	operator<=>(const tuple& __t, const _UTuple& __u)
> > +	{
> > +	  using _Cat = typename __tuple_like_common_comparison_category<_UTuple>::type;
> > +	  return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Elements...>());
> > +	}
> >  #endif // C++23
> >  
> >  #else // ! (concepts && consteval)
> > @@ -2433,27 +2634,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      forward_as_tuple(_Elements&&... __args) noexcept
> >      { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }
> >  
> > -  // Declarations of std::array and its std::get overloads, so that
> > -  // std::tuple_cat can use them if <tuple> is included before <array>.
> > -
> > -  template<typename _Tp, size_t _Nm> struct array;
> > -
> > -  template<size_t _Int, typename _Tp, size_t _Nm>
> > -    constexpr _Tp&
> > -    get(array<_Tp, _Nm>&) noexcept;
> > -
> > -  template<size_t _Int, typename _Tp, size_t _Nm>
> > -    constexpr _Tp&&
> > -    get(array<_Tp, _Nm>&&) noexcept;
> > -
> > -  template<size_t _Int, typename _Tp, size_t _Nm>
> > -    constexpr const _Tp&
> > -    get(const array<_Tp, _Nm>&) noexcept;
> > -
> > -  template<size_t _Int, typename _Tp, size_t _Nm>
> > -    constexpr const _Tp&&
> > -    get(const array<_Tp, _Nm>&&) noexcept;
> > -
> >    /// @cond undocumented
> >    template<size_t, typename, typename, size_t>
> >      struct __make_tuple_impl;
> > @@ -2569,8 +2749,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    /// @endcond
> >  
> >    /// Create a `tuple` containing all elements from multiple tuple-like objects
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  template<__tuple_like... _Tpls>
> > +#else
> >    template<typename... _Tpls, typename = typename
> >             enable_if<__and_<__is_tuple_like<_Tpls>...>::value>::type>
> > +#endif
> >      constexpr auto
> >      tuple_cat(_Tpls&&... __tpls)
> >      -> typename __tuple_cat_result<_Tpls...>::__type
> > @@ -2722,7 +2906,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
> >      }
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  template <typename _Fn, __tuple_like _Tuple>
> > +#else
> >    template <typename _Fn, typename _Tuple>
> > +#endif
> >      constexpr decltype(auto)
> >      apply(_Fn&& __f, _Tuple&& __t)
> >      noexcept(__unpack_std_tuple<is_nothrow_invocable, _Fn, _Tuple>)
> > @@ -2741,7 +2929,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      __make_from_tuple_impl(_Tuple&& __t, index_sequence<_Idx...>)
> >      { return _Tp(std::get<_Idx>(std::forward<_Tuple>(__t))...); }
> >  
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  template <typename _Tp, __tuple_like _Tuple>
> > +#else
> >    template <typename _Tp, typename _Tuple>
> > +#endif
> >      constexpr _Tp
> >      make_from_tuple(_Tuple&& __t)
> >      noexcept(__unpack_std_tuple<is_nothrow_constructible, _Tp, _Tuple>)
> > @@ -2759,17 +2951,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      }
> >  #endif
> >  
> > -#if __cpp_lib_ranges_zip // >= C++23
> > -  template<typename... _TTypes, typename... _UTypes,
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> > +	   template<typename> class _TQual, template<typename> class _UQual,
> > +	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
> > +  struct __tuple_like_common_reference;
> > +
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> > +	   template<typename> class _TQual, template<typename> class _UQual,
> > +	   size_t... _Is>
> > +    requires requires
> > +      { typename tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
> > +					  _UQual<tuple_element_t<_Is, _UTuple>>>...>; }
> > +  struct __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual, index_sequence<_Is...>>
> > +  {
> > +    using type = tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
> > +					  _UQual<tuple_element_t<_Is, _UTuple>>>...>;
> > +  };
> > +
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> >  	   template<typename> class _TQual, template<typename> class _UQual>
> > -    requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; }
> > -  struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual>
> > -  { using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; };
> > -
> > -  template<typename... _TTypes, typename... _UTypes>
> > -    requires requires { typename tuple<common_type_t<_TTypes, _UTypes>...>; }
> > -  struct common_type<tuple<_TTypes...>, tuple<_UTypes...>>
> > -  { using type = tuple<common_type_t<_TTypes, _UTypes>...>; };
> > +    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
> > +      && is_same_v<_TTuple, decay_t<_TTuple>>
> > +      && is_same_v<_UTuple, decay_t<_UTuple>>
> > +      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
> > +      && requires { typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type; }
> > +  struct basic_common_reference<_TTuple, _UTuple, _TQual, _UQual>
> > +  {
> > +    using type = typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type;
> > +  };
> > +
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple,
> > +	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
> > +  struct __tuple_like_common_type;
> > +
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple, size_t... _Is>
> > +    requires requires
> > +      { typename tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
> > +				   tuple_element_t<_Is, _UTuple>>...>; }
> > +  struct __tuple_like_common_type<_TTuple, _UTuple, index_sequence<_Is...>>
> > +  {
> > +    using type = tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
> > +				     tuple_element_t<_Is, _UTuple>>...>;
> > +  };
> > +
> > +  template<__tuple_like _TTuple, __tuple_like _UTuple>
> > +    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
> > +      && is_same_v<_TTuple, decay_t<_TTuple>>
> > +      && is_same_v<_UTuple, decay_t<_UTuple>>
> > +      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
> > +      && requires { typename __tuple_like_common_type<_TTuple, _UTuple>::type; }
> > +  struct common_type<_TTuple, _UTuple>
> > +  {
> > +    using type = typename __tuple_like_common_type<_TTuple, _UTuple>::type;
> > +  };
> >  #endif // C++23
> >  
> >    /// @}
> > diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/include/std/unordered_map
> > index efad0cef584..ea6129d6494 100644
> > --- a/libstdc++-v3/include/std/unordered_map
> > +++ b/libstdc++-v3/include/std/unordered_map
> > @@ -51,6 +51,7 @@
> >  #define __glibcxx_want_node_extract
> >  #define __glibcxx_want_nonmember_container_access
> >  #define __glibcxx_want_unordered_map_try_emplace
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >  
> >  #if __cplusplus >= 201703L
> > diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
> > index f113d572e59..212513f6f48 100644
> > --- a/libstdc++-v3/include/std/utility
> > +++ b/libstdc++-v3/include/std/utility
> > @@ -92,6 +92,7 @@
> >  #define __glibcxx_want_tuple_element_t
> >  #define __glibcxx_want_tuples_by_type
> >  #define __glibcxx_want_unreachable
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >  
> >  namespace std _GLIBCXX_VISIBILITY(default)
> > diff --git a/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
> > new file mode 100644
> > index 00000000000..ef06df1c53f
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
> > @@ -0,0 +1,173 @@
> > +// Verify P2165R4 enhancements to std::pair.
> > +// { dg-do run { target c++23 } }
> > +
> > +#include <array>
> > +#include <tuple>
> > +#include <utility>
> > +#include <testsuite_hooks.h>
> > +
> > +using std::array;
> > +using std::pair;
> > +using std::tuple;
> > +
> > +struct A { };
> > +
> > +template<template<typename> class pair_like_t>
> > +constexpr bool
> > +test01()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr B(A&) : m(0) { }
> > +    constexpr B(A&&) : m(1) { }
> > +    constexpr B(const A&) : m(2) { }
> > +    constexpr B(const A&&) : m(3) { }
> > +  };
> > +
> > +  // template<pair-like UPair>
> > +  //   constexpr explicit(false) pair(UPair&&);
> > +
> > +  pair_like_t<A> pair_like;
> > +
> > +  [&] {
> > +    pair<B, B> p2b = pair_like;
> > +    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b = std::move(pair_like);
> > +    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b = std::as_const(pair_like);
> > +    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b = std::move(std::as_const(pair_like));
> > +    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> > +  }();
> > +
> > +  // Verify dangling checks.
> > +  static_assert( !std::is_constructible_v<pair<const int&, int>, pair_like_t<long>> );
> > +  static_assert( !std::is_constructible_v<pair<int, const int&>, pair_like_t<long>> );
> > +
> > +  return true;
> > +}
> > +
> > +template<template<typename> class pair_like_t>
> > +constexpr bool
> > +test02()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr explicit B(A&) : m(0) { }
> > +    constexpr explicit B(A&&) : m(1) { }
> > +    constexpr explicit B(const A&) : m(2) { }
> > +    constexpr explicit B(const A&&) : m(3) { }
> > +  };
> > +
> > +  // template<pair-like UPair>
> > +  //   constexpr explicit(true) pair(UPair&&);
> > +
> > +  static_assert( !std::is_convertible_v<pair_like_t<A>, pair<B, B>> );
> > +
> > +  pair_like_t<A> pair_like;
> > +
> > +  [&] {
> > +    pair<B, B> p2b{pair_like};
> > +    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b{std::move(pair_like)};
> > +    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b{std::as_const(pair_like)};
> > +    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> > +  }();
> > +  [&] {
> > +    pair<B, B> p2b{std::move(std::as_const(pair_like))};
> > +    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> > +  }();
> > +
> > +  return true;
> > +}
> > +
> > +template<template<typename> class pair_like_t>
> > +constexpr bool
> > +test03()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr B& operator=(A&) { m = 0; return *this; }
> > +    constexpr B& operator=(A&&) { m = 1; return *this; }
> > +    constexpr B& operator=(const A&) { m = 2; return *this; }
> > +    constexpr B& operator=(const A&&) { m = 3; return *this; }
> > +  };
> > +
> > +  // template<pair-like UPair>
> > +  //   constexpr pair& operator=(UPair&&);
> > +
> > +  pair_like_t<A> pair_like;
> > +
> > +  pair<B, B> p2b;
> > +  p2b = pair_like;
> > +  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> > +  p2b = std::move(pair_like);
> > +  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> > +  p2b = std::as_const(pair_like);
> > +  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> > +  p2b = std::move(std::as_const(pair_like));
> > +  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> > +
> > +  return true;
> > +}
> > +
> > +template<template<typename> class pair_like_t>
> > +constexpr bool
> > +test04()
> > +{
> > +  struct B {
> > +    mutable int m;
> > +    constexpr const B& operator=(A&) const { m = 0; return *this; }
> > +    constexpr const B& operator=(A&&) const { m = 1; return *this; }
> > +    constexpr const B& operator=(const A&) const { m = 2; return *this; }
> > +    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
> > +  };
> > +
> > +  // template<pair-like UPair>
> > +  //   constexpr const pair& operator=(UPair&&) const;
> > +
> > +  pair_like_t<A> pair_like;
> > +
> > +  const pair<B, B> p2b;
> > +  p2b = pair_like;
> > +  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
> > +  p2b = std::move(pair_like);
> > +  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
> > +  p2b = std::as_const(pair_like);
> > +  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
> > +  p2b = std::move(std::as_const(pair_like));
> > +  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
> > +
> > +  return true;
> > +}
> > +
> > +template<typename T>
> > +using pair_like_array = array<T, 2>;
> > +
> > +template<typename T>
> > +using pair_like_tuple = tuple<T, T>;
> > +
> > +int
> > +main()
> > +{
> > +  static_assert( test01<pair_like_array>() );
> > +  static_assert( test02<pair_like_array>() );
> > +  static_assert( test03<pair_like_array>() );
> > +  static_assert( test04<pair_like_array>() );
> > +
> > +  static_assert( test01<pair_like_tuple>() );
> > +  static_assert( test02<pair_like_tuple>() );
> > +  static_assert( test03<pair_like_tuple>() );
> > +  static_assert( test04<pair_like_tuple>() );
> > +}
> > diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> > new file mode 100644
> > index 00000000000..e2437c469b6
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
> > @@ -0,0 +1,335 @@
> > +// Verify P2165R4 enhancements to std::tuple.
> > +// { dg-do run { target c++23 } }
> > +
> > +#include <array>
> > +#include <tuple>
> > +#include <utility>
> > +#include <memory>
> > +#include <testsuite_hooks.h>
> > +
> > +using std::array;
> > +using std::pair;
> > +using std::tuple;
> > +using std::allocator;
> > +using std::allocator_arg_t;
> > +using std::allocator_arg;
> > +
> > +namespace alloc {
> > +  struct B01;
> > +  struct B02;
> > +}
> > +
> > +template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
> > +template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
> > +
> > +struct A { };
> > +
> > +template<template<typename> class tuple_like_t>
> > +constexpr bool
> > +test01()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr B(A&) : m(0) { }
> > +    constexpr B(A&&) : m(1) { }
> > +    constexpr B(const A&) : m(2) { }
> > +    constexpr B(const A&&) : m(3) { }
> > +  };
> > +
> > +  // template<tuple-like UTuple>
> > +  //   constexpr explicit(false) tuple(UTuple&&);
> > +
> > +  tuple_like_t<A> tuple_like;
> > +
> > +  [&] {
> > +    tuple<B, B, B> t3b = tuple_like;
> > +    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b = std::move(tuple_like);
> > +    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b = std::as_const(tuple_like);
> > +    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b = std::move(std::as_const(tuple_like));
> > +    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +  }();
> > +
> > +  // Verify dangling checks.
> > +  static_assert( !std::is_constructible_v<tuple<const int&, int, int>, tuple_like_t<long>> );
> > +  static_assert( !std::is_constructible_v<tuple<int, const int&, int>, tuple_like_t<long>> );
> > +  static_assert( !std::is_constructible_v<tuple<int, int, const int&>, tuple_like_t<long>> );
> > +
> > +  return true;
> > +}
> > +
> > +namespace alloc
> > +{
> > +  struct B01 {
> > +    int m;
> > +    B01(A&);
> > +    B01(A&&);
> > +    B01(const A&);
> > +    B01(const A&&);
> > +    constexpr B01(allocator_arg_t, allocator<int>, A&) : m(0) { }
> > +    constexpr B01(allocator_arg_t, allocator<int>, A&&) : m(1) { }
> > +    constexpr B01(allocator_arg_t, allocator<int>, const A&) : m(2) { }
> > +    constexpr B01(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
> > +  };
> > +
> > +  template<template<typename> class tuple_like_t>
> > +  constexpr bool
> > +  test01()
> > +  {
> > +    using B = B01;
> > +
> > +    // template<tuple-like UTuple>
> > +    //   constexpr explicit(false) tuple(allocator_arg_t, const Alloc&, UTuple&&);
> > +
> > +    tuple_like_t<A> tuple_like;
> > +
> > +    [&] {
> > +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, tuple_like};
> > +      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(tuple_like)};
> > +      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
> > +      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
> > +      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +    }();
> > +
> > +  // Verify dangling checks.
> > +    static_assert( !std::is_constructible_v<tuple<const int&, int, int>,
> > +					    allocator_arg_t, allocator<int>,
> > +					    tuple_like_t<long>> );
> > +    static_assert( !std::is_constructible_v<tuple<int, const int&, int>,
> > +					    allocator_arg_t, allocator<int>,
> > +					    tuple_like_t<long>> );
> > +    static_assert( !std::is_constructible_v<tuple<int, int, const int&>,
> > +					    allocator_arg_t, allocator<int>,
> > +					    tuple_like_t<long>> );
> > +
> > +    return true;
> > +  }
> > +}
> > +
> > +template<template<typename> class tuple_like_t>
> > +constexpr bool
> > +test02()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr explicit B(A&) : m(0) { }
> > +    constexpr explicit B(A&&) : m(1) { }
> > +    constexpr explicit B(const A&) : m(2) { }
> > +    constexpr explicit B(const A&&) : m(3) { }
> > +  };
> > +
> > +  // template<tuple-like UTuple>
> > +  //   constexpr explicit(true) tuple(UTuple&&);
> > +
> > +  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
> > +
> > +  tuple_like_t<A> tuple_like;
> > +
> > +  [&] {
> > +    tuple<B, B, B> t3b{tuple_like};
> > +    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b{std::move(tuple_like)};
> > +    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b{std::as_const(tuple_like)};
> > +    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +  }();
> > +  [&] {
> > +    tuple<B, B, B> t3b{std::move(std::as_const(tuple_like))};
> > +    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +  }();
> > +
> > +  return true;
> > +}
> > +
> > +namespace alloc
> > +{
> > +  struct B02 {
> > +    int m;
> > +    explicit B02(A&);
> > +    explicit B02(A&&);
> > +    explicit B02(const A&);
> > +    explicit B02(const A&&);
> > +    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : m(0) { }
> > +    explicit constexpr B02(allocator_arg_t, allocator<int>, A&&) : m(1) { }
> > +    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&) : m(2) { }
> > +    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
> > +  };
> > +
> > +  template<template<typename> class tuple_like_t>
> > +  constexpr bool
> > +  test02()
> > +  {
> > +    using B = B02;
> > +
> > +    // template<tuple-like UTuple>
> > +    //   constexpr explicit(true) tuple(allocator_arg_t, const Alloc&, UTuple&&);
> > +
> > +    static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
> > +
> > +    tuple_like_t<A> tuple_like;
> > +
> > +    [&] {
> > +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, tuple_like};
> > +      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(tuple_like)};
> > +      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
> > +      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +    }();
> > +    [&] {
> > +      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
> > +      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +    }();
> > +
> > +    return true;
> > +  }
> > +}
> > +
> > +
> > +template<template<typename> class tuple_like_t>
> > +constexpr bool
> > +test03()
> > +{
> > +  struct B {
> > +    int m;
> > +    constexpr B& operator=(A&) { m = 0; return *this; }
> > +    constexpr B& operator=(A&&) { m = 1; return *this; }
> > +    constexpr B& operator=(const A&) { m = 2; return *this; }
> > +    constexpr B& operator=(const A&&) { m = 3; return *this; }
> > +  };
> > +
> > +  // template<tuple-like UTuple>
> > +  //   constexpr tuple& operator=(UTuple&&);
> > +
> > +  tuple_like_t<A> tuple_like;
> > +
> > +  tuple<B, B, B> t3b;
> > +  t3b = tuple_like;
> > +  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +  t3b = std::move(tuple_like);
> > +  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +  t3b = std::as_const(tuple_like);
> > +  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +  t3b = std::move(std::as_const(tuple_like));
> > +  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +
> > +  return true;
> > +}
> > +
> > +template<template<typename> class tuple_like_t>
> > +constexpr bool
> > +test04()
> > +{
> > +  struct B {
> > +    mutable int m;
> > +    constexpr const B& operator=(A&) const { m = 0; return *this; }
> > +    constexpr const B& operator=(A&&) const { m = 1; return *this; }
> > +    constexpr const B& operator=(const A&) const { m = 2; return *this; }
> > +    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
> > +  };
> > +
> > +  // template<tuple-like UTuple>
> > +  //   constexpr const tuple& operator=(UTuple&&) const;
> > +
> > +  tuple_like_t<A> tuple_like;
> > +
> > +  const tuple<B, B, B> t3b;
> > +  t3b = tuple_like;
> > +  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
> > +  t3b = std::move(tuple_like);
> > +  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
> > +  t3b = std::as_const(tuple_like);
> > +  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
> > +  t3b = std::move(std::as_const(tuple_like));
> > +  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
> > +
> > +  return true;
> > +}
> > +
> > +template<template<typename> class tuple_like_t>
> > +constexpr bool
> > +test05()
> > +{
> > +  // template<tuple-like UTuple>
> > +  //   constexpr bool operator==(const tuple&, const UTuple&);
> > +
> > +  static_assert( tuple{1, 2, 3} == tuple_like_t{1, 2, 3} );
> > +  static_assert( tuple{1, 2, 4} != tuple_like_t{1, 2, 3} );
> > +  static_assert( tuple_like_t{1, 2, 3} == tuple{1, 2, 3} );
> > +  static_assert( tuple_like_t{1, 2, 3} != tuple{1, 2, 4} );
> > +
> > +  // template<tuple-like UTuple>
> > +  //   constexpr bool operator<=>const tuple&, const UTuple&);
> > +
> > +  static_assert( (tuple{1, 2, 3} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::equal );
> > +  static_assert( (tuple{1, 2, 4} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::greater );
> > +  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 3}) == std::strong_ordering::equal );
> > +  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 4}) == std::strong_ordering::less  );
> > +
> > +  static_assert( tuple{1, 2, 4} > tuple_like_t{1, 2, 3} );
> > +  static_assert( tuple_like_t{1, 2, 3} < tuple{1, 2, 4} );
> > +
> > +  // template<tuple-like TTuple, tuple-like UTuple, ...>
> > +  //   struct basic_common_reference<TTuple, UTuple, ...>;
> > +
> > +  static_assert( std::same_as<std::common_reference_t<tuple_like_t<int>,
> > +						      tuple<int, long, int>>,
> > +			      tuple<int, long, int>> );
> > +
> > +  static_assert( std::same_as<std::common_reference_t<tuple<int, long, int>,
> > +						      tuple_like_t<int>>,
> > +			      tuple<int, long, int>> );
> > +
> > +  // template<tuple-like TTuple, tuple-like UTuple>
> > +  //   struct common_type<TTuple, UTuple>;
> > +
> > +  static_assert( std::same_as<std::common_type_t<tuple_like_t<const int&>,
> > +						 tuple<int, long, int>>,
> > +			      tuple<int, long, int>> );
> > +
> > +  static_assert( std::same_as<std::common_type_t<tuple<int, long, int>,
> > +						 tuple_like_t<const int&>>,
> > +			      tuple<int, long, int>> );
> > +
> > +  return true;
> > +}
> > +
> > +template<typename T>
> > +using tuple_like_array = array<T, 3>;
> > +
> > +int
> > +main()
> > +{
> > +  static_assert( test01<tuple_like_array>() );
> > +  static_assert( alloc::test01<tuple_like_array>() );
> > +  static_assert( test02<tuple_like_array>() );
> > +  static_assert( alloc::test02<tuple_like_array>() );
> > +  static_assert( test03<tuple_like_array>() );
> > +  static_assert( test04<tuple_like_array>() );
> > +  static_assert( test05<tuple_like_array>() );
> > +}
> > diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> > index b7717aed92c..672a8c356d9 100644
> > --- a/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> > +++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
> > @@ -41,8 +41,8 @@ test01()
> >    VERIFY( i2 == z2.end() );
> >    VERIFY( ranges::size(z2) == 2 );
> >    VERIFY( ranges::size(std::as_const(z2)) == 2 );
> > -  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
> > -  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
> > +  VERIFY( std::get<0>(z2[0]) == 1 && std::get<1>(z2[0]) == 3 );
> > +  VERIFY( std::get<0>(z2[1]) == 2 && std::get<1>(z2[1]) == 4 );
> >    for (const auto [x, y] : z2)
> >      {
> >        VERIFY( y - x == 2 );
> > -- 
> > 2.43.0.386.ge02ecfcc53
> > 
> > 
> 

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Type: text/x-diff; name=0001-libstdc-Implement-P2165R4-changes-to-std-pair-tuple-.patch, Size: 61811 bytes --]

From c1189dc4a003d218f892d9c6642edf273496ab0c Mon Sep 17 00:00:00 2001
From: Patrick Palka <ppalka@redhat.com>
Date: Wed, 24 Jan 2024 17:17:39 -0500
Subject: [PATCH] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
 [PR113309]

Tested on x86_64-pc-linux-gnu, does this look OK for trunK?

-- >8 --

This implements the C++23 paper P2165R4 Compatibility between tuple,
pair and tuple-like objects, which builds upon many changes from the
earlier C++23 paper P2321R2 zip.

Some declarations had to be moved around so that they're visible from
<bits/stl_pair.h> without bloating the header.  In the end, the
only new include is for <bits/utility.h> from <bits/stl_iterator.h>,
for tuple_element_t.

	PR libstdc++/113309
	PR libstdc++/109203

libstdc++-v3/ChangeLog:

	* include/bits/ranges_util.h (__detail::__pair_like): Don't
	define in C++23 mode.
	(__detail::__pair_like_convertible_from): Adjust as per P2165R4.
	(__detail::__is_subrange<subrange>): Moved from <ranges>.
	(__detail::__is_tuple_like_v<subrange>): Likewise.
	* include/bits/stl_iterator.h: Include <bits/utility.h> for
	C++23.
	(__different_from): Move to <concepts>.
	(__iter_key_t): Adjust for C++23 as per P2165R4.
	(__iter_val_t): Likewise.
	* include/bits/stl_pair.h (pair, array): Forward declare.
	(get): Forward declare all overloads relevant to P2165R4
	tuple-like constructors.
	(__is_tuple_v): Define for C++23.
	(__is_tuple_like_v): Define for C++23.
	(__tuple_like): Define for C++23 as per P2165R4.
	(__pair_like): Define for C++23 as per P2165R4.
	(__eligibile_tuple_like): Define for C++23.
	(__eligibile_pair_like): Define for C++23.
	(pair::_S_constructible_from_pair_like): Define for C++23.
	(pair::_S_convertible_from_pair_like): Define for C++23.
	(pair::_S_dangles_from_pair_like): Define for C++23.
	(pair::pair): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	(pair::_S_assignable_from_tuple_like): Define for C++23.
	(pair::_S_const_assignable_from_tuple_like): Define for C++23.
	(pair::operator=): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	* include/bits/utility.h (ranges::__detail::__is_subrange):
	Moved from <ranges>.
	* include/bits/version.def (tuple_like): Define for C++23.
	* include/bits/version.h: Regenerate.
	* include/std/concepts (__different_from): Moved from
	<bits/stl_iterator.h>.
	(ranges::__swap::__adl_swap): Clarify which __detail namespace.
	* include/std/map (__cpp_lib_tuple_like): Define C++23.
	* include/std/ranges (__detail::__is_subrange): Moved to
	<bits/utility.h>.
	(__detail::__is_subrange<subrange>): Moved to <bits/ranges_util.h>
	(__detail::__has_tuple_element): Adjust for C++23 as per P2165R4.
	(__detail::__tuple_or_pair): Remove as per P2165R4.  Replace all
	uses with plain tuple as per P2165R4.
	* include/std/tuple (__cpp_lib_tuple_like): Define for C++23.
	(__tuple_like_tag_t): Define for C++23.
	(__tuple_cmp): Forward declare for C++23.
	(_Tuple_impl::_Tuple_impl): Define overloads taking
	__tuple_like_tag_t and a tuple-like type for C++23.
	(_Tuple_impl::_M_assign): Likewise.
	(tuple::__constructible_from_tuple_like): Define for C++23.
	(tuple::__convertible_from_tuple_like): Define for C++23.
	(tuple::__dangles_from_tuple_like): Define for C++23.
	(tuple::tuple): Define overloads taking a tuple-like type for
	C++23 as per P2165R4.
	(tuple::__assignable_from_tuple_like): Define for C++23.
	(tuple::__const_assignable_from_tuple_like): Define for C++23.
	(tuple::operator=): Define overloads taking a tuple-like type
	for C++23 as per P2165R4.
	(tuple::__tuple_like_common_comparison_category): Define for C++23.
	(tuple::operator<=>): Define overload taking a tuple-like type
	for C++23 as per P2165R4.
	(array, get): Forward declarations moved to <bits/stl_pair.h>.
	(tuple_cat): Constrain with __tuple_like for C++23 as per P2165R4.
	(apply): Likewise.
	(make_from_tuple): Likewise.
	(__tuple_like_common_reference): Define for C++23.
	(basic_common_reference): Adjust as per P2165R4.
	(__tuple_like_common_type): Define for C++23.
	(common_type): Adjust as per P2165R4.
	* include/std/unordered_map (__cpp_lib_tuple_like): Define for
	C++23.
	* include/std/utility (__cpp_lib_tuple_like): Define for C++23.
	* testsuite/std/ranges/zip/1.cc (test01): Adjust to handle pair
	and 2-tuple interchangeably.
	(test05): New test.
	* testsuite/20_util/pair/p2165r4.cc: New test.
	* testsuite/20_util/tuple/p2165r4.cc: New test.
---
 libstdc++-v3/include/bits/ranges_util.h       |  17 +-
 libstdc++-v3/include/bits/stl_iterator.h      |  16 +-
 libstdc++-v3/include/bits/stl_pair.h          | 182 ++++++++++
 libstdc++-v3/include/bits/utility.h           |   8 +
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  11 +
 libstdc++-v3/include/std/concepts             |  11 +-
 libstdc++-v3/include/std/map                  |   1 +
 libstdc++-v3/include/std/ranges               |  48 +--
 libstdc++-v3/include/std/tuple                | 323 ++++++++++++++---
 libstdc++-v3/include/std/unordered_map        |   1 +
 libstdc++-v3/include/std/utility              |   1 +
 .../testsuite/20_util/pair/p2165r4.cc         | 173 +++++++++
 .../testsuite/20_util/tuple/p2165r4.cc        | 335 ++++++++++++++++++
 libstdc++-v3/testsuite/std/ranges/zip/1.cc    |  17 +-
 15 files changed, 1069 insertions(+), 83 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc

diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
index bb04c49f044..9b79c3a229d 100644
--- a/libstdc++-v3/include/bits/ranges_util.h
+++ b/libstdc++-v3/include/bits/ranges_util.h
@@ -224,6 +224,10 @@ namespace ranges
 	&& !__uses_nonqualification_pointer_conversion<decay_t<_From>,
 						       decay_t<_To>>;
 
+#if __glibcxx_tuple_like // >= C++23
+    // P2165R4 version of __pair_like is defined in <bits/stl_pair.h>.
+#else
+    // C++20 version of __pair_like from P2321R2.
     template<typename _Tp>
       concept __pair_like
 	= !is_reference_v<_Tp> && requires(_Tp __t)
@@ -235,10 +239,11 @@ namespace ranges
 	  { get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
 	  { get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
 	};
+#endif
 
     template<typename _Tp, typename _Up, typename _Vp>
       concept __pair_like_convertible_from
-	= !range<_Tp> && __pair_like<_Tp>
+	= !range<_Tp> && !is_reference_v<_Vp> && __pair_like<_Tp>
 	&& constructible_from<_Tp, _Up, _Vp>
 	&& __convertible_to_non_slicing<_Up, tuple_element_t<0, _Tp>>
 	&& convertible_to<_Vp, tuple_element_t<1, _Tp>>;
@@ -463,8 +468,18 @@ namespace ranges
     using borrowed_subrange_t = __conditional_t<borrowed_range<_Range>,
 						subrange<iterator_t<_Range>>,
 						dangling>;
+
+  // __is_subrange is defined in <bits/utility.h>.
+  template<typename _Iter, typename _Sent, subrange_kind _Kind>
+    inline constexpr bool __detail::__is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
 } // namespace ranges
 
+#if __glibcxx_tuple_like // >= C++23
+  // __is_tuple_like_v is defined in <bits/stl_pair.h>.
+  template<typename _It, typename _Sent, ranges::subrange_kind _Kind>
+    inline constexpr bool __is_tuple_like_v<ranges::subrange<_It, _Sent, _Kind>> = true;
+#endif
+
 // The following ranges algorithms are used by <ranges>, and are defined here
 // so that <ranges> can avoid including all of <bits/ranges_algo.h>.
 namespace ranges
diff --git a/libstdc++-v3/include/bits/stl_iterator.h b/libstdc++-v3/include/bits/stl_iterator.h
index d71a793e10d..560a10a7abe 100644
--- a/libstdc++-v3/include/bits/stl_iterator.h
+++ b/libstdc++-v3/include/bits/stl_iterator.h
@@ -78,6 +78,10 @@
 # include <bits/stl_construct.h>
 #endif
 
+#if __glibcxx_tuple_like // >= C++23
+# include <bits/utility.h> // for tuple_element_t
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -95,10 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     template<typename _Cat, typename _Limit, typename _Otherwise = _Cat>
       using __clamp_iter_cat
 	= __conditional_t<derived_from<_Cat, _Limit>, _Limit, _Otherwise>;
-
-    template<typename _Tp, typename _Up>
-      concept __different_from
-	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
   }
 #endif
 
@@ -2983,11 +2983,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // of associative containers.
   template<typename _InputIterator>
     using __iter_key_t = remove_const_t<
+#if __glibcxx_tuple_like // >= C++23
+      tuple_element_t<0, typename iterator_traits<_InputIterator>::value_type>>;
+#else
       typename iterator_traits<_InputIterator>::value_type::first_type>;
+#endif
 
   template<typename _InputIterator>
     using __iter_val_t
+#if __glibcxx_tuple_like // >= C++23
+      = tuple_element_t<1, typename iterator_traits<_InputIterator>::value_type>;
+#else
       = typename iterator_traits<_InputIterator>::value_type::second_type;
+#endif
 
   template<typename _T1, typename _T2>
     struct pair;
diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index b81b479ad43..00ec53ebc33 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @cond undocumented
 
   // Forward declarations.
+  template<typename _T1, typename _T2>
+    struct pair;
+
   template<typename...>
     class tuple;
 
+  // Declarations of std::array and its std::get overloads, so that
+  // std::tuple_cat can use them if <tuple> is included before <array>.
+  // We also declare the other std::get overloads here so that they're
+  // visible to the P2165R4 tuple-like constructors of pair and tuple.
+  template<typename _Tp, size_t _Nm>
+    struct array;
+
   template<size_t...>
     struct _Index_tuple;
 
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
+    get(const pair<_Tp1, _Tp2>& __in) noexcept;
+
+  template<size_t _Int, class _Tp1, class _Tp2>
+    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
+    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
+    get(tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
+    get(const tuple<_Elements...>& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t __i, typename... _Elements>
+    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
+    get(const tuple<_Elements...>&& __t) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&
+    get(array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr _Tp&&
+    get(array<_Tp, _Nm>&&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&
+    get(const array<_Tp, _Nm>&) noexcept;
+
+  template<size_t _Int, typename _Tp, size_t _Nm>
+    constexpr const _Tp&&
+    get(const array<_Tp, _Nm>&&) noexcept;
+
 #if ! __cpp_lib_concepts
   // Concept utility functions, reused in conditionally-explicit
   // constructors.
@@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // lib concepts
 #endif // C++11
 
+#if __glibcxx_tuple_like // >= C++23
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_v = false;
+
+  template<typename... _Ts>
+    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
+
+  // TODO: Reuse __is_tuple_like from <type_traits>?
+  template<typename _Tp>
+    inline constexpr bool __is_tuple_like_v = false;
+
+  template<typename... _Elements>
+    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
+
+  template<typename _T1, typename _T2>
+    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
+
+  template<typename _Tp, size_t _Nm>
+    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
+
+  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
+
+  template<typename _Tp>
+    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp>
+    concept __pair_like = __tuple_like<_Tp> && tuple_size_v<remove_cvref_t<_Tp>> == 2;
+
+  template<typename _Tp, typename _Tuple>
+    concept __eligible_tuple_like
+      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
+	&& (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+
+  template<typename _Tp, typename _Pair>
+    concept __eligible_pair_like
+      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
+	&& !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
+#endif // C++23
+
   template<typename _U1, typename _U2> class __pair_base
   {
 #if __cplusplus >= 201103L && ! __cpp_lib_concepts
@@ -295,6 +393,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return false;
 #endif
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_constructible_from_pair_like()
+	{
+	  return _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
+				  decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_convertible_from_pair_like()
+	{
+	  return _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
+				decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_dangles_from_pair_like()
+	{
+	  return _S_dangles<decltype(std::get<0>(std::declval<_UPair>())),
+			    decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
     public:
@@ -393,6 +517,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	pair(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_constructible_from_pair_like<_UPair>())
+	  && (!_S_dangles_from_pair_like<_UPair>())
+	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
+	pair(_UPair&& __p)
+	: first(std::get<0>(std::forward<_UPair>(__p))),
+	  second(std::get<1>(std::forward<_UPair>(__p)))
+	{ }
+
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_constructible_from_pair_like<_UPair>())
+	  && (_S_dangles_from_pair_like<_UPair>())
+	constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
+	pair(_UPair&&) = delete;
+#endif // C++23
+
   private:
       /// @cond undocumented
       template<typename _U1, typename _U2>
@@ -421,6 +562,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return is_nothrow_assignable_v<_T2&, _U2>;
 	  return false;
 	}
+
+#if __glibcxx_tuple_like // >= C++23
+      template<typename _UPair>
+	static constexpr bool
+	_S_assignable_from_tuple_like()
+	{
+	  return _S_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+			       decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+
+      template<typename _UPair>
+	static constexpr bool
+	_S_const_assignable_from_tuple_like()
+	{
+	  return _S_const_assignable<decltype(std::get<0>(std::declval<_UPair>())),
+				     decltype(std::get<1>(std::declval<_UPair>()))>();
+	}
+#endif // C++23
       /// @endcond
 
   public:
@@ -516,6 +675,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 #endif // C++23
+
+#if __glibcxx_tuple_like // >= C++23
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_assignable_from_tuple_like<_UPair>())
+	constexpr pair&
+	operator=(_UPair&& __p)
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+
+      template<__eligible_pair_like<pair> _UPair>
+	requires (_S_const_assignable_from_tuple_like<_UPair>())
+	constexpr const pair&
+	operator=(_UPair&& __p) const
+	{
+	  first = std::get<0>(std::forward<_UPair>(__p));
+	  second = std::get<1>(std::forward<_UPair>(__p));
+	  return *this;
+	}
+#endif // C++23
+
 #else // !__cpp_lib_concepts
       // C++11/14/17 implementation using enable_if, partially constexpr.
 
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index d8a5fb960fe..2a741bf7000 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -266,6 +266,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 #endif
 
+#if __glibcxx_ranges
+  namespace ranges::__detail
+  {
+    template<typename _Range>
+      inline constexpr bool __is_subrange = false;
+  } // namespace __detail
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 8fb8a2877ee..502961eb269 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1780,6 +1780,14 @@ ftms = {
   };
 };
 
+ftms = {
+  name = tuple_like;
+  values = {
+    v = 202207;
+    cxxmin = 23;
+  };
+};
+
 // Standard test specifications.
 stds[97] = ">= 199711L";
 stds[03] = ">= 199711L";
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9ba99deeda6..511030bde47 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2169,4 +2169,15 @@
 #endif /* !defined(__cpp_lib_generator) && defined(__glibcxx_want_generator) */
 #undef __glibcxx_want_generator
 
+// from version.def line 1774
+#if !defined(__cpp_lib_tuple_like)
+# if (__cplusplus >= 202100L)
+#  define __glibcxx_tuple_like 202207L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_tuple_like)
+#   define __cpp_lib_tuple_like 202207L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_tuple_like) && defined(__glibcxx_want_tuple_like) */
+#undef __glibcxx_want_tuple_like
+
 #undef __glibcxx_want_all
diff --git a/libstdc++-v3/include/std/concepts b/libstdc++-v3/include/std/concepts
index 66ed3714b25..4f3e059b051 100644
--- a/libstdc++-v3/include/std/concepts
+++ b/libstdc++-v3/include/std/concepts
@@ -62,6 +62,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     concept same_as
       = __detail::__same_as<_Tp, _Up> && __detail::__same_as<_Up, _Tp>;
 
+  namespace __detail
+  {
+    template<typename _Tp, typename _Up>
+      concept __different_from
+	= !same_as<remove_cvref_t<_Tp>, remove_cvref_t<_Up>>;
+  } // namespace __detail
+
   /// [concept.derived], concept derived_from
   template<typename _Derived, typename _Base>
     concept derived_from = __is_base_of(_Base, _Derived)
@@ -185,8 +192,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       template<typename _Tp, typename _Up>
 	concept __adl_swap
-	  = (__detail::__class_or_enum<remove_reference_t<_Tp>>
-	    || __detail::__class_or_enum<remove_reference_t<_Up>>)
+	  = (std::__detail::__class_or_enum<remove_reference_t<_Tp>>
+	    || std::__detail::__class_or_enum<remove_reference_t<_Up>>)
 	  && requires(_Tp&& __t, _Up&& __u) {
 	    swap(static_cast<_Tp&&>(__t), static_cast<_Up&&>(__u));
 	  };
diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
index dcfd222d173..4a96e59a5bc 100644
--- a/libstdc++-v3/include/std/map
+++ b/libstdc++-v3/include/std/map
@@ -74,6 +74,7 @@
 #define __glibcxx_want_map_try_emplace
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index f2413badd9c..7d739852677 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -2389,11 +2389,7 @@ namespace views::__adaptor
 	inline constexpr bool __is_basic_string_view<basic_string_view<_CharT, _Traits>>
 	  = true;
 
-      template<typename _Range>
-	inline constexpr bool __is_subrange = false;
-
-      template<typename _Iter, typename _Sent, subrange_kind _Kind>
-	inline constexpr bool __is_subrange<subrange<_Iter, _Sent, _Kind>> = true;
+      using ranges::__detail::__is_subrange;
 
       template<typename _Range>
 	inline constexpr bool __is_iota_view = false;
@@ -4166,6 +4162,10 @@ namespace views::__adaptor
 
   namespace __detail
   {
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _Tp, size_t _Nm>
+    concept __has_tuple_element = __tuple_like<_Tp> && _Nm < tuple_size_v<_Tp>;
+#else
     template<typename _Tp, size_t _Nm>
     concept __has_tuple_element = requires(_Tp __t)
       {
@@ -4175,6 +4175,7 @@ namespace views::__adaptor
 	{ std::get<_Nm>(__t) }
 	  -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
       };
+#endif
 
     template<typename _Tp, size_t _Nm>
       concept __returnable_element
@@ -4559,23 +4560,12 @@ namespace views::__adaptor
 	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
 	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
 
-    template<typename... _Ts>
-      struct __tuple_or_pair
-      { using type = std::tuple<_Ts...>; };
-
-    template<typename _Tp, typename _Up>
-      struct __tuple_or_pair<_Tp, _Up>
-      { using type = pair<_Tp, _Up>; };
-
-    template<typename... _Ts>
-      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
-
     template<typename _Fp, typename _Tuple>
       constexpr auto
       __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
       {
 	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
-	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
+	  return tuple<invoke_result_t<_Fp&, _Ts>...>
 	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
 	}, std::forward<_Tuple>(__tuple));
       }
@@ -4696,7 +4686,7 @@ namespace views::__adaptor
 #ifdef __clang__ // LLVM-61763 workaround
   public:
 #endif
-    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr explicit
     _Iterator(decltype(_M_current) __current)
@@ -4728,7 +4718,7 @@ namespace views::__adaptor
     // iterator_category defined in __zip_view_iter_cat
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
     using difference_type
       = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
 
@@ -4900,7 +4890,7 @@ namespace views::__adaptor
   template<bool _Const>
   class zip_view<_Vs...>::_Sentinel
   {
-    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
+    tuple<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
 
     constexpr explicit
     _Sentinel(decltype(_M_end) __end)
@@ -8325,8 +8315,7 @@ namespace views::__adaptor
 		    && __detail::__cartesian_product_is_common<_First, _Vs...>)
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<_First>,
-						 iterator_t<_Vs>...>;
+	using _Ret = tuple<iterator_t<_First>, iterator_t<_Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8342,8 +8331,7 @@ namespace views::__adaptor
     end() const requires __detail::__cartesian_product_is_common<const _First, const _Vs...>
     {
       auto __its = [this]<size_t... _Is>(index_sequence<_Is...>) {
-	using _Ret = __detail::__tuple_or_pair_t<iterator_t<const _First>,
-						 iterator_t<const _Vs>...>;
+	using _Ret = tuple<iterator_t<const _First>, iterator_t<const _Vs>...>;
 	bool __empty_tail = (ranges::empty(std::get<1 + _Is>(_M_bases)) || ...);
 	auto& __first = std::get<0>(_M_bases);
 	return _Ret{(__empty_tail
@@ -8416,8 +8404,8 @@ namespace views::__adaptor
   {
     using _Parent = __maybe_const_t<_Const, cartesian_product_view>;
     _Parent* _M_parent = nullptr;
-    __detail::__tuple_or_pair_t<iterator_t<__maybe_const_t<_Const, _First>>,
-				iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
+    tuple<iterator_t<__maybe_const_t<_Const, _First>>,
+	  iterator_t<__maybe_const_t<_Const, _Vs>>...> _M_current;
 
     constexpr
     _Iterator(_Parent& __parent, decltype(_M_current) __current)
@@ -8444,11 +8432,11 @@ namespace views::__adaptor
     using iterator_category = input_iterator_tag;
     using iterator_concept = decltype(_S_iter_concept());
     using value_type
-      = __detail::__tuple_or_pair_t<range_value_t<__maybe_const_t<_Const, _First>>,
-				    range_value_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_value_t<__maybe_const_t<_Const, _First>>,
+	      range_value_t<__maybe_const_t<_Const, _Vs>>...>;
     using reference
-      = __detail::__tuple_or_pair_t<range_reference_t<__maybe_const_t<_Const, _First>>,
-				    range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
+      = tuple<range_reference_t<__maybe_const_t<_Const, _First>>,
+	      range_reference_t<__maybe_const_t<_Const, _Vs>>...>;
     using difference_type = decltype(cartesian_product_view::_S_difference_type());
 
     _Iterator() = default;
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index be92f1eb973..ba364d6a4f8 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -50,6 +50,7 @@
 #define __glibcxx_want_apply
 #define __glibcxx_want_make_from_tuple
 #define __glibcxx_want_ranges_zip
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -246,6 +247,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _Head _M_head_impl;
     };
 
+#if __cpp_lib_tuple_like // >= C++23
+  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
+
+  // These forward declarations are used by the operator<=> overload for
+  // tuple-like types.
+  template<typename _Cat, typename _Tp, typename _Up>
+    constexpr _Cat
+    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
+
+  template<typename _Cat, typename _Tp, typename _Up,
+	   size_t _Idx0, size_t... _Idxs>
+    constexpr _Cat
+    __tuple_cmp(const _Tp& __t, const _Up& __u,
+		index_sequence<_Idx0, _Idxs...>);
+#endif // C++23
+
   /**
    * Contains the actual implementation of the @c tuple template, stored
    * as a recursive inheritance hierarchy from the first element (most
@@ -342,6 +359,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -428,6 +453,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple, size_t... _Is>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<_Is...>)
+	: _Tuple_impl(__tag, __a, std::get<_Is>(std::forward<_UTuple>(__u))...)
+	{ }
+#endif // C++23
+
       template<typename... _UElements>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -470,6 +504,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+
+      template<typename _UTuple>
+	constexpr void
+	_M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
+	{
+	  _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
+	  _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
+	}
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -563,6 +615,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -633,6 +693,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, typename _UTuple>
+	constexpr
+	_Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const _Alloc& __a,
+		    _UTuple&& __u, index_sequence<0>)
+	: _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
+	{ }
+#endif // C++23
+
       template<typename _UHead>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -667,6 +736,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+
+    template<typename _UTuple>
+      constexpr void
+      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
+      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
+#endif // C++23
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -846,6 +927,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 	}
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__dangles_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __dangles<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__constructible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__convertible_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+#endif // C++23
+
     public:
       constexpr
       explicit(!(__is_implicitly_default_constructible_v<_Elements> && ...))
@@ -1016,10 +1126,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(_UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (!__dangles_from_tuple_like<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(_UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     std::forward<_UTuple>(__u),
+		     index_sequence_for<_Elements...>{})
+	{ }
+
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (__dangles_from_tuple_like<_UTuple>())
+	tuple(_UTuple&&) = delete;
 #endif // C++23
 
       // Allocator-extended constructors.
@@ -1202,10 +1325,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = delete;
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<typename _Alloc, __tuple_like _UTuple>
-	constexpr explicit(...)
-	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (!__dangles_from_tuple_like<_UTuple>())
+	constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
+	tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
+	: _Inherited(__tuple_like_tag_t{},
+		     __tag, __a, std::forward<_UTuple>(__u),
+		     index_sequence_for<_Elements...>{})
+	{ }
+
+      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
+	requires (__constructible_from_tuple_like<_UTuple>())
+	  && (!__use_other_ctor<_UTuple>())
+	  && (__dangles_from_tuple_like<_UTuple>())
+	tuple(allocator_arg_t, const _Alloc&, _UTuple&&) = delete;
 #endif // C++23
 
 #else // !(concepts && conditional_explicit)
@@ -1539,6 +1675,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
+#if __cpp_lib_tuple_like // >= C++23
+      template<typename _UTuple>
+	static consteval bool
+	__assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<typename _UTuple>
+	static consteval bool
+	__const_assignable_from_tuple_like()
+	{
+	  return []<size_t... _Is>(index_sequence<_Is...>) {
+	    return __const_assignable<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
+	  }(index_sequence_for<_Elements...>{});
+	}
+#endif // C++23
+
     public:
 
       tuple& operator=(const tuple& __u) = delete;
@@ -1661,14 +1817,59 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 #endif // C++23
 
-#if 0 && __cpp_lib_tuple_like // >= C++23
-      template<__tuple_like _UTuple>
+#if __cpp_lib_tuple_like // >= C++23
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__assignable_from_tuple_like<_UTuple>())
 	constexpr tuple&
-	operator=(_UTuple&& __u);
+	operator=(_UTuple&& __u)
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
+
+      template<__eligible_tuple_like<tuple> _UTuple>
+	requires (__const_assignable_from_tuple_like<_UTuple>())
+	constexpr const tuple&
+	operator=(_UTuple&& __u) const
+	{
+	  this->_M_assign(__tuple_like_tag_t{}, std::forward<_UTuple>(__u));
+	  return *this;
+	}
 
       template<__tuple_like _UTuple>
-	constexpr tuple&
-	operator=(_UTuple&& __u) const;
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr bool
+	operator==(const tuple& __t, const _UTuple& __u)
+	{
+	  static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>,
+	      "tuple objects can only be compared if they have equal sizes.");
+	  return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	    return (bool(std::get<_Is>(__t) == std::get<_Is>(__u))
+		    && ...);
+	  }(index_sequence_for<_Elements...>{});
+	}
+
+      template<__tuple_like _UTuple,
+	       typename = make_index_sequence<tuple_size_v<_UTuple>>>
+	struct __tuple_like_common_comparison_category;
+
+      template<__tuple_like _UTuple, size_t... _Is>
+	requires requires
+	  { typename void_t<__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>; }
+	struct __tuple_like_common_comparison_category<_UTuple, index_sequence<_Is...>>
+	{
+	  using type = common_comparison_category_t
+	    <__detail::__synth3way_t<_Elements, tuple_element_t<_Is, _UTuple>>...>;
+	};
+
+      template<__tuple_like _UTuple>
+	requires (!__is_tuple_v<_UTuple>)
+	friend constexpr typename __tuple_like_common_comparison_category<_UTuple>::type
+	operator<=>(const tuple& __t, const _UTuple& __u)
+	{
+	  using _Cat = typename __tuple_like_common_comparison_category<_UTuple>::type;
+	  return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Elements...>());
+	}
 #endif // C++23
 
 #else // ! (concepts && consteval)
@@ -2433,27 +2634,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     forward_as_tuple(_Elements&&... __args) noexcept
     { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }
 
-  // Declarations of std::array and its std::get overloads, so that
-  // std::tuple_cat can use them if <tuple> is included before <array>.
-
-  template<typename _Tp, size_t _Nm> struct array;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&
-    get(array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr _Tp&&
-    get(array<_Tp, _Nm>&&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&
-    get(const array<_Tp, _Nm>&) noexcept;
-
-  template<size_t _Int, typename _Tp, size_t _Nm>
-    constexpr const _Tp&&
-    get(const array<_Tp, _Nm>&&) noexcept;
-
   /// @cond undocumented
   template<size_t, typename, typename, size_t>
     struct __make_tuple_impl;
@@ -2569,8 +2749,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// @endcond
 
   /// Create a `tuple` containing all elements from multiple tuple-like objects
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like... _Tpls>
+#else
   template<typename... _Tpls, typename = typename
            enable_if<__and_<__is_tuple_like<_Tpls>...>::value>::type>
+#endif
     constexpr auto
     tuple_cat(_Tpls&&... __tpls)
     -> typename __tuple_cat_result<_Tpls...>::__type
@@ -2722,7 +2906,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 			   std::get<_Idx>(std::forward<_Tuple>(__t))...);
     }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Fn, __tuple_like _Tuple>
+#else
   template <typename _Fn, typename _Tuple>
+#endif
     constexpr decltype(auto)
     apply(_Fn&& __f, _Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_invocable, _Fn, _Tuple>)
@@ -2741,7 +2929,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     __make_from_tuple_impl(_Tuple&& __t, index_sequence<_Idx...>)
     { return _Tp(std::get<_Idx>(std::forward<_Tuple>(__t))...); }
 
+#if __cpp_lib_tuple_like // >= C++23
+  template <typename _Tp, __tuple_like _Tuple>
+#else
   template <typename _Tp, typename _Tuple>
+#endif
     constexpr _Tp
     make_from_tuple(_Tuple&& __t)
     noexcept(__unpack_std_tuple<is_nothrow_constructible, _Tp, _Tuple>)
@@ -2759,17 +2951,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif
 
-#if __cpp_lib_ranges_zip // >= C++23
-  template<typename... _TTypes, typename... _UTypes,
+#if __cpp_lib_tuple_like // >= C++23
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_reference;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   template<typename> class _TQual, template<typename> class _UQual,
+	   size_t... _Is>
+    requires requires
+      { typename tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>; }
+  struct __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual, index_sequence<_Is...>>
+  {
+    using type = tuple<common_reference_t<_TQual<tuple_element_t<_Is, _TTuple>>,
+					  _UQual<tuple_element_t<_Is, _UTuple>>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
 	   template<typename> class _TQual, template<typename> class _UQual>
-    requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; }
-  struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual>
-  { using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; };
-
-  template<typename... _TTypes, typename... _UTypes>
-    requires requires { typename tuple<common_type_t<_TTypes, _UTypes>...>; }
-  struct common_type<tuple<_TTypes...>, tuple<_UTypes...>>
-  { using type = tuple<common_type_t<_TTypes, _UTypes>...>; };
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type; }
+  struct basic_common_reference<_TTuple, _UTuple, _TQual, _UQual>
+  {
+    using type = typename __tuple_like_common_reference<_TTuple, _UTuple, _TQual, _UQual>::type;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple,
+	   typename = make_index_sequence<tuple_size_v<_TTuple>>>
+  struct __tuple_like_common_type;
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple, size_t... _Is>
+    requires requires
+      { typename tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				   tuple_element_t<_Is, _UTuple>>...>; }
+  struct __tuple_like_common_type<_TTuple, _UTuple, index_sequence<_Is...>>
+  {
+    using type = tuple<common_type_t<tuple_element_t<_Is, _TTuple>,
+				     tuple_element_t<_Is, _UTuple>>...>;
+  };
+
+  template<__tuple_like _TTuple, __tuple_like _UTuple>
+    requires (__is_tuple_v<_TTuple> || __is_tuple_v<_UTuple>)
+      && is_same_v<_TTuple, decay_t<_TTuple>>
+      && is_same_v<_UTuple, decay_t<_UTuple>>
+      && (tuple_size_v<_TTuple> == tuple_size_v<_UTuple>)
+      && requires { typename __tuple_like_common_type<_TTuple, _UTuple>::type; }
+  struct common_type<_TTuple, _UTuple>
+  {
+    using type = typename __tuple_like_common_type<_TTuple, _UTuple>::type;
+  };
 #endif // C++23
 
   /// @}
diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/include/std/unordered_map
index efad0cef584..ea6129d6494 100644
--- a/libstdc++-v3/include/std/unordered_map
+++ b/libstdc++-v3/include/std/unordered_map
@@ -51,6 +51,7 @@
 #define __glibcxx_want_node_extract
 #define __glibcxx_want_nonmember_container_access
 #define __glibcxx_want_unordered_map_try_emplace
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility
index f113d572e59..212513f6f48 100644
--- a/libstdc++-v3/include/std/utility
+++ b/libstdc++-v3/include/std/utility
@@ -92,6 +92,7 @@
 #define __glibcxx_want_tuple_element_t
 #define __glibcxx_want_tuples_by_type
 #define __glibcxx_want_unreachable
+#define __glibcxx_want_tuple_like
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
diff --git a/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
new file mode 100644
index 00000000000..ef06df1c53f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/p2165r4.cc
@@ -0,0 +1,173 @@
+// Verify P2165R4 enhancements to std::pair.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+
+struct A { };
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(false) pair(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b = pair_like;
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(pair_like);
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::as_const(pair_like);
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b = std::move(std::as_const(pair_like));
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  // Verify dangling checks.
+  static_assert( !std::is_constructible_v<pair<const int&, int>, pair_like_t<long>> );
+  static_assert( !std::is_constructible_v<pair<int, const int&>, pair_like_t<long>> );
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr explicit(true) pair(UPair&&);
+
+  static_assert( !std::is_convertible_v<pair_like_t<A>, pair<B, B>> );
+
+  pair_like_t<A> pair_like;
+
+  [&] {
+    pair<B, B> p2b{pair_like};
+    VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(pair_like)};
+    VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::as_const(pair_like)};
+    VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  }();
+  [&] {
+    pair<B, B> p2b{std::move(std::as_const(pair_like))};
+    VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+  }();
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr pair& operator=(UPair&&);
+
+  pair_like_t<A> pair_like;
+
+  pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<template<typename> class pair_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<pair-like UPair>
+  //   constexpr const pair& operator=(UPair&&) const;
+
+  pair_like_t<A> pair_like;
+
+  const pair<B, B> p2b;
+  p2b = pair_like;
+  VERIFY( p2b.first.m == 0 && p2b.second.m == 0 );
+  p2b = std::move(pair_like);
+  VERIFY( p2b.first.m == 1 && p2b.second.m == 1 );
+  p2b = std::as_const(pair_like);
+  VERIFY( p2b.first.m == 2 && p2b.second.m == 2 );
+  p2b = std::move(std::as_const(pair_like));
+  VERIFY( p2b.first.m == 3 && p2b.second.m == 3 );
+
+  return true;
+}
+
+template<typename T>
+using pair_like_array = array<T, 2>;
+
+template<typename T>
+using pair_like_tuple = tuple<T, T>;
+
+int
+main()
+{
+  static_assert( test01<pair_like_array>() );
+  static_assert( test02<pair_like_array>() );
+  static_assert( test03<pair_like_array>() );
+  static_assert( test04<pair_like_array>() );
+
+  static_assert( test01<pair_like_tuple>() );
+  static_assert( test02<pair_like_tuple>() );
+  static_assert( test03<pair_like_tuple>() );
+  static_assert( test04<pair_like_tuple>() );
+}
diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
new file mode 100644
index 00000000000..e2437c469b6
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/p2165r4.cc
@@ -0,0 +1,335 @@
+// Verify P2165R4 enhancements to std::tuple.
+// { dg-do run { target c++23 } }
+
+#include <array>
+#include <tuple>
+#include <utility>
+#include <memory>
+#include <testsuite_hooks.h>
+
+using std::array;
+using std::pair;
+using std::tuple;
+using std::allocator;
+using std::allocator_arg_t;
+using std::allocator_arg;
+
+namespace alloc {
+  struct B01;
+  struct B02;
+}
+
+template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
+
+struct A { };
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test01()
+{
+  struct B {
+    int m;
+    constexpr B(A&) : m(0) { }
+    constexpr B(A&&) : m(1) { }
+    constexpr B(const A&) : m(2) { }
+    constexpr B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(false) tuple(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b = tuple_like;
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::as_const(tuple_like);
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b = std::move(std::as_const(tuple_like));
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  // Verify dangling checks.
+  static_assert( !std::is_constructible_v<tuple<const int&, int, int>, tuple_like_t<long>> );
+  static_assert( !std::is_constructible_v<tuple<int, const int&, int>, tuple_like_t<long>> );
+  static_assert( !std::is_constructible_v<tuple<int, int, const int&>, tuple_like_t<long>> );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B01 {
+    int m;
+    B01(A&);
+    B01(A&&);
+    B01(const A&);
+    B01(const A&&);
+    constexpr B01(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    constexpr B01(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    constexpr B01(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test01()
+  {
+    using B = B01;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(false) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b = {allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+  // Verify dangling checks.
+    static_assert( !std::is_constructible_v<tuple<const int&, int, int>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+    static_assert( !std::is_constructible_v<tuple<int, const int&, int>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+    static_assert( !std::is_constructible_v<tuple<int, int, const int&>,
+					    allocator_arg_t, allocator<int>,
+					    tuple_like_t<long>> );
+
+    return true;
+  }
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test02()
+{
+  struct B {
+    int m;
+    constexpr explicit B(A&) : m(0) { }
+    constexpr explicit B(A&&) : m(1) { }
+    constexpr explicit B(const A&) : m(2) { }
+    constexpr explicit B(const A&&) : m(3) { }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr explicit(true) tuple(UTuple&&);
+
+  static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+  tuple_like_t<A> tuple_like;
+
+  [&] {
+    tuple<B, B, B> t3b{tuple_like};
+    VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::as_const(tuple_like)};
+    VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  }();
+  [&] {
+    tuple<B, B, B> t3b{std::move(std::as_const(tuple_like))};
+    VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+  }();
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B02 {
+    int m;
+    explicit B02(A&);
+    explicit B02(A&&);
+    explicit B02(const A&);
+    explicit B02(const A&&);
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : m(0) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&&) : m(1) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&) : m(2) { }
+    explicit constexpr B02(allocator_arg_t, allocator<int>, const A&&) : m(3) { }
+  };
+
+  template<template<typename> class tuple_like_t>
+  constexpr bool
+  test02()
+  {
+    using B = B02;
+
+    // template<tuple-like UTuple>
+    //   constexpr explicit(true) tuple(allocator_arg_t, const Alloc&, UTuple&&);
+
+    static_assert( !std::is_convertible_v<tuple_like_t<A>, tuple<B, B, B>> );
+
+    tuple_like_t<A> tuple_like;
+
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, tuple_like};
+      VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::as_const(tuple_like)};
+      VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+    }();
+    [&] {
+      tuple<B, B, B> t3b{allocator_arg, allocator<int>{}, std::move(std::as_const(tuple_like))};
+      VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+    }();
+
+    return true;
+  }
+}
+
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test03()
+{
+  struct B {
+    int m;
+    constexpr B& operator=(A&) { m = 0; return *this; }
+    constexpr B& operator=(A&&) { m = 1; return *this; }
+    constexpr B& operator=(const A&) { m = 2; return *this; }
+    constexpr B& operator=(const A&&) { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr tuple& operator=(UTuple&&);
+
+  tuple_like_t<A> tuple_like;
+
+  tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test04()
+{
+  struct B {
+    mutable int m;
+    constexpr const B& operator=(A&) const { m = 0; return *this; }
+    constexpr const B& operator=(A&&) const { m = 1; return *this; }
+    constexpr const B& operator=(const A&) const { m = 2; return *this; }
+    constexpr const B& operator=(const A&&) const { m = 3; return *this; }
+  };
+
+  // template<tuple-like UTuple>
+  //   constexpr const tuple& operator=(UTuple&&) const;
+
+  tuple_like_t<A> tuple_like;
+
+  const tuple<B, B, B> t3b;
+  t3b = tuple_like;
+  VERIFY( std::get<0>(t3b).m == 0 && std::get<1>(t3b).m == 0 && std::get<2>(t3b).m == 0 );
+  t3b = std::move(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 1 && std::get<1>(t3b).m == 1 && std::get<2>(t3b).m == 1 );
+  t3b = std::as_const(tuple_like);
+  VERIFY( std::get<0>(t3b).m == 2 && std::get<1>(t3b).m == 2 && std::get<2>(t3b).m == 2 );
+  t3b = std::move(std::as_const(tuple_like));
+  VERIFY( std::get<0>(t3b).m == 3 && std::get<1>(t3b).m == 3 && std::get<2>(t3b).m == 3 );
+
+  return true;
+}
+
+template<template<typename> class tuple_like_t>
+constexpr bool
+test05()
+{
+  // template<tuple-like UTuple>
+  //   constexpr bool operator==(const tuple&, const UTuple&);
+
+  static_assert( tuple{1, 2, 3} == tuple_like_t{1, 2, 3} );
+  static_assert( tuple{1, 2, 4} != tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} == tuple{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} != tuple{1, 2, 4} );
+
+  // template<tuple-like UTuple>
+  //   constexpr bool operator<=>const tuple&, const UTuple&);
+
+  static_assert( (tuple{1, 2, 3} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple{1, 2, 4} <=> tuple_like_t{1, 2, 3}) == std::strong_ordering::greater );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 3}) == std::strong_ordering::equal );
+  static_assert( (tuple_like_t{1, 2, 3} <=> tuple{1, 2, 4}) == std::strong_ordering::less  );
+
+  static_assert( tuple{1, 2, 4} > tuple_like_t{1, 2, 3} );
+  static_assert( tuple_like_t{1, 2, 3} < tuple{1, 2, 4} );
+
+  // template<tuple-like TTuple, tuple-like UTuple, ...>
+  //   struct basic_common_reference<TTuple, UTuple, ...>;
+
+  static_assert( std::same_as<std::common_reference_t<tuple_like_t<int>,
+						      tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_reference_t<tuple<int, long, int>,
+						      tuple_like_t<int>>,
+			      tuple<int, long, int>> );
+
+  // template<tuple-like TTuple, tuple-like UTuple>
+  //   struct common_type<TTuple, UTuple>;
+
+  static_assert( std::same_as<std::common_type_t<tuple_like_t<const int&>,
+						 tuple<int, long, int>>,
+			      tuple<int, long, int>> );
+
+  static_assert( std::same_as<std::common_type_t<tuple<int, long, int>,
+						 tuple_like_t<const int&>>,
+			      tuple<int, long, int>> );
+
+  return true;
+}
+
+template<typename T>
+using tuple_like_array = array<T, 3>;
+
+int
+main()
+{
+  static_assert( test01<tuple_like_array>() );
+  static_assert( alloc::test01<tuple_like_array>() );
+  static_assert( test02<tuple_like_array>() );
+  static_assert( alloc::test02<tuple_like_array>() );
+  static_assert( test03<tuple_like_array>() );
+  static_assert( test04<tuple_like_array>() );
+  static_assert( test05<tuple_like_array>() );
+}
diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
index b7717aed92c..ea4274d267b 100644
--- a/libstdc++-v3/testsuite/std/ranges/zip/1.cc
+++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
@@ -41,8 +41,8 @@ test01()
   VERIFY( i2 == z2.end() );
   VERIFY( ranges::size(z2) == 2 );
   VERIFY( ranges::size(std::as_const(z2)) == 2 );
-  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
-  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
+  VERIFY( std::get<0>(z2[0]) == 1 && std::get<1>(z2[0]) == 3 );
+  VERIFY( std::get<0>(z2[1]) == 2 && std::get<1>(z2[1]) == 4 );
   for (const auto [x, y] : z2)
     {
       VERIFY( y - x == 2 );
@@ -124,6 +124,18 @@ test04()
   return true;
 }
 
+constexpr bool
+test05()
+{
+  // PR libstdc++/109203
+  int x[] = {1, 1, 2};
+  int y[] = {2, 1, 3};
+  auto r = views::zip(x, y);
+  ranges::sort(r);
+
+  return true;
+}
+
 int
 main()
 {
@@ -131,4 +143,5 @@ main()
   static_assert(test02());
   static_assert(test03());
   static_assert(test04());
+  static_assert(test05());
 }
-- 
2.43.0.493.gbc7ee2e5e1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc
  2024-01-31 19:41               ` Patrick Palka
@ 2024-01-31 21:55                 ` Jonathan Wakely
  0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Wakely @ 2024-01-31 21:55 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Wed, 31 Jan 2024 at 19:41, Patrick Palka <ppalka@redhat.com> wrote:
>
> On Wed, 31 Jan 2024, Patrick Palka wrote:
>
> > On Wed, 24 Jan 2024, Patrick Palka wrote:
> > >
> > > In v2:
> > >
> > > * Named the template parameters of the forward declaration of pair.
> > > * Added dangling checks for the new tuple and pair constructors
> > >   and corresponding tests.
> > > * Replaced make_index_sequence with index_sequence_for where applicable.
> >
> > Ping.
>
> ... now also as an attachment since it seems Gmail doesn't like my
> inline patch.

Please add this above __dangles_from_tuple_like

// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 4045. tuple can create dangling references from tuple-like

OK fo trunk with that change, thanks.


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-01-31 21:56 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-23 23:53 [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Patrick Palka
2024-01-23 23:53 ` [PATCH 2/2] libstdc++: Implement P2165R4 changes to std::pair/tuple/etc Patrick Palka
2024-01-24 12:01   ` Jonathan Wakely
2024-01-24 12:08     ` Jonathan Wakely
2024-01-24 15:24     ` Patrick Palka
2024-01-24 17:27       ` Jonathan Wakely
2024-01-24 18:57         ` Patrick Palka
2024-01-24 19:47           ` Patrick Palka
2024-01-31 19:39             ` Patrick Palka
2024-01-31 19:41               ` Patrick Palka
2024-01-31 21:55                 ` Jonathan Wakely
2024-01-24 21:16         ` Jonathan Wakely
2024-01-24  0:21 ` [PATCH 1/2] libstdc++/pair: Define _S_const_assignable helper for C++20 Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).