[2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2

Message ID 20220823013500.1756466-2-ppalka@redhat.com
State New, archived
Headers
Series [1/3] libstdc++: Separate construct/convertibility tests for std::tuple |

Commit Message

Patrick Palka Aug. 23, 2022, 1:34 a.m. UTC
  This implements the non-<ranges> changes from P2321R2, which primarily
consist of new converting constructors, assignment operator and swap
overloads for std::pair and std::tuple.

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

libstdc++-v3/ChangeLog:

	* include/bits/stl_bvector.h (_Bit_reference::operator=): Define
	const overload for C++23 as per P2321R2.
	* include/bits/stl_pair.h (pair::swap): Likewise.
	(pair::pair): Define additional converting constructors for
	C++23 as per P2321R2.
	(pair::operator=): Define const overloads for C++23 as per
	P2321R2.
	(swap): Define overload taking const pair& for C++23 as per
	P2321R2.
	(basic_common_reference): Define partial specialization for
	pair for C++23 as per P2321R2.
	(common_type): Likewise.
	* include/bits/uses_allocator_args.h
	(uses_allocator_construction_args): Define additional pair
	overloads for C++23 as per P2321R2.
	* include/std/tuple (_Tuple_impl::_Tuple_impl): Define
	additional converting constructors for C++23 as per P2321R2.
	(_Tuple_impl::_M_assign): Define const overloads for C++23
	as per P2321R2.
	(_Tuple_impl::_M_swap): Likewise.
	(tuple::__constructible): Define as a convenient renaming of
	_TCC<true>::__constructible.
	(tuple::__convertible): As above but for _TCC<true>::__convertible.
	(tuple::tuple): Define additional converting constructors for
	C++23 as per P2321R2.
	(tuple::operator=): Define const overloads for C++23 as per
	P2321R2.
	(tuple::swap): Likewise.
	(basic_common_reference): Define partial specialization for
	tuple for C++23 as per P2321R2.
	(common_type): Likewise.
	* testsuite/20_util/pair/p2321.cc: New test.
	* testsuite/20_util/tuple/p2321.cc: New test.
	* testsuite/23_containers/vector/bool/element_access/1.cc: New test.
---
 libstdc++-v3/include/bits/stl_bvector.h       |  12 +
 libstdc++-v3/include/bits/stl_pair.h          | 118 +++-
 .../include/bits/uses_allocator_args.h        |  41 ++
 libstdc++-v3/include/std/tuple                | 416 +++++++++++
 libstdc++-v3/testsuite/20_util/pair/p2321.cc  | 208 ++++++
 libstdc++-v3/testsuite/20_util/tuple/p2321.cc | 664 ++++++++++++++++++
 .../vector/bool/element_access/1.cc           |  26 +
 7 files changed, 1480 insertions(+), 5 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/pair/p2321.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/p2321.cc
 create mode 100644 libstdc++-v3/testsuite/23_containers/vector/bool/element_access/1.cc
  

Comments

Jonathan Wakely Aug. 23, 2022, 12:03 p.m. UTC | #1
On Tue, 23 Aug 2022 at 02:36, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
> --- a/libstdc++-v3/include/bits/stl_pair.h
> +++ b/libstdc++-v3/include/bits/stl_pair.h
> @@ -212,6 +212,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         swap(second, __p.second);
>        }
>
> +#if __cplusplus > 202002L
> +      /// Swap the first members and then the second members.
> +      constexpr void
> +      swap(const pair& __p) const
> +      noexcept(__and_<__is_nothrow_swappable<const _T1>,
> +                     __is_nothrow_swappable<const _T2>>::value)

This could use __and_v (which is just __and_::value today, but could
theoretically be optimized to use a requires expression and avoid
instantiating __and_ one day).

Is consistency with the C++11 overload more important? I *hope* we
won't need to make many changes to these noexcept-specifiers, so the
maintenance burden of using __ad_::value in one and __and_v in the
other shouldn't be too high.

> @@ -710,6 +792,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      noexcept(noexcept(__x.swap(__y)))
>      { __x.swap(__y); }
>
> +#if __cplusplus > 202002L
> +  template<typename _T1, typename _T2>
> +    requires is_swappable<const _T1>::value && is_swappable<const _T2>::value

is_swappable_v instead of ::value here ... this is already using a
requires-clause and so is substantially different to the old overload
anyway.



> +
>        // tuple swap
>        _GLIBCXX20_CONSTEXPR
>        void
>        swap(tuple& __in)
>        noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value)
>        { _Inherited::_M_swap(__in); }
> +
> +#if __cplusplus > 202002L
> +      constexpr void
> +      swap(const tuple& __in) const
> +      noexcept(__and_<__is_nothrow_swappable<const _Elements>...>::value)

__and_v ?




>        _GLIBCXX20_CONSTEXPR
>        void
>        swap(tuple& __in)
>        noexcept(__and_<__is_nothrow_swappable<_T1>,
>                       __is_nothrow_swappable<_T2>>::value)
>        { _Inherited::_M_swap(__in); }
> +
> +#if __cplusplus > 202002L
> +      constexpr void
> +      swap(const tuple& __in) const
> +      noexcept(__and_<__is_nothrow_swappable<const _T1>,
> +                     __is_nothrow_swappable<const _T2>>::value)

__and_v ?


Thanks for doing this, those changes looked tedious to implement and test!

If you agree with the suggestions to use _v variable templates, this
is OK for trunk with those changes. I am willing to be persuaded to
not use the variable templates if there's a good reason I've missed.
  
Patrick Palka Aug. 23, 2022, 3:14 p.m. UTC | #2
On Tue, 23 Aug 2022, Jonathan Wakely wrote:

> On Tue, 23 Aug 2022 at 02:36, Patrick Palka via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> > --- a/libstdc++-v3/include/bits/stl_pair.h
> > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > @@ -212,6 +212,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         swap(second, __p.second);
> >        }
> >
> > +#if __cplusplus > 202002L
> > +      /// Swap the first members and then the second members.
> > +      constexpr void
> > +      swap(const pair& __p) const
> > +      noexcept(__and_<__is_nothrow_swappable<const _T1>,
> > +                     __is_nothrow_swappable<const _T2>>::value)
> 
> This could use __and_v (which is just __and_::value today, but could
> theoretically be optimized to use a requires expression and avoid
> instantiating __and_ one day).
> 
> Is consistency with the C++11 overload more important? I *hope* we
> won't need to make many changes to these noexcept-specifiers, so the
> maintenance burden of using __ad_::value in one and __and_v in the
> other shouldn't be too high.

Makes sense.

> 
> > @@ -710,6 +792,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >      noexcept(noexcept(__x.swap(__y)))
> >      { __x.swap(__y); }
> >
> > +#if __cplusplus > 202002L
> > +  template<typename _T1, typename _T2>
> > +    requires is_swappable<const _T1>::value && is_swappable<const _T2>::value
> 
> is_swappable_v instead of ::value here ... this is already using a
> requires-clause and so is substantially different to the old overload
> anyway.
> 
> 
> 
> > +
> >        // tuple swap
> >        _GLIBCXX20_CONSTEXPR
> >        void
> >        swap(tuple& __in)
> >        noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value)
> >        { _Inherited::_M_swap(__in); }
> > +
> > +#if __cplusplus > 202002L
> > +      constexpr void
> > +      swap(const tuple& __in) const
> > +      noexcept(__and_<__is_nothrow_swappable<const _Elements>...>::value)
> 
> __and_v ?
> 
> 
> 
> 
> >        _GLIBCXX20_CONSTEXPR
> >        void
> >        swap(tuple& __in)
> >        noexcept(__and_<__is_nothrow_swappable<_T1>,
> >                       __is_nothrow_swappable<_T2>>::value)
> >        { _Inherited::_M_swap(__in); }
> > +
> > +#if __cplusplus > 202002L
> > +      constexpr void
> > +      swap(const tuple& __in) const
> > +      noexcept(__and_<__is_nothrow_swappable<const _T1>,
> > +                     __is_nothrow_swappable<const _T2>>::value)
> 
> __and_v ?
> 
> 
> Thanks for doing this, those changes looked tedious to implement and test!
> 
> If you agree with the suggestions to use _v variable templates, this
> is OK for trunk with those changes. I am willing to be persuaded to
> not use the variable templates if there's a good reason I've missed.

