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 );