Agreed on all points!  Thanks a lot.
  

Patch

diff --git a/libstdc++-v3/include/bits/stl_bvector.h b/libstdc++-v3/include/bits/stl_bvector.h
index d256af40f40..c5fd19e7309 100644
--- a/libstdc++-v3/include/bits/stl_bvector.h
+++ b/libstdc++-v3/include/bits/stl_bvector.h
@@ -106,6 +106,18 @@  _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       return *this;
     }
 
+#if __cplusplus > 202002L
+    constexpr const _Bit_reference&
+    operator=(bool __x) const noexcept
+    {
+      if (__x)
+	*_M_p |= _M_mask;
+      else
+	*_M_p &= ~_M_mask;
+      return *this;
+    }
+#endif
+
     _GLIBCXX20_CONSTEXPR
     _Bit_reference&
     operator=(const _Bit_reference& __x) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h
index 831e770d54b..d0efba635bc 100644
--- a/libstdc++-v3/include/bits/stl_pair.h
+++ b/libstdc++-v3/include/bits/stl_pair.h
@@ -212,6 +212,19 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	swap(second, __p.second);
       }
 
+#if __cplusplus > 202002L
+      /// Swap the first members and then the second members.
+      constexpr void
+      swap(const pair& __p) const
+      noexcept(__and_<__is_nothrow_swappable<const _T1>,
+		      __is_nothrow_swappable<const _T2>>::value)
+      {
+	using std::swap;
+	swap(first, __p.first);
+	swap(second, __p.second);
+      }
+#endif // C++23
+
     private:
       template<typename... _Args1, size_t... _Indexes1,
 	       typename... _Args2, size_t... _Indexes2>
@@ -283,7 +296,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y))
 	{ }
 
-      /// Converting constructor from a `pair<U1, U2>` lvalue
+      /// Converting constructor from a const `pair<U1, U2>` lvalue
       template<typename _U1, typename _U2>
 	requires (_S_constructible<const _U1&, const _U2&>())
 	constexpr explicit(!_S_convertible<const _U1&, const _U2&>())
@@ -292,7 +305,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: first(__p.first), second(__p.second)
 	{ }
 
-      /// Converting constructor from a `pair<U1, U2>` rvalue
+      /// Converting constructor from a non-const `pair<U1, U2>` rvalue
       template<typename _U1, typename _U2>
 	requires (_S_constructible<_U1, _U2>())
 	constexpr explicit(!_S_convertible<_U1, _U2>())
@@ -302,6 +315,27 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  second(std::forward<_U2>(__p.second))
 	{ }
 
+#if __cplusplus > 202002L
+      /// Converting constructor from a non-const `pair<U1, U2>` lvalue
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<_U1&, _U2&>())
+	constexpr explicit(!_S_convertible<_U1&, _U2&>())
+	pair(pair<_U1, _U2>& __p)
+	noexcept(_S_nothrow_constructible<_U1&, _U2&>())
+	: first(__p.first), second(__p.second)
+	{ }
+
+      /// Converting constructor from a const `pair<U1, U2>` rvalue
+      template<typename _U1, typename _U2>
+	requires (_S_constructible<const _U1, const _U2>())
+	constexpr explicit(!_S_convertible<const _U1, const _U2>())
+	pair(const pair<_U1, _U2>&& __p)
+	noexcept(_S_nothrow_constructible<const _U1, const _U2>())
+	: first(std::forward<const _U1>(__p.first)),
+	  second(std::forward<const _U2>(__p.second))
+	{ }
+#endif
+
   private:
       /// @cond undocumented
       template<typename _U1, typename _U2>
@@ -349,7 +383,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return *this;
       }
 
-      /// Converting assignment from a `pair<U1, U2>` lvalue
+      /// Converting assignment from a const `pair<U1, U2>` lvalue
       template<typename _U1, typename _U2>
 	constexpr pair&
 	operator=(const pair<_U1, _U2>& __p)
@@ -361,7 +395,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 
-      /// Converting assignment from a `pair<U1, U2>` rvalue
+      /// Converting assignment from a non-const `pair<U1, U2>` rvalue
       template<typename _U1, typename _U2>
 	constexpr pair&
 	operator=(pair<_U1, _U2>&& __p)
@@ -372,7 +406,55 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  second = std::forward<_U2>(__p.second);
 	  return *this;
 	}
-#else
+
+#if __cplusplus > 202002L
+      /// Copy assignment operator
+      constexpr const pair&
+      operator=(const pair& __p) const
+      requires is_copy_assignable_v<const first_type>
+	&& is_copy_assignable_v<const second_type>
+      {
+	first = __p.first;
+	second = __p.second;
+	return *this;
+      }
+
+      /// Move assignment operator
+      constexpr const pair&
+      operator=(pair&& __p) const
+      requires is_assignable_v<const first_type&, first_type>
+	&& is_assignable_v<const second_type&, second_type>
+      {
+	first = std::forward<first_type>(__p.first);
+	second = std::forward<second_type>(__p.second);
+	return *this;
+      }
+
+      /// Converting assignment from a const `pair<U1, U2>` lvalue
+      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&>
+	{
+	  first = __p.first;
+	  second = __p.second;
+	  return *this;
+	}
+
+      /// Converting assignment from a non-const `pair<U1, U2>` rvalue
+      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>
+	{
+	  first = std::forward<_U1>(__p.first);
+	  second = std::forward<_U2>(__p.second);
+	  return *this;
+	}
+#endif // C++23
+#else // !__cpp_lib_concepts
       // C++11/14/17 implementation using enable_if, partially constexpr.
 
       /** The default constructor creates @c first and @c second using their
@@ -710,6 +792,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     noexcept(noexcept(__x.swap(__y)))
     { __x.swap(__y); }
 
+#if __cplusplus > 202002L
+  template<typename _T1, typename _T2>
+    requires is_swappable<const _T1>::value && is_swappable<const _T2>::value
+    constexpr void
+    swap(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
+    noexcept(noexcept(__x.swap(__y)))
+    { __x.swap(__y); }
+#endif // C++23
+
 #if __cplusplus > 201402L || !defined(__STRICT_ANSI__) // c++1z or gnu++11
   template<typename _T1, typename _T2>
     typename enable_if<!__and_<__is_swappable<_T1>,
@@ -918,6 +1009,23 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     get(const pair<_Up, _Tp>&& __p) noexcept
     { return std::move(__p.second); }
 
+#if __cplusplus > 202002L
+  template<typename _T1, typename _T2, typename _U1, typename _U2,
+	   template<typename> class _TQual, template<typename> class _UQual>
+    requires requires { typename pair<common_reference_t<_TQual<_T1>, _UQual<_U1>>,
+				      common_reference_t<_TQual<_T2>, _UQual<_U2>>>; }
+  struct basic_common_reference<pair<_T1, _T2>, pair<_U1, _U2>, _TQual, _UQual>
+  {
+    using type = pair<common_reference_t<_TQual<_T1>, _UQual<_U1>>,
+		      common_reference_t<_TQual<_T2>, _UQual<_U2>>>;
+  };
+
+  template<typename _T1, typename _T2, typename _U1, typename _U2>
+    requires requires { typename pair<common_type_t<_T1, _U1>, common_type_t<_T2, _U2>>; }
+  struct common_type<pair<_T1, _T2>, pair<_U1, _U2>>
+  { using type = pair<common_type_t<_T1, _U1>, common_type_t<_T2, _U2>>; };
+#endif
+
 #endif // C++14
   /// @}
 #endif // C++11
diff --git a/libstdc++-v3/include/bits/uses_allocator_args.h b/libstdc++-v3/include/bits/uses_allocator_args.h
index 09cdbf1aaa8..3528e4cc4fa 100644
--- a/libstdc++-v3/include/bits/uses_allocator_args.h
+++ b/libstdc++-v3/include/bits/uses_allocator_args.h
@@ -107,6 +107,17 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     constexpr auto
     uses_allocator_construction_args(const _Alloc&, pair<_Up, _Vp>&&) noexcept;
 
+#if __cplusplus > 202002L
+  template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&,
+				     pair<_Up, _Vp>&) noexcept;
+
+  template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc&, const pair<_Up, _Vp>&&) noexcept;
+#endif
+
   template<_Std_pair _Tp, typename _Alloc, typename _Tuple1, typename _Tuple2>
     constexpr auto
     uses_allocator_construction_args(const _Alloc& __a, piecewise_construct_t,
@@ -181,6 +192,36 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    std::move(__pr).second));
     }
 
+#if __cplusplus > 202002L
+  template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a,
+				     pair<_Up, _Vp>& __pr) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a, __pr.first),
+	  std::uses_allocator_construction_args<_Tp2>(__a, __pr.second));
+    }
+
+  template<_Std_pair _Tp, typename _Alloc, typename _Up, typename _Vp>
+    constexpr auto
+    uses_allocator_construction_args(const _Alloc& __a,
+				     const pair<_Up, _Vp>&& __pr) noexcept
+    {
+      using _Tp1 = typename _Tp::first_type;
+      using _Tp2 = typename _Tp::second_type;
+
+      return std::make_tuple(piecewise_construct,
+	  std::uses_allocator_construction_args<_Tp1>(__a,
+	    std::move(__pr).first),
+	  std::uses_allocator_construction_args<_Tp2>(__a,
+	    std::move(__pr).second));
+    }
+#endif
+
   template<typename _Tp, typename _Alloc, typename... _Args>
     constexpr _Tp
     make_obj_using_allocator(const _Alloc& __a, _Args&&... __args)
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index d0c168fd7e2..812e70d17be 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -316,6 +316,24 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 		(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in)))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename... _UElements>
+	constexpr
+	_Tuple_impl(_Tuple_impl<_Idx, _UElements...>& __in)
+	: _Inherited(_Tuple_impl<_Idx, _UElements...>::_M_tail(__in)),
+	  _Base(_Tuple_impl<_Idx, _UElements...>::_M_head(__in))
+	{ }
+
+      template<typename _UHead, typename... _UTails>
+	constexpr
+	_Tuple_impl(const _Tuple_impl<_Idx, _UHead, _UTails...>&& __in)
+	: _Inherited(std::move
+		     (_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in))),
+	  _Base(std::forward<const _UHead>
+		(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in)))
+	{ }
+#endif
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -379,6 +397,29 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 		(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in)))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename _Alloc, typename _UHead, typename... _UTails>
+	constexpr
+	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a,
+		    _Tuple_impl<_Idx, _UHead, _UTails...>& __in)
+	: _Inherited(__tag, __a,
+		     _Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in)),
+	  _Base(__use_alloc<_Head, _Alloc, _UHead&>(__a),
+		_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in))
+	{ }
+
+      template<typename _Alloc, typename _UHead, typename... _UTails>
+	constexpr
+	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a,
+		    const _Tuple_impl<_Idx, _UHead, _UTails...>&& __in)
+	: _Inherited(__tag, __a, std::move
+		     (_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in))),
+	  _Base(__use_alloc<_Head, _Alloc, const _UHead>(__a),
+		std::forward<const _UHead>
+		(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in)))
+	{ }
+#endif
+
       template<typename... _UElements>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -400,6 +441,27 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	      std::move(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in)));
 	}
 
+#if __cplusplus > 202002L
+      template<typename... _UElements>
+	constexpr void
+	_M_assign(const _Tuple_impl<_Idx, _UElements...>& __in) const
+	{
+	  _M_head(*this) = _Tuple_impl<_Idx, _UElements...>::_M_head(__in);
+	  _M_tail(*this)._M_assign(
+	      _Tuple_impl<_Idx, _UElements...>::_M_tail(__in));
+	}
+
+      template<typename _UHead, typename... _UTails>
+	constexpr void
+	_M_assign(_Tuple_impl<_Idx, _UHead, _UTails...>&& __in) const
+	{
+	  _M_head(*this) = std::forward<_UHead>
+	    (_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in));
+	  _M_tail(*this)._M_assign(
+	      std::move(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in)));
+	}
+#endif
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -409,6 +471,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	swap(_M_head(*this), _M_head(__in));
 	_Inherited::_M_swap(_M_tail(__in));
       }
+
+#if __cplusplus > 202002L
+      constexpr void
+      _M_swap(const _Tuple_impl& __in) const
+      {
+	using std::swap;
+	swap(_M_head(*this), _M_head(__in));
+	_Inherited::_M_swap(_M_tail(__in));
+      }
+#endif
     };
 
   // Basis case of inheritance recursion.
@@ -469,6 +541,20 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _Base(std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in)))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename _UHead>
+	constexpr
+	_Tuple_impl(_Tuple_impl<_Idx, _UHead>& __in)
+	: _Base(_Tuple_impl<_Idx, _UHead>::_M_head(__in))
+	{ }
+
+      template<typename _UHead>
+	constexpr
+	_Tuple_impl(const _Tuple_impl<_Idx, _UHead>&& __in)
+	: _Base(std::forward<const _UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in)))
+	{ }
+#endif
+
       template<typename _Alloc>
 	_GLIBCXX20_CONSTEXPR
 	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
@@ -521,6 +607,24 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 		std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in)))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename _Alloc, typename _UHead>
+	constexpr
+	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a,
+		    _Tuple_impl<_Idx, _UHead>& __in)
+	: _Base(__use_alloc<_Head, _Alloc, _UHead&>(__a),
+		_Tuple_impl<_Idx, _UHead>::_M_head(__in))
+	{ }
+
+      template<typename _Alloc, typename _UHead>
+	constexpr
+	_Tuple_impl(allocator_arg_t __tag, const _Alloc& __a,
+		    const _Tuple_impl<_Idx, _UHead>&& __in)
+	: _Base(__use_alloc<_Head, _Alloc, const _UHead>(__a),
+		std::forward<const _UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in)))
+	{ }
+#endif
+
       template<typename _UHead>
 	_GLIBCXX20_CONSTEXPR
 	void
@@ -538,6 +642,23 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    = std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in));
 	}
 
+#if __cplusplus > 202002L
+      template<typename _UHead>
+	constexpr void
+	_M_assign(const _Tuple_impl<_Idx, _UHead>& __in) const
+	{
+	  _M_head(*this) = _Tuple_impl<_Idx, _UHead>::_M_head(__in);
+	}
+
+      template<typename _UHead>
+	constexpr void
+	_M_assign(_Tuple_impl<_Idx, _UHead>&& __in) const
+	{
+	  _M_head(*this)
+	    = std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in));
+	}
+#endif
+
     protected:
       _GLIBCXX20_CONSTEXPR
       void
@@ -546,6 +667,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	using std::swap;
 	swap(_M_head(*this), _M_head(__in));
       }
+
+#if __cplusplus > 202002L
+      constexpr void
+      _M_swap(const _Tuple_impl& __in) const
+      {
+	using std::swap;
+	swap(_M_head(*this), _M_head(__in));
+      }
+#endif
     };
 
   // Concept utility functions, reused in conditionally-explicit
@@ -728,6 +858,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	static constexpr bool __use_other_ctor()
 	{ return _UseOtherCtor<_Tuple>::value; }
 
+#if __cplusplus > 202002L
+      template<typename... _Args>
+	static constexpr bool __constructible
+	  = _TCC<true>::template __constructible<_Args...>;
+
+      template<typename... _Args>
+	static constexpr bool __convertible
+	  = _TCC<true>::template __convertible<_Args...>;
+#endif
+
     public:
       template<typename _Dummy = void,
 	       _ImplicitDefaultCtor<is_void<_Dummy>::value> = true>
@@ -815,6 +955,29 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	noexcept(__nothrow_constructible<_UElements...>())
 	: _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { }
 
+#if __cplusplus > 202002L
+      template<typename... _UElements>
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (!__use_other_ctor<tuple<_UElements...>&>())
+	  && __constructible<_UElements&...>
+	explicit(!__convertible<_UElements&...>)
+	constexpr
+	tuple(tuple<_UElements...>& __in)
+	noexcept(__nothrow_constructible<_UElements&...>())
+	: _Inherited(static_cast<_Tuple_impl<0, _UElements...>&>(__in))
+	{ }
+
+      template<typename... _UElements>
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (!__use_other_ctor<const tuple<_UElements...>&&>())
+	  && __constructible<const _UElements...>
+	explicit(!__convertible<const _UElements...>)
+	constexpr
+	tuple(const tuple<_UElements...>&& __in)
+	noexcept(__nothrow_constructible<const _UElements...>())
+	: _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&&>(__in)) { }
+#endif
+
       // Allocator-extended constructors.
 
       template<typename _Alloc,
@@ -913,6 +1076,32 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	             static_cast<_Tuple_impl<0, _UElements...>&&>(__in))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename _Alloc, typename... _UElements>
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (!__use_other_ctor<tuple<_UElements...>&>())
+	  && __constructible<_UElements&...>
+	explicit(!__convertible<_UElements&...>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a,
+	      tuple<_UElements...>& __in)
+	: _Inherited(__tag, __a,
+	             static_cast<_Tuple_impl<0, _UElements...>&>(__in))
+	{ }
+
+      template<typename _Alloc, typename... _UElements>
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (!__use_other_ctor<const tuple<_UElements...>>())
+	  && __constructible<const _UElements...>
+	explicit(!__convertible<const _UElements...>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a,
+	      const tuple<_UElements...>&& __in)
+	: _Inherited(__tag, __a,
+	             static_cast<const _Tuple_impl<0, _UElements...>&&>(__in))
+	{ }
+#endif
+
       // tuple assignment
 
       _GLIBCXX20_CONSTEXPR
@@ -957,12 +1146,57 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 
+#if __cplusplus > 202002L
+      constexpr const tuple&
+      operator=(const tuple& __in) const
+      requires (is_copy_assignable_v<const _Elements> && ...)
+      {
+	this->_M_assign(__in);
+	return *this;
+      }
+
+      constexpr const tuple&
+      operator=(tuple&& __in) const
+      requires (is_assignable_v<const _Elements&, _Elements> && ...)
+      {
+	this->_M_assign(std::move(__in));
+	return *this;
+      }
+
+      template<typename... _UElements>
+	constexpr const tuple&
+	operator=(const tuple<_UElements...>& __in) const
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (is_assignable_v<const _Elements&, const _UElements&> && ...)
+	{
+	  this->_M_assign(__in);
+	  return *this;
+	}
+
+      template<typename... _UElements>
+	constexpr const tuple&
+	operator=(tuple<_UElements...>&& __in) const
+	requires (sizeof...(_Elements) == sizeof...(_UElements))
+	  && (is_assignable_v<const _Elements&, _UElements> && ...)
+	{
+	  this->_M_assign(std::move(__in));
+	  return *this;
+	}
+#endif
+
       // tuple swap
       _GLIBCXX20_CONSTEXPR
       void
       swap(tuple& __in)
       noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value)
       { _Inherited::_M_swap(__in); }
+
+#if __cplusplus > 202002L
+      constexpr void
+      swap(const tuple& __in) const
+      noexcept(__and_<__is_nothrow_swappable<const _Elements>...>::value)
+      { _Inherited::_M_swap(__in); }
+#endif
     };
 
 #if __cpp_deduction_guides >= 201606
@@ -985,6 +1219,9 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     public:
       _GLIBCXX20_CONSTEXPR
       void swap(tuple&) noexcept { /* no-op */ }
+#if __cplusplus > 202002L
+      constexpr void swap(const tuple&) const noexcept { /* no-op */ }
+#endif
       // We need the default since we're going to define no-op
       // allocator constructors.
       tuple() = default;
@@ -1064,6 +1301,16 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	static constexpr bool __is_alloc_arg()
 	{ return is_same<__remove_cvref_t<_U1>, allocator_arg_t>::value; }
 
+#if __cplusplus > 202002L
+      template<typename _U1, typename _U2>
+	static constexpr bool __constructible
+	  = _TCC<true>::template __constructible<_U1, _U2>;
+
+      template<typename _U1, typename _U2>
+	static constexpr bool __convertible
+	  = _TCC<true>::template __convertible<_U1, _U2>;
+#endif
+
     public:
       template<bool _Dummy = true,
 	       _ImplicitDefaultCtor<_Dummy, _T1, _T2> = true>
@@ -1139,6 +1386,24 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	noexcept(__nothrow_constructible<_U1, _U2>())
 	: _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in)) { }
 
+#if __cplusplus > 202002L
+      template<typename _U1, typename _U2>
+	requires __constructible<_U1&, _U2&>
+	explicit(!__convertible<_U1&, _U2&>)
+	constexpr
+	tuple(tuple<_U1, _U2>& __in)
+	noexcept(__nothrow_constructible<_U1&, _U2&>())
+	: _Inherited(static_cast<_Tuple_impl<0, _U1, _U2>&>(__in)) { }
+
+      template<typename _U1, typename _U2>
+	requires __constructible<const _U1, const _U2>
+	explicit(!__convertible<const _U1, const _U2>)
+	constexpr
+	tuple(const tuple<_U1, _U2>&& __in)
+	noexcept(__nothrow_constructible<const _U1, const _U2>())
+	: _Inherited(static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in)) { }
+#endif
+
       template<typename _U1, typename _U2,
 	       _ImplicitCtor<true, const _U1&, const _U2&> = true>
 	constexpr
@@ -1169,6 +1434,25 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _Inherited(std::forward<_U1>(__in.first),
 		     std::forward<_U2>(__in.second)) { }
 
+#if __cplusplus > 202002L
+      template<typename _U1, typename _U2>
+	requires __constructible<_U1&, _U2&>
+	explicit(!__convertible<_U1&, _U2&>)
+	constexpr
+	tuple(pair<_U1, _U2>& __in)
+	noexcept(__nothrow_constructible<_U1&, _U2&>())
+	: _Inherited(__in.first, __in.second) { }
+
+      template<typename _U1, typename _U2>
+	requires __constructible<const _U1, const _U2>
+	explicit(!__convertible<const _U1, const _U2>)
+	constexpr
+	tuple(const pair<_U1, _U2>&& __in)
+	noexcept(__nothrow_constructible<const _U1, const _U2>())
+	: _Inherited(std::forward<const _U1>(__in.first),
+		     std::forward<const _U2>(__in.second)) { }
+#endif
+
       // Allocator-extended constructors.
 
       template<typename _Alloc,
@@ -1252,6 +1536,28 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _Inherited(__tag, __a, static_cast<_Tuple_impl<0, _U1, _U2>&&>(__in))
 	{ }
 
+#if __cplusplus > 202002L
+      template<typename _Alloc, typename _U1, typename _U2>
+	requires __constructible<_U1&, _U2&>
+	explicit(!__convertible<_U1&, _U2&>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a,
+	      tuple<_U1, _U2>& __in)
+	: _Inherited(__tag, __a,
+	             static_cast<_Tuple_impl<0, _U1, _U2>&>(__in))
+	{ }
+
+      template<typename _Alloc, typename _U1, typename _U2>
+	requires __constructible<const _U1, const _U2>
+	explicit(!__convertible<const _U1, const _U2>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a,
+	      const tuple<_U1, _U2>&& __in)
+	: _Inherited(__tag, __a,
+	             static_cast<const _Tuple_impl<0, _U1, _U2>&&>(__in))
+	{ }
+#endif
+
       template<typename _Alloc, typename _U1, typename _U2,
 	       _ImplicitCtor<true, const _U1&, const _U2&> = true>
 	_GLIBCXX20_CONSTEXPR
@@ -1282,6 +1588,24 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _Inherited(__tag, __a, std::forward<_U1>(__in.first),
 		     std::forward<_U2>(__in.second)) { }
 
+#if __cplusplus > 202002L
+      template<typename _Alloc, typename _U1, typename _U2>
+	requires __constructible<_U1&, _U2&>
+	explicit(!__convertible<_U1&, _U2&>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a,
+	      pair<_U1, _U2>& __in)
+	: _Inherited(__tag, __a, __in.first, __in.second) { }
+
+      template<typename _Alloc, typename _U1, typename _U2>
+	requires __constructible<const _U1, const _U2>
+	explicit(!__convertible<const _U1, const _U2>)
+	constexpr
+	tuple(allocator_arg_t __tag, const _Alloc& __a, const pair<_U1, _U2>&& __in)
+	: _Inherited(__tag, __a, std::forward<const _U1>(__in.first),
+		     std::forward<const _U2>(__in.second)) { }
+#endif
+
       // Tuple assignment.
 
       _GLIBCXX20_CONSTEXPR
@@ -1326,6 +1650,44 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 
+#if __cplusplus > 202002L
+      constexpr const tuple&
+      operator=(const tuple& __in) const
+      requires is_copy_assignable_v<const _T1> && is_copy_assignable_v<const _T2>
+      {
+	this->_M_assign(__in);
+	return *this;
+      }
+
+      constexpr const tuple&
+      operator=(tuple&& __in) const
+      requires is_assignable_v<const _T1&, _T1> && is_assignable_v<const _T2, _T2>
+      {
+	this->_M_assign(std::move(__in));
+	return *this;
+      }
+
+      template<typename _U1, typename _U2>
+	constexpr const tuple&
+	operator=(const tuple<_U1, _U2>& __in) const
+	requires is_assignable_v<const _T1&, const _U1&>
+	  && is_assignable_v<const _T2&, const _U2&>
+	{
+	  this->_M_assign(__in);
+	  return *this;
+	}
+
+      template<typename _U1, typename _U2>
+	constexpr const tuple&
+	operator=(tuple<_U1, _U2>&& __in) const
+	requires is_assignable_v<const _T1&, _U1>
+	  && is_assignable_v<const _T2&, _U2>
+	{
+	  this->_M_assign(std::move(__in));
+	  return *this;
+	}
+#endif
+
       template<typename _U1, typename _U2>
 	_GLIBCXX20_CONSTEXPR
 	__enable_if_t<__assignable<const _U1&, const _U2&>(), tuple&>
@@ -1348,12 +1710,44 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  return *this;
 	}
 
+#if __cplusplus > 202002L
+      template<typename _U1, typename _U2>
+	constexpr const tuple&
+	operator=(const pair<_U1, _U2>& __in) const
+	requires is_assignable_v<const _T1&, const _U1&>
+	  && is_assignable_v<const _T2&, const _U2&>
+	{
+	  this->_M_head(*this) = __in.first;
+	  this->_M_tail(*this)._M_head(*this) = __in.second;
+	  return *this;
+	}
+
+      template<typename _U1, typename _U2>
+	constexpr const tuple&
+	operator=(pair<_U1, _U2>&& __in) const
+	requires is_assignable_v<const _T1&, _U1>
+	  && is_assignable_v<const _T2&, _U2>
+	{
+	  this->_M_head(*this) = std::forward<_U1>(__in.first);
+	  this->_M_tail(*this)._M_head(*this) = std::forward<_U2>(__in.second);
+	  return *this;
+	}
+#endif
+
       _GLIBCXX20_CONSTEXPR
       void
       swap(tuple& __in)
       noexcept(__and_<__is_nothrow_swappable<_T1>,
 		      __is_nothrow_swappable<_T2>>::value)
       { _Inherited::_M_swap(__in); }
+
+#if __cplusplus > 202002L
+      constexpr void
+      swap(const tuple& __in) const
+      noexcept(__and_<__is_nothrow_swappable<const _T1>,
+		      __is_nothrow_swappable<const _T2>>::value)
+      { _Inherited::_M_swap(__in); }
+#endif
     };
 
 
@@ -1781,6 +2175,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     noexcept(noexcept(__x.swap(__y)))
     { __x.swap(__y); }
 
+#if __cplusplus > 202002L
+  template<typename... _Elements>
+    requires (__is_swappable<const _Elements>::value && ...)
+    constexpr void
+    swap(const tuple<_Elements...>& __x, const tuple<_Elements...>& __y)
+    noexcept(noexcept(__x.swap(__y)))
+    { __x.swap(__y); }
+#endif
+
 #if __cplusplus > 201402L || !defined(__STRICT_ANSI__) // c++1z or gnu++11
   template<typename... _Elements>
     _GLIBCXX20_CONSTEXPR
@@ -1905,6 +2308,19 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 #endif // C++17
 
+#if __cplusplus > 202002L
+  template<typename... _TTypes, typename... _UTypes,
+	   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>...>; };
+#endif
+
   /// @}
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/testsuite/20_util/pair/p2321.cc b/libstdc++-v3/testsuite/20_util/pair/p2321.cc
new file mode 100644
index 00000000000..4f436ee03d6
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/pair/p2321.cc
@@ -0,0 +1,208 @@ 
+// Verify P2321R2 "zip" enhancements to std::pair.
+// { dg-options "-std=gnu++23" }
+// { dg-do run { target c++23 } }
+
+#include <utility>
+#include <testsuite_hooks.h>
+
+using std::pair;
+
+struct A { };
+
+constexpr bool
+test01()
+{
+  struct B { bool v; constexpr B(A&) : v(true) { } };
+
+  // template<class U1, class U2>
+  //   constexpr explicit(false) pair(pair<U1, U2>&);
+
+  pair<A, int> p2a0;
+  pair<B, int> p2b0 = p2a0;
+  VERIFY( std::get<0>(p2b0).v );
+
+  pair<int, A> p2a1;
+  pair<int, B> p2b1 = p2a1;
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+constexpr bool
+test02()
+{
+  struct B { bool v; explicit constexpr B(A&) : v(true) { } };
+
+  // template<class U1, class U2>
+  //   constexpr explicit(true) pair(pair<U1, U2>&);
+
+  static_assert(!std::is_convertible_v<pair<A, int>&, pair<B, int>>);
+  static_assert(!std::is_convertible_v<pair<int, A>&, pair<int, B>>);
+
+  pair<A, int> p2a0;
+  pair<B, int> p2b0(p2a0);
+  VERIFY( std::get<0>(p2b0).v );
+
+  pair<int, A> p2a1;
+  pair<int, B> p2b1(p2a1);
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+constexpr bool
+test03()
+{
+  struct B { bool v; constexpr B(const A&&) : v(true) { } };
+
+  // template<class U1, class U2>
+  //   constexpr explicit(false) pair(const pair<U1, U2>&&);
+
+  const pair<A, int> p2a0;
+  pair<B, int> p2b0 = std::move(p2a0);
+  VERIFY( std::get<0>(p2b0).v );
+
+  const pair<int, A> p2a1;
+  pair<int, B> p2b1 = std::move(p2a1);
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+constexpr bool
+test04()
+{
+  struct B { bool v; explicit constexpr B(const A&&) : v(true) { } };
+
+  // template<class U1, class U2>
+  //   constexpr explicit(true) pair(const pair<U1, U2>&&);
+
+  static_assert(!std::is_convertible_v<const pair<A, int>&&, pair<B, int>>);
+  static_assert(!std::is_convertible_v<const pair<int, A>&&, pair<int, B>>);
+
+  const pair<A, int> p2a0;
+  pair<B, int> p2b0(std::move(p2a0));
+  VERIFY( std::get<0>(p2b0).v );
+
+  const pair<int, A> p2a1;
+  pair<int, B> p2b1(std::move(p2a1));
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+constexpr bool
+test05()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(const A&) const { v = true; return *this; }
+  };
+
+  // template<class U1, class U2>
+  //   constexpr const pair& operator=(const pair<U1, U2>&) const;
+
+  const pair<A, A> p2a;
+  const pair<B, B> p2b;
+  p2b = p2a;
+
+  return true;
+}
+
+constexpr bool
+test06()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(A&&) const { v = true; return *this; }
+  };
+
+  // template<class U1, class U2>
+  //   constexpr const pair& operator=(pair<U1, U2>&&) const;
+
+  pair<A, A> p2a;
+  const pair<B, B> p2b;
+  p2b = std::move(p2a);
+
+  return true;
+}
+
+constexpr bool
+test07()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(const B&) const { v = true; return *this; }
+  };
+
+  // constexpr const pair& operator=(const pair&) const;
+
+  const pair<B, B> t2a;
+  const pair<B, B> t2b;
+  t2b = t2a;
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  return true;
+}
+
+constexpr bool
+test08()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(B&&) const { v = true; return *this; }
+  };
+
+  // constexpr const pair& operator=(pair&&) const;
+
+  pair<B, B> t2a;
+  const pair<B, B> t2b;
+  t2b = std::move(t2a);
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  return true;
+}
+
+struct S
+{
+  mutable int v = 0;
+  friend constexpr void swap(S&& x, S&& y) = delete;
+  friend constexpr void swap(const S& x, const S& y) { ++x.v; ++y.v; }
+};
+
+constexpr bool
+test09()
+{
+  const pair<S, S> t2, u2;
+  std::swap(t2, u2);
+  VERIFY( std::get<0>(t2).v == 1 );
+  VERIFY( std::get<0>(u2).v == 1 );
+  VERIFY( std::get<1>(t2).v == 1 );
+  VERIFY( std::get<1>(u2).v == 1 );
+
+  static_assert(!std::is_swappable_v<const pair<A, int>&>);
+  static_assert(!std::is_swappable_v<const pair<int, A>&>);
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(test02());
+  static_assert(test03());
+  static_assert(test04());
+  // FIXME: G++ doesn't support reading mutable members during constexpr (PR c++/92505).
+  test05();
+  test06();
+  test07();
+  test08();
+  test09();
+}
diff --git a/libstdc++-v3/testsuite/20_util/tuple/p2321.cc b/libstdc++-v3/testsuite/20_util/tuple/p2321.cc
new file mode 100644
index 00000000000..80fc23cf9d4
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/p2321.cc
@@ -0,0 +1,664 @@ 
+// Verify P2321R2 "zip" enhancements to std::tuple.
+// { dg-options "-std=gnu++23" }
+// { dg-do run { target c++23 } }
+
+#include <tuple>
+#include <memory>
+#include <testsuite_hooks.h>
+
+using std::tuple;
+using std::pair;
+using std::allocator;
+using std::allocator_arg_t;
+using std::allocator_arg;
+
+namespace alloc {
+  struct B01;
+  struct B02;
+  struct B03;
+  struct B04;
+}
+
+template<> struct std::uses_allocator<alloc::B01, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B02, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B03, allocator<int>> : std::true_type { };
+template<> struct std::uses_allocator<alloc::B04, allocator<int>> : std::true_type { };
+
+struct A { };
+
+constexpr bool
+test01()
+{
+  struct B { bool v; constexpr B(A&) : v(true) { } };
+
+  // template<class... UTypes>
+  //   constexpr explicit(false) tuple(tuple<UTypes...>&);
+
+  tuple<A> t1a;
+  tuple<B> t1b = t1a;
+  VERIFY( std::get<0>(t1b).v );
+
+  tuple<A, int> t2a0;
+  tuple<B, int> t2b0 = t2a0;
+  VERIFY( std::get<0>(t2b0).v );
+
+  tuple<int, A> t2a1;
+  tuple<int, B> t2b1 = t2a1;
+  VERIFY( std::get<1>(t2b1).v );
+
+  tuple<A, int, int> t3a0;
+  tuple<B, int, int> t3b0 = t3a0;
+  VERIFY( std::get<0>(t3b0).v );
+
+  tuple<int, A, int> t3a1;
+  tuple<int, B, int> t3b1 = t3a1;
+  VERIFY( std::get<1>(t3b1).v );
+
+  tuple<int, int, A> t3a2;
+  tuple<int, int, B> t3b2 = t3a2;
+  VERIFY( std::get<2>(t3b2).v );
+
+  // template<class... UTypes>
+  //   constexpr explicit(false) tuple(pair<UTypes...>&);
+
+  pair<A, int> p2a0;
+  tuple<B, int> p2b0 = p2a0;
+  VERIFY( std::get<0>(p2b0).v );
+
+  pair<int, A> p2a1;
+  tuple<int, B> p2b1 = p2a1;
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B01
+  {
+    bool v;
+    B01(A&);
+    constexpr B01(allocator_arg_t, allocator<int>, A&) : v(true) { }
+  };
+
+  constexpr bool
+  test01()
+  {
+    using B = B01;
+
+    // template<class Alloc, class... UTypes>
+    //   constexpr explicit(false)
+    //     tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&);
+
+    tuple<A> t1a;
+    tuple<B> t1b = {allocator_arg, allocator<int>{}, t1a};
+    VERIFY( std::get<0>(t1b).v );
+
+    tuple<A, int> t2a0;
+    tuple<B, int> t2b0 = {allocator_arg, allocator<int>{}, t2a0};
+    VERIFY( std::get<0>(t2b0).v );
+
+    tuple<int, A> t2a1;
+    tuple<int, B> t2b1 = {allocator_arg, allocator<int>{}, t2a1};
+    VERIFY( std::get<1>(t2b1).v );
+
+    tuple<A, int, int> t3a0;
+    tuple<B, int, int> t3b0 = {allocator_arg, allocator<int>{}, t3a0};
+    VERIFY( std::get<0>(t3b0).v );
+
+    tuple<int, A, int> t3a1;
+    tuple<int, B, int> t3b1 = {allocator_arg, allocator<int>{}, t3a1};
+    VERIFY( std::get<1>(t3b1).v );
+
+    tuple<int, int, A> t3a2;
+    tuple<int, int, B> t3b2 = {allocator_arg, allocator<int>{}, t3a2};
+    VERIFY( std::get<2>(t3b2).v );
+
+    // template<class Alloc, class U1, class U2>
+    //   constexpr explicit(false)
+    //     tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&);
+
+    pair<A, int> p2a0;
+    tuple<B, int> p2b0 = {allocator_arg, allocator<int>{}, p2a0};
+    VERIFY( std::get<0>(p2b0).v );
+
+    pair<int, A> p2a1;
+    tuple<int, B> p2b1 = {allocator_arg, allocator<int>{}, p2a1};
+    VERIFY( std::get<1>(p2b1).v );
+
+    return true;
+  }
+}
+
+constexpr bool
+test02()
+{
+  struct B { bool v; explicit constexpr B(A&) : v(true) { } };
+
+  // template<class... UTypes>
+  //   constexpr explicit(true) tuple(tuple<UTypes...>&);
+
+  static_assert(!std::is_convertible_v<tuple<A>&, tuple<B>>);
+
+  tuple<A> t1a;
+  tuple<B> t1b(t1a);
+  VERIFY( std::get<0>(t1b).v );
+
+  static_assert(!std::is_convertible_v<tuple<A, int>&, tuple<B, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, A>&, tuple<int, B>>);
+
+  tuple<A, int> t2a0;
+  tuple<B, int> t2b0(t2a0);
+  VERIFY( std::get<0>(t2b0).v );
+
+  tuple<int, A> t2a1;
+  tuple<int, B> t2b1(t2a1);
+  VERIFY( std::get<1>(t2b1).v );
+
+  static_assert(!std::is_convertible_v<tuple<A, int, int>&, tuple<B, int, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, A, int>&, tuple<int, B, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, int, A>&, tuple<int, int, B>>);
+
+  tuple<A, int, int> t3a0;
+  tuple<B, int, int> t3b0(t3a0);
+  VERIFY( std::get<0>(t3b0).v );
+
+  tuple<int, A, int> t3a1;
+  tuple<int, B, int> t3b1(t3a1);
+  VERIFY( std::get<1>(t3b1).v );
+
+  tuple<int, int, A> t3a2;
+  tuple<int, int, B> t3b2(t3a2);
+  VERIFY( std::get<2>(t3b2).v );
+
+  // template<class... UTypes>
+  //   constexpr explicit(true) tuple(pair<UTypes...>&);
+
+  static_assert(!std::is_convertible_v<pair<A, int>&, tuple<B, int>>);
+  static_assert(!std::is_convertible_v<pair<int, A>&, tuple<int, B>>);
+
+  pair<A, int> p2a0;
+  tuple<B, int> p2b0(p2a0);
+  VERIFY( std::get<0>(p2b0).v );
+
+  pair<int, A> p2a1;
+  tuple<int, B> p2b1(p2a1);
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B02
+  {
+    bool v;
+    explicit B02(A&);
+    explicit constexpr B02(allocator_arg_t, allocator<int>, A&) : v(true) { }
+  };
+
+  constexpr bool
+  test02()
+  {
+    using B = B02;
+
+    // template<class Alloc, class... UTypes>
+    //   constexpr explicit(true)
+    //     tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&);
+
+    tuple<A> t1a;
+    tuple<B> t1b(allocator_arg, allocator<int>{}, t1a);
+    VERIFY( std::get<0>(t1b).v );
+
+    tuple<A, int> t2a0;
+    tuple<B, int> t2b0(allocator_arg, allocator<int>{}, t2a0);
+    VERIFY( std::get<0>(t2b0).v );
+
+    tuple<int, A> t2a1;
+    tuple<int, B> t2b1(allocator_arg, allocator<int>{}, t2a1);
+    VERIFY( std::get<1>(t2b1).v );
+
+    tuple<A, int, int> t3a0;
+    tuple<B, int, int> t3b0(allocator_arg, allocator<int>{}, t3a0);
+    VERIFY( std::get<0>(t3b0).v );
+
+    tuple<int, A, int> t3a1;
+    tuple<int, B, int> t3b1(allocator_arg, allocator<int>{}, t3a1);
+    VERIFY( std::get<1>(t3b1).v );
+
+    tuple<int, int, A> t3a2;
+    tuple<int, int, B> t3b2(allocator_arg, allocator<int>{}, t3a2);
+    VERIFY( std::get<2>(t3b2).v );
+
+    // template<class Alloc, class U1, class U2>
+    //   constexpr explicit(true)
+    //     tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&);
+
+    pair<A, int> p2a0;
+    tuple<B, int> p2b0(allocator_arg, allocator<int>{}, p2a0);
+    VERIFY( std::get<0>(p2b0).v );
+
+    pair<int, A> p2a1;
+    tuple<int, B> p2b1(allocator_arg, allocator<int>{}, p2a1);
+    VERIFY( std::get<1>(p2b1).v );
+
+    return true;
+  }
+} // namespace alloc
+
+constexpr bool
+test03()
+{
+  struct B { bool v; constexpr B(const A&&) : v(true) { } };
+
+  // template<class... UTypes>
+  //   constexpr explicit(false) tuple(const tuple<UTypes...>&&);
+
+  const tuple<A> t1a;
+  tuple<B> t1b = std::move(t1a);
+  VERIFY( std::get<0>(t1b).v );
+
+  const tuple<A, int> t2a0;
+  tuple<B, int> t2b0 = std::move(t2a0);
+  VERIFY( std::get<0>(t2b0).v );
+
+  const tuple<int, A> t2a1;
+  tuple<int, B> t2b1 = std::move(t2a1);
+  VERIFY( std::get<1>(t2b1).v );
+
+  const tuple<A, int, int> t3a0;
+  tuple<B, int, int> t3b0 = std::move(t3a0);
+  VERIFY( std::get<0>(t3b0).v );
+
+  const tuple<int, A, int> t3a1;
+  tuple<int, B, int> t3b1 = std::move(t3a1);
+  VERIFY( std::get<1>(t3b1).v );
+
+  const tuple<int, int, A> t3a2;
+  tuple<int, int, B> t3b2 = std::move(t3a2);
+  VERIFY( std::get<2>(t3b2).v );
+
+  // template<class... UTypes>
+  //   constexpr explicit(false) tuple(const pair<UTypes...>&&);
+
+  const pair<A, int> p2a0;
+  tuple<B, int> p2b0 = std::move(p2a0);
+  VERIFY( std::get<0>(p2b0).v );
+
+  const pair<int, A> p2a1;
+  tuple<int, B> p2b1 = std::move(p2a1);
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B03
+  {
+    bool v;
+    B03(const A&&);
+    constexpr B03(allocator_arg_t, allocator<int>, const A&&) : v(true) { }
+  };
+
+  constexpr bool
+  test03()
+  {
+    using B = B03;
+
+    // template<class Alloc, class... UTypes>
+    //   constexpr explicit(false)
+    //     tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&);
+
+    const tuple<A> t1a;
+    tuple<B> t1b = {allocator_arg, allocator<int>{}, std::move(t1a)};
+    VERIFY( std::get<0>(t1b).v );
+
+    const tuple<A, int> t2a0;
+    tuple<B, int> t2b0 = {allocator_arg, allocator<int>{}, std::move(t2a0)};
+    VERIFY( std::get<0>(t2b0).v );
+
+    const tuple<int, A> t2a1;
+    tuple<int, B> t2b1 = {allocator_arg, allocator<int>{}, std::move(t2a1)};
+    VERIFY( std::get<1>(t2b1).v );
+
+    const tuple<A, int, int> t3a0;
+    tuple<B, int, int> t3b0 = {allocator_arg, allocator<int>{}, std::move(t3a0)};
+    VERIFY( std::get<0>(t3b0).v );
+
+    const tuple<int, A, int> t3a1;
+    tuple<int, B, int> t3b1 = {allocator_arg, allocator<int>{}, std::move(t3a1)};
+    VERIFY( std::get<1>(t3b1).v );
+
+    const tuple<int, int, A> t3a2;
+    tuple<int, int, B> t3b2 = {allocator_arg, allocator<int>{}, std::move(t3a2)};
+    VERIFY( std::get<2>(t3b2).v );
+
+    // template<class Alloc, class U1, class U2>
+    //   constexpr explicit(false)
+    //     tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);
+
+    const pair<A, int> p2a0;
+    tuple<B, int> p2b0 = {allocator_arg, allocator<int>{}, std::move(p2a0)};
+    VERIFY( std::get<0>(p2b0).v );
+
+    const pair<int, A> p2a1;
+    tuple<int, B> p2b1 = {allocator_arg, allocator<int>{}, std::move(p2a1)};
+    VERIFY( std::get<1>(p2b1).v );
+
+    return true;
+  }
+};
+
+constexpr bool
+test04()
+{
+  struct B { bool v; explicit constexpr B(const A&&) : v(true) { } };
+
+  // template<class... UTypes>
+  //   constexpr explicit(true) tuple(const tuple<UTypes...>&&);
+
+  static_assert(!std::is_convertible_v<tuple<A>&, tuple<B>>);
+
+  const tuple<A> t1a;
+  tuple<B> t1b(std::move(t1a));
+  VERIFY( std::get<0>(t1b).v );
+
+  static_assert(!std::is_convertible_v<tuple<A, int>&, tuple<B, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, A>&, tuple<int, B>>);
+
+  const tuple<A, int> t2a0;
+  tuple<B, int> t2b0(std::move(t2a0));
+  VERIFY( std::get<0>(t2b0).v );
+
+  const tuple<int, A> t2a1;
+  tuple<int, B> t2b1(std::move(t2a1));
+  VERIFY( std::get<1>(t2b1).v );
+
+  static_assert(!std::is_convertible_v<tuple<A, int, int>&, tuple<B, int, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, A, int>&, tuple<int, B, int>>);
+  static_assert(!std::is_convertible_v<tuple<int, int, A>&, tuple<int, int, B>>);
+
+  const tuple<A, int, int> t3a0;
+  tuple<B, int, int> t3b0(std::move(t3a0));
+  VERIFY( std::get<0>(t3b0).v );
+
+  const tuple<int, A, int> t3a1;
+  tuple<int, B, int> t3b1(std::move(t3a1));
+  VERIFY( std::get<1>(t3b1).v );
+
+  const tuple<int, int, A> t3a2;
+  tuple<int, int, B> t3b2(std::move(t3a2));
+  VERIFY( std::get<2>(t3b2).v );
+
+  // template<class... UTypes>
+  //   constexpr explicit(true) tuple(const pair<UTypes...>&&);
+
+  static_assert(!std::is_convertible_v<pair<A, int>&, tuple<B, int>>);
+  static_assert(!std::is_convertible_v<pair<int, A>&, tuple<int, B>>);
+
+  const pair<A, int> p2a0;
+  tuple<B, int> p2b0(std::move(p2a0));
+  VERIFY( std::get<0>(p2b0).v );
+
+  const pair<int, A> p2a1;
+  tuple<int, B> p2b1(std::move(p2a1));
+  VERIFY( std::get<1>(p2b1).v );
+
+  return true;
+}
+
+namespace alloc
+{
+  struct B04
+  {
+    bool v;
+    explicit B04(const A&&);
+    explicit constexpr B04(allocator_arg_t, allocator<int>, const A&&) : v(true) { }
+  };
+
+  constexpr bool
+  test04()
+  {
+    using B = B04;
+
+    // template<class Alloc, class... UTypes>
+    //   constexpr explicit(true)
+    //     tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&);
+
+    const tuple<A> t1a;
+    tuple<B> t1b(allocator_arg, allocator<int>{}, std::move(t1a));
+    VERIFY( std::get<0>(t1b).v );
+
+    const tuple<A, int> t2a0;
+    tuple<B, int> t2b0(allocator_arg, allocator<int>{}, std::move(t2a0));
+    VERIFY( std::get<0>(t2b0).v );
+
+    const tuple<int, A> t2a1;
+    tuple<int, B> t2b1(allocator_arg, allocator<int>{}, std::move(t2a1));
+    VERIFY( std::get<1>(t2b1).v );
+
+    const tuple<A, int, int> t3a0;
+    tuple<B, int, int> t3b0(allocator_arg, allocator<int>{}, std::move(t3a0));
+    VERIFY( std::get<0>(t3b0).v );
+
+    const tuple<int, A, int> t3a1;
+    tuple<int, B, int> t3b1(allocator_arg, allocator<int>{}, std::move(t3a1));
+    VERIFY( std::get<1>(t3b1).v );
+
+    const tuple<int, int, A> t3a2;
+    tuple<int, int, B> t3b2(allocator_arg, allocator<int>{}, std::move(t3a2));
+    VERIFY( std::get<2>(t3b2).v );
+
+    // template<class Alloc, class U1, class U2>
+    //   constexpr explicit(true)
+    //     tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);
+
+    tuple<B, int> p2b0(allocator_arg, allocator<int>{}, std::move(t2a0));
+    VERIFY( std::get<0>(p2b0).v );
+
+    tuple<int, B> p2b1(allocator_arg, allocator<int>{}, std::move(t2a1));
+    VERIFY( std::get<1>(p2b1).v );
+
+    return true;
+  }
+};
+
+constexpr bool
+test05()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(const A&) const { v = true; return *this; }
+  };
+
+  // template<class... UTypes>
+  //   constexpr const tuple& operator=(const tuple<UTypes...>&) const;
+
+  const tuple<A> t1a;
+  const tuple<B> t1b;
+  t1b = t1a;
+  VERIFY( std::get<0>(t1b).v );
+
+  const tuple<A, A> t2a;
+  const tuple<B, B> t2b;
+  t2b = t2a;
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  const tuple<A, A, A> t3a;
+  const tuple<B, B, B> t3b;
+  t3b = t3a;
+  VERIFY( std::get<0>(t3b).v );
+  VERIFY( std::get<1>(t3b).v );
+  VERIFY( std::get<2>(t3b).v );
+
+  // template<class U1, class U2>
+  //   constexpr const tuple& operator=(const pair<U1, U2>&) const;
+
+  const pair<A, A> p2a;
+  const tuple<B, B> p2b;
+  p2b = p2a;
+
+  return true;
+}
+
+constexpr bool
+test06()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(A&&) const { v = true; return *this; }
+  };
+
+  // template<class... UTypes>
+  //   constexpr const tuple& operator=(tuple<UTypes...>&&) const;
+
+  tuple<A> t1a;
+  const tuple<B> t1b;
+  t1b = std::move(t1a);
+  VERIFY( std::get<0>(t1b).v );
+
+  tuple<A, A> t2a;
+  const tuple<B, B> t2b;
+  t2b = std::move(t2a);
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  tuple<A, A, A> t3a;
+  const tuple<B, B, B> t3b;
+  t3b = std::move(t3a);
+  VERIFY( std::get<0>(t3b).v );
+  VERIFY( std::get<1>(t3b).v );
+  VERIFY( std::get<2>(t3b).v );
+
+  // template<class U1, class U2>
+  //   constexpr const tuple& operator=(pair<U1, U2>&&) const;
+
+  pair<A, A> p2a;
+  const tuple<B, B> p2b;
+  p2b = std::move(p2a);
+
+  return true;
+}
+
+constexpr bool
+test07()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(const B&) const { v = true; return *this; }
+  };
+
+  // constexpr const tuple& operator=(const tuple&) const;
+
+  const tuple<B> t1a;
+  const tuple<B> t1b;
+  t1b = t1a;
+  VERIFY( std::get<0>(t1b).v );
+
+  const tuple<B, B> t2a;
+  const tuple<B, B> t2b;
+  t2b = t2a;
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  const tuple<B, B, B> t3a;
+  const tuple<B, B, B> t3b;
+  t3b = t3a;
+  VERIFY( std::get<0>(t3b).v );
+  VERIFY( std::get<1>(t3b).v );
+  VERIFY( std::get<2>(t3b).v );
+
+  return true;
+}
+
+constexpr bool
+test08()
+{
+  struct B
+  {
+    mutable bool v;
+    constexpr const B& operator=(B&&) const { v = true; return *this; }
+  };
+
+  // constexpr const tuple& operator=(tuple&&) const;
+
+  tuple<B> t1a;
+  const tuple<B> t1b;
+  t1b = std::move(t1a);
+  VERIFY( std::get<0>(t1b).v );
+
+  tuple<B, B> t2a;
+  const tuple<B, B> t2b;
+  t2b = std::move(t2a);
+  VERIFY( std::get<0>(t2b).v );
+  VERIFY( std::get<1>(t2b).v );
+
+  tuple<B, B, B> t3a;
+  const tuple<B, B, B> t3b;
+  t3b = std::move(t3a);
+  VERIFY( std::get<0>(t3b).v );
+  VERIFY( std::get<1>(t3b).v );
+  VERIFY( std::get<2>(t3b).v );
+
+  return true;
+}
+
+struct S
+{
+  mutable int v = 0;
+  friend constexpr void swap(S&& x, S&& y) = delete;
+  friend constexpr void swap(const S& x, const S& y) { ++x.v; ++y.v; }
+};
+
+constexpr bool
+test09()
+{
+  const tuple<S> t1, u1;
+  std::swap(t1, u1);
+  VERIFY( std::get<0>(t1).v == 1 );
+  VERIFY( std::get<0>(u1).v == 1 );
+
+  const tuple<S, S> t2, u2;
+  std::swap(t2, u2);
+  VERIFY( std::get<0>(t2).v == 1 );
+  VERIFY( std::get<0>(u2).v == 1 );
+  VERIFY( std::get<1>(t2).v == 1 );
+  VERIFY( std::get<1>(u2).v == 1 );
+
+  const tuple<S, S, S> t3, u3;
+  std::swap(t3, u3);
+  VERIFY( std::get<0>(t3).v == 1 );
+  VERIFY( std::get<0>(u3).v == 1 );
+  VERIFY( std::get<1>(t3).v == 1 );
+  VERIFY( std::get<1>(u3).v == 1 );
+  VERIFY( std::get<2>(t3).v == 1 );
+  VERIFY( std::get<2>(u3).v == 1 );
+
+  static_assert(!std::is_swappable_v<const tuple<A>&>);
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(alloc::test01());
+  static_assert(test02());
+  static_assert(alloc::test02());
+  static_assert(test03());
+  static_assert(alloc::test03());
+  static_assert(test04());
+  static_assert(alloc::test04());
+  // FIXME: G++ doesn't support reading mutable members during constexpr (PR c++/92505).
+  test05();
+  test06();
+  test07();
+  test08();
+  test09();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/element_access/1.cc b/libstdc++-v3/testsuite/23_containers/vector/bool/element_access/1.cc
new file mode 100644
index 00000000000..9016c026b33
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/vector/bool/element_access/1.cc
@@ -0,0 +1,26 @@ 
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+// { dg-xfail-if "not supported" { debug_mode } }
+
+#include <vector>
+#include <testsuite_hooks.h>
+
+constexpr bool
+test01()
+{
+  // P2321R2
+  // constexpr const reference& vector<bool>::operator=(bool x) const noexcept;
+
+  std::vector<bool> v(1);
+  const auto e = v[0];
+  e = true;
+  VERIFY( v[0] );
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+}