public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple
@ 2022-08-23  1:34 Patrick Palka
  2022-08-23  1:34 ` [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2 Patrick Palka
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Patrick Palka @ 2022-08-23  1:34 UTC (permalink / raw)
  To: gcc-patches; +Cc: libstdc++, Patrick Palka

P2321R2 adds new conditionally explicit constructors to std::tuple which
we'll concisely implement in a subsequent patch using explicit(bool), like
in our C++20 std::pair implementation.  But before we can do that, this
patch first adds members to _TupleConstraints that test for constructibility
and convertibility separately; we'll use the first in the new constructors'
constraints, and the second in their explicit specifier.

In passing, this patch also redefines the existing predicates
__is_ex/implicitly_constructible in terms of these new members.  This
seems to reduce compile time and memory usage by about 10% for large
tuples when using the relevant constructors constrained by
_Explicit/_ImplicitCtor (since we no longer have to redundantly expand
and process is_constructible<_Types, _UTypes>... twice for each pair of
such constructors).  In order to retain maximal short circuiting, do
this only when constexpr if is available.

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

libstdc++-v3/ChangeLog:

	* include/std/tuple (_TupleConstraints::__convertible): Define
	for C++17.
	(_TupleConstraints::__constructible): Likewise.
	(_TupleConstraints::__is_explicitly_constructible): For C++17
	define this in terms of __convertible and __constructible
	using constexpr if.
	(_TupleConstraints::__is_implicitly_constructible): Likewise.
---
 libstdc++-v3/include/std/tuple | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 6d0060a191c..d0c168fd7e2 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -553,15 +553,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<bool, typename... _Types>
     struct _TupleConstraints
     {
+#if __cplusplus >= 201703L
+      template<typename... _UTypes>
+	static constexpr bool __constructible
+	  = __and_v<is_constructible<_Types, _UTypes>...>;
+
+      template<typename... _UTypes>
+	static constexpr bool __convertible
+	  = __and_v<is_convertible<_UTypes, _Types>...>;
+#endif
+
       // Constraint for a non-explicit constructor.
       // True iff each Ti in _Types... can be constructed from Ui in _UTypes...
       // and every Ui is implicitly convertible to Ti.
       template<typename... _UTypes>
 	static constexpr bool __is_implicitly_constructible()
 	{
+#if __cplusplus >= 201703L
+	  if constexpr (__constructible<_UTypes...>)
+	    return __convertible<_UTypes...>;
+	  return false;
+#else
 	  return __and_<is_constructible<_Types, _UTypes>...,
 			is_convertible<_UTypes, _Types>...
 			>::value;
+#endif
 	}
 
       // Constraint for a non-explicit constructor.
@@ -570,9 +586,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename... _UTypes>
 	static constexpr bool __is_explicitly_constructible()
 	{
+#if __cplusplus >= 201703L
+	  if constexpr (__constructible<_UTypes...>)
+	    return !__convertible<_UTypes...>;
+	  return false;
+#else
 	  return __and_<is_constructible<_Types, _UTypes>...,
 			__not_<__and_<is_convertible<_UTypes, _Types>...>>
 			>::value;
+#endif
 	}
 
       static constexpr bool __is_implicitly_default_constructible()
-- 
2.37.2.382.g795ea8776b


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

* [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2
  2022-08-23  1:34 [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Patrick Palka
@ 2022-08-23  1:34 ` Patrick Palka
  2022-08-23 12:03   ` Jonathan Wakely
  2022-08-23  1:35 ` [PATCH 3/3] libstdc++: Implement ranges::zip_view " Patrick Palka
  2022-08-23  9:15 ` [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Jonathan Wakely
  2 siblings, 1 reply; 11+ messages in thread
From: Patrick Palka @ 2022-08-23  1:34 UTC (permalink / raw)
  To: gcc-patches; +Cc: libstdc++, Patrick Palka

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

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());
+}
-- 
2.37.2.382.g795ea8776b


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

* [PATCH 3/3] libstdc++: Implement ranges::zip_view from P2321R2
  2022-08-23  1:34 [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Patrick Palka
  2022-08-23  1:34 ` [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2 Patrick Palka
@ 2022-08-23  1:35 ` Patrick Palka
  2022-08-24 12:15   ` Jonathan Wakely
  2022-08-26 20:05   ` Jonathan Wakely
  2022-08-23  9:15 ` [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Jonathan Wakely
  2 siblings, 2 replies; 11+ messages in thread
From: Patrick Palka @ 2022-08-23  1:35 UTC (permalink / raw)
  To: gcc-patches; +Cc: libstdc++, Patrick Palka

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

libstdc++-v3/ChangeLog:

	* include/bits/ranges_algo.h (__min_fn, min): Move to ...
	* include/bits/ranges_util.h: ... here in order to avoid
	including all of ranges_algo.h from <ranges>.
	* include/std/ranges (__detail::__zip_is_common): Define for
	C++23 as per P2321R2.
	(__detail::__tuple_or_pair): Likewise.
	(__detail::__tuple_or_pair_t): Likewise.
	(__detail::__tuple_transform): Likewise.
	(__detail::__tuple_for_each): Likewise.
	(zip_view): Likewise.
	(enable_borrowed_range<zip_view>): Likewise.
	(__detail::__all_random_access): Likewise.
	(__detail::__all_bidirectional): Likewise.
	(__detail::__all_forward): Likewise.
	(__detail::__zip_view_iter_cat): Likewise.
	(zip_view::iterator): Likewise.
	(zip_view::sentinel): Likewise.
	(testsuite/std/ranges/zip/1.cc): New test.
---
 libstdc++-v3/include/bits/ranges_algo.h    |  54 +--
 libstdc++-v3/include/bits/ranges_util.h    |  55 +++
 libstdc++-v3/include/std/ranges            | 445 +++++++++++++++++++++
 libstdc++-v3/testsuite/std/ranges/zip/1.cc | 101 +++++
 4 files changed, 602 insertions(+), 53 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/zip/1.cc

diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h
index 3d30fb1428c..2a116361a67 100644
--- a/libstdc++-v3/include/bits/ranges_algo.h
+++ b/libstdc++-v3/include/bits/ranges_algo.h
@@ -2902,59 +2902,7 @@ namespace ranges
 
   inline constexpr __set_symmetric_difference_fn set_symmetric_difference{};
 
-  struct __min_fn
-  {
-    template<typename _Tp, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
-	       _Comp = ranges::less>
-      constexpr const _Tp&
-      operator()(const _Tp& __a, const _Tp& __b,
-		 _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	if (std::__invoke(__comp,
-			  std::__invoke(__proj, __b),
-			  std::__invoke(__proj, __a)))
-	  return __b;
-	else
-	  return __a;
-      }
-
-    template<input_range _Range, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<iterator_t<_Range>, _Proj>>
-	       _Comp = ranges::less>
-      requires indirectly_copyable_storable<iterator_t<_Range>,
-					    range_value_t<_Range>*>
-      constexpr range_value_t<_Range>
-      operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	auto __first = ranges::begin(__r);
-	auto __last = ranges::end(__r);
-	__glibcxx_assert(__first != __last);
-	auto __result = *__first;
-	while (++__first != __last)
-	  {
-	    auto __tmp = *__first;
-	    if (std::__invoke(__comp,
-			      std::__invoke(__proj, __tmp),
-			      std::__invoke(__proj, __result)))
-	      __result = std::move(__tmp);
-	  }
-	return __result;
-      }
-
-    template<copyable _Tp, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
-	       _Comp = ranges::less>
-      constexpr _Tp
-      operator()(initializer_list<_Tp> __r,
-		 _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	return (*this)(ranges::subrange(__r),
-		       std::move(__comp), std::move(__proj));
-      }
-  };
-
-  inline constexpr __min_fn min{};
+  // min is defined in <bits/ranges_util.h>.
 
   struct __max_fn
   {
diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
index 37d7bc862f9..bb56deee01b 100644
--- a/libstdc++-v3/include/bits/ranges_util.h
+++ b/libstdc++-v3/include/bits/ranges_util.h
@@ -649,6 +649,61 @@ namespace ranges
   };
 
   inline constexpr __search_fn search{};
+
+  struct __min_fn
+  {
+    template<typename _Tp, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
+	       _Comp = ranges::less>
+      constexpr const _Tp&
+      operator()(const _Tp& __a, const _Tp& __b,
+		 _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	if (std::__invoke(__comp,
+			  std::__invoke(__proj, __b),
+			  std::__invoke(__proj, __a)))
+	  return __b;
+	else
+	  return __a;
+      }
+
+    template<input_range _Range, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<iterator_t<_Range>, _Proj>>
+	       _Comp = ranges::less>
+      requires indirectly_copyable_storable<iterator_t<_Range>,
+					    range_value_t<_Range>*>
+      constexpr range_value_t<_Range>
+      operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	auto __first = ranges::begin(__r);
+	auto __last = ranges::end(__r);
+	__glibcxx_assert(__first != __last);
+	auto __result = *__first;
+	while (++__first != __last)
+	  {
+	    auto __tmp = *__first;
+	    if (std::__invoke(__comp,
+			      std::__invoke(__proj, __tmp),
+			      std::__invoke(__proj, __result)))
+	      __result = std::move(__tmp);
+	  }
+	return __result;
+      }
+
+    template<copyable _Tp, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
+	       _Comp = ranges::less>
+      constexpr _Tp
+      operator()(initializer_list<_Tp> __r,
+		 _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	return (*this)(ranges::subrange(__r),
+		       std::move(__comp), std::move(__proj));
+      }
+  };
+
+  inline constexpr __min_fn min{};
+
 } // namespace ranges
 
   using ranges::get;
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 3e71ecb32b7..8d0c2a52b40 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -39,6 +39,7 @@
 #if __cpp_lib_concepts
 
 #include <compare>
+#include <cstdint>
 #include <initializer_list>
 #include <iterator>
 #include <optional>
@@ -4352,6 +4353,450 @@ namespace views::__adaptor
     inline constexpr auto values = elements<1>;
   } // namespace views
 
+#if __cplusplus > 202002L
+  namespace __detail
+  {
+    template<typename... _Rs>
+      concept __zip_is_common = (sizeof...(_Rs) == 1 && (common_range<_Rs> && ...))
+	|| (!(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>...>
+	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
+	}, std::forward<_Tuple>(__tuple));
+      }
+
+    template<typename _Fp, typename _Tuple>
+      constexpr void
+      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
+      {
+	std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
+	  (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
+	}, std::forward<_Tuple>(__tuple));
+      }
+  } // namespace __detail
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  class zip_view : public view_interface<zip_view<_Vs...>>
+  {
+    tuple<_Vs...> _M_views;
+
+    template<bool> class iterator;
+    template<bool> class sentinel;
+
+  public:
+    zip_view() = default;
+
+    constexpr explicit
+    zip_view(_Vs... views)
+      : _M_views(std::move(views)...)
+    { }
+
+    constexpr auto
+    begin() requires (!(__detail::__simple_view<_Vs> && ...))
+    { return iterator<false>(__detail::__tuple_transform(ranges::begin, _M_views)); }
+
+    constexpr auto
+    begin() const requires (range<const _Vs> && ...)
+    { return iterator<true>(__detail::__tuple_transform(ranges::begin, _M_views)); }
+
+    constexpr auto
+    end() requires (!(__detail::__simple_view<_Vs> && ...))
+    {
+      if constexpr (!__detail::__zip_is_common<_Vs...>)
+        return sentinel<false>(__detail::__tuple_transform(ranges::end, _M_views));
+      else if constexpr ((random_access_range<_Vs> && ...))
+        return begin() + iter_difference_t<iterator<false>>(size());
+      else
+        return iterator<false>(__detail::__tuple_transform(ranges::end, _M_views));
+    }
+
+    constexpr auto
+    end() const requires (range<const _Vs> && ...)
+    {
+      if constexpr (!__detail::__zip_is_common<const _Vs...>)
+        return sentinel<true>(__detail::__tuple_transform(ranges::end, _M_views));
+      else if constexpr ((random_access_range<const _Vs> && ...))
+        return begin() + iter_difference_t<iterator<true>>(size());
+      else
+        return iterator<true>(__detail::__tuple_transform(ranges::end, _M_views));
+    }
+
+    constexpr auto
+    size() requires (sized_range<_Vs> && ...)
+    {
+      return std::apply([](auto... sizes) {
+	using _CT = __detail::__make_unsigned_like_t<common_type_t<decltype(sizes)...>>;
+	return ranges::min({_CT(sizes)...});
+      }, __detail::__tuple_transform(ranges::size, _M_views));
+    }
+
+    constexpr auto
+    size() const requires (sized_range<const _Vs> && ...)
+    {
+      return std::apply([](auto... sizes) {
+	using _CT = __detail::__make_unsigned_like_t<common_type_t<decltype(sizes)...>>;
+	return ranges::min({_CT(sizes)...});
+      }, __detail::__tuple_transform(ranges::size, _M_views));
+    }
+  };
+
+  template<typename... _Rs>
+    zip_view(_Rs&&...) -> zip_view<views::all_t<_Rs>...>;
+
+  template<typename... _Views>
+    inline constexpr bool enable_borrowed_range<zip_view<_Views...>>
+      = (enable_borrowed_range<_Views> && ...);
+
+  namespace __detail
+  {
+    template<bool _Const, typename... _Vs>
+      concept __all_random_access
+	= (random_access_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Vs>
+      concept __all_bidirectional
+	= (bidirectional_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Vs>
+      concept __all_forward
+	= (forward_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Views>
+      struct __zip_view_iter_cat
+      { };
+
+    template<bool _Const, typename... _Views>
+      requires __all_forward<_Const, _Views...>
+      struct __zip_view_iter_cat<_Const, _Views...>
+      { using iterator_category = input_iterator_tag; };
+  } // namespace __detail
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  template<bool _Const>
+  class zip_view<_Vs...>::iterator
+    : public __detail::__zip_view_iter_cat<_Const, _Vs...>
+  {
+    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
+
+    constexpr explicit
+    iterator(decltype(_M_current) __current)
+      : _M_current(std::move(__current))
+    { }
+
+    static auto
+    _S_iter_concept()
+    {
+      if constexpr (__detail::__all_random_access<_Const, _Vs...>)
+	return random_access_iterator_tag{};
+      else if constexpr (__detail::__all_bidirectional<_Const, _Vs...>)
+	return bidirectional_iterator_tag{};
+      else if constexpr (__detail::__all_forward<_Const, _Vs...>)
+	return forward_iterator_tag{};
+      else
+	return input_iterator_tag{};
+    }
+
+  public:
+    // 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>>...>;
+    using difference_type
+      = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+
+    iterator() = default;
+
+    constexpr
+    iterator(iterator<!_Const> __i)
+      requires _Const
+	&& (convertible_to<iterator_t<_Vs>,
+			   iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+      : _M_current(std::move(__i._M_current))
+    { }
+
+    constexpr auto
+    operator*() const
+    {
+      auto __f = [](auto& __i) -> decltype(auto) {
+	return *__i;
+      };
+      return __detail::__tuple_transform(__f, _M_current);
+    }
+
+    constexpr iterator&
+    operator++()
+    {
+      __detail::__tuple_for_each([](auto& __i) { ++__i; }, _M_current);
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    constexpr iterator
+    operator++(int)
+      requires __detail::__all_forward<_Const, _Vs...>
+    {
+      auto __tmp = *this;
+      ++*this;
+      return __tmp;
+    }
+
+    constexpr iterator&
+    operator--()
+      requires __detail::__all_bidirectional<_Const, _Vs...>
+    {
+      __detail::__tuple_for_each([](auto& __i) { --__i; }, _M_current);
+      return *this;
+    }
+
+    constexpr iterator
+    operator--(int)
+      requires __detail::__all_bidirectional<_Const, _Vs...>
+    {
+      auto __tmp = *this;
+      --*this;
+      return __tmp;
+    }
+
+    constexpr iterator&
+    operator+=(difference_type __x)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) {
+	__i += iter_difference_t<_It>(__x);
+      };
+      __detail::__tuple_for_each(__f, _M_current);
+      return *this;
+    }
+
+    constexpr iterator&
+    operator-=(difference_type __x)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) {
+	__i -= iter_difference_t<_It>(__x);
+      };
+      __detail::__tuple_for_each(__f, _M_current);
+      return *this;
+    }
+
+    constexpr auto
+    operator[](difference_type __n) const
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) -> decltype(auto) {
+	return __i[iter_difference_t<_It>(__n)];
+      };
+      return __detail::__tuple_transform(__f, _M_current);
+    }
+
+    friend constexpr bool
+    operator==(const iterator& __x, const iterator& __y)
+      requires (equality_comparable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      if constexpr (__detail::__all_bidirectional<_Const, _Vs...>)
+	return __x._M_current == __y._M_current;
+      else
+	return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	  return ((std::get<_Is>(__x._M_current) == std::get<_Is>(__y._M_current)) || ...);
+	}(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend constexpr bool
+    operator<(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return __x._M_current < __y._M_current; }
+
+    friend constexpr bool
+    operator>(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return __y < __x; }
+
+    friend constexpr bool
+    operator<=(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return !(__y < __x); }
+
+    friend constexpr bool
+    operator>=(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return !(__x < __y); }
+
+    friend constexpr auto
+    operator<=>(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+	&& (three_way_comparable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    { return __x._M_current <=> __y._M_current; }
+
+    friend constexpr iterator
+    operator+(const iterator& __i, difference_type __n)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r += __n;
+      return __r;
+    }
+
+    friend constexpr iterator
+    operator+(difference_type __n, const iterator& __i)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r += __n;
+      return __r;
+    }
+
+    friend constexpr iterator
+    operator-(const iterator& __i, difference_type __n)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r -= __n;
+      return __r;
+    }
+
+    friend constexpr difference_type
+    operator-(const iterator& __x, const iterator& __y)
+      requires (sized_sentinel_for<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ranges::min({difference_type(std::get<_Is>(__x._M_current)
+					    - std::get<_Is>(__y._M_current))...},
+			   ranges::less{},
+			   [](difference_type __i) -> make_unsigned_t<difference_type> {
+			     // TODO: use constexpr std::abs from P0533R9 once implemented
+			     return __i < 0 ? -__i : __i;
+			   });
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend constexpr auto
+    iter_move(const iterator& __i)
+    { return __detail::__tuple_transform(ranges::iter_move, __i._M_current); }
+
+    friend constexpr void
+    iter_swap(const iterator& __l, const iterator& __r)
+      requires (indirectly_swappable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      [&]<size_t... _Is>(index_sequence<_Is...>) {
+	(ranges::iter_swap(std::get<_Is>(__l._M_current), std::get<_Is>(__r._M_current)), ...);
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend class zip_view;
+  };
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  template<bool _Const>
+  class zip_view<_Vs...>::sentinel
+  {
+    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
+
+    constexpr explicit
+    sentinel(decltype(_M_end) __end)
+      : _M_end(__end)
+    { }
+
+    friend class zip_view;
+
+  public:
+    sentinel() = default;
+
+    constexpr
+    sentinel(sentinel<!_Const> __i)
+      requires _Const
+	&& (convertible_to<sentinel_t<_Vs>,
+			   sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+      : _M_end(std::move(__i._M_end))
+    { }
+
+    template<bool _OtherConst>
+      requires (sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+			     iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr bool
+    operator==(const iterator<_OtherConst>& __x, const sentinel& __y)
+    {
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ((std::get<_Is>(__x._M_current) == std::get<_Is>(__y._M_end)) || ...);
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    template<bool _OtherConst>
+      requires (sized_sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr auto
+    operator-(const iterator<_OtherConst>& __x, const sentinel& __y)
+    {
+      using _Ret
+	= common_type_t<range_difference_t<__detail::__maybe_const_t<_OtherConst, _Vs>>...>;
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ranges::min({_Ret(std::get<_Is>(__x._M_current) - std::get<_Is>(__y._M_end))...},
+			   ranges::less{},
+			   [](_Ret __i) -> make_unsigned_t<_Ret> {
+			     // TODO: use constexpr std::abs from P0533R9 once implemented
+			     return __i < 0 ? -__i : __i;
+			   });
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    template<bool _OtherConst>
+      requires (sized_sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr auto
+    operator-(const sentinel& __y, const iterator<_OtherConst>& __x)
+    { return -(__x - __y); }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename... _Ts>
+	concept __can_zip_view
+	  = requires { zip_view<all_t<_Ts>...>(std::declval<_Ts>()...); };
+    }
+
+    struct _Zip
+    {
+      template<typename... _Ts>
+	requires (sizeof...(_Ts) == 0 || __detail::__can_zip_view<_Ts...>)
+	[[nodiscard]]
+	constexpr auto
+	operator()(_Ts&&... __ts) const
+	{
+	  if constexpr (sizeof...(_Ts) == 0)
+	    return views::empty<tuple<>>;
+	  else
+	    return zip_view<all_t<_Ts>...>(std::forward<_Ts>(__ts)...);
+	}
+    };
+
+    inline constexpr _Zip zip;
+  }
+#endif // C++23
 } // namespace ranges
 
   namespace views = ranges::views;
diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
new file mode 100644
index 00000000000..4d5835829dd
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
@@ -0,0 +1,101 @@
+// { dg-options "-std=gnu++23" }
+// { dg-do run { target c++23 } }
+
+#include <ranges>
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+namespace ranges = std::ranges;
+namespace views = std::views;
+
+constexpr bool
+test01()
+{
+  static_assert(ranges::empty(views::zip()));
+  static_assert(ranges::empty(views::empty<int>));
+
+  auto z1 = views::zip(std::array{1, 2});
+  const auto i0 = z1.begin(), i1 = z1.begin() + 1;
+  VERIFY( i0 + 1 - 1 == i0 );
+  VERIFY( i0 < i1 );
+  VERIFY( i1 < z1.end() );
+  VERIFY( i1 - i0 == 1 );
+  VERIFY( i0 - i1 == -1 );
+  VERIFY( z1.end() - i1 == 1 );
+  VERIFY( i1 - z1.end() == -1 );
+  ranges::iter_swap(i0, i1);
+  VERIFY( ranges::equal(std::move(z1) | views::keys, (int[]){2, 1}) );
+
+  auto z2 = views::zip(std::array{1, 2}, std::array{3, 4, 5});
+  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 );
+  for (const auto [x, y] : z2)
+    {
+      VERIFY( y - x == 2 );
+      std::swap(x, y);
+    }
+
+  int x[2] = {1, 2}, y[2] = {3, 4}, z[2] = {5, 6};
+  const auto z3 = views::zip(x, y, z);
+  VERIFY( ranges::size(z3) == 2 );
+  for (int i = 0; i < ranges::size(x); i++)
+    {
+      VERIFY( &std::get<0>(z3[i]) == &x[i] );
+      VERIFY( &std::get<1>(z3[i]) == &y[i] );
+      VERIFY( &std::get<2>(z3[i]) == &z[i] );
+    }
+
+  return true;
+}
+
+constexpr bool
+test02()
+{
+  using __gnu_test::test_input_range;
+  using __gnu_test::test_forward_range;
+  using __gnu_test::test_random_access_range;
+
+  using ty1 = ranges::zip_view<views::all_t<test_forward_range<int>>,
+			       views::all_t<test_random_access_range<int>>>;
+  static_assert(ranges::forward_range<ty1>);
+  static_assert(!ranges::random_access_range<ty1>);
+  static_assert(!ranges::sized_range<ty1>);
+
+  using ty2 = ranges::zip_view<views::all_t<test_forward_range<int>>,
+			       views::all_t<test_input_range<int>>,
+			       views::all_t<test_forward_range<int>>>;
+  static_assert(ranges::input_range<ty2>);
+  static_assert(!ranges::forward_range<ty2>);
+  static_assert(!ranges::sized_range<ty2>);
+
+  return true;
+}
+
+constexpr bool
+test03()
+{
+  int u[] = {1, 2, 3, 4}, v[] = {4, 5, 6}, w[] = {7, 8, 9, 10};
+  auto z = views::zip(u | views::filter([](auto) { return true; }), v, w);
+  using ty = decltype(z);
+  static_assert(ranges::forward_range<ty>);
+  static_assert(!ranges::common_range<ty>);
+  static_assert(!ranges::sized_range<ty>);
+  VERIFY( z.begin() == z.begin() );
+  VERIFY( z.begin() != z.end() );
+  VERIFY( ranges::next(z.begin(), 3) == z.end() );
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(test02());
+  static_assert(test03());
+}
-- 
2.37.2.382.g795ea8776b


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

* Re: [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple
  2022-08-23  1:34 [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Patrick Palka
  2022-08-23  1:34 ` [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2 Patrick Palka
  2022-08-23  1:35 ` [PATCH 3/3] libstdc++: Implement ranges::zip_view " Patrick Palka
@ 2022-08-23  9:15 ` Jonathan Wakely
  2022-08-23 13:44   ` Patrick Palka
  2 siblings, 1 reply; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-23  9:15 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> P2321R2 adds new conditionally explicit constructors to std::tuple which
> we'll concisely implement in a subsequent patch using explicit(bool), like
> in our C++20 std::pair implementation.  But before we can do that, this
> patch first adds members to _TupleConstraints that test for constructibility
> and convertibility separately; we'll use the first in the new constructors'
> constraints, and the second in their explicit specifier.
>
> In passing, this patch also redefines the existing predicates
> __is_ex/implicitly_constructible in terms of these new members.  This
> seems to reduce compile time and memory usage by about 10% for large

Nice.

> tuples when using the relevant constructors constrained by
> _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> and process is_constructible<_Types, _UTypes>... twice for each pair of
> such constructors).  In order to retain maximal short circuiting, do
> this only when constexpr if is available.

Would we get similar benefits for C++11 and C++14 by doing:

       return __and_<__and_<is_constructible<_Types, _UTypes>...>,
                     __and_<is_convertible<_UTypes, _Types>...>
                     >::value;

This is slightly more work in total, but if we have __and_<A,B> and
__and_<A,__not_<B>> then the A and B instantiations will be cached and
can be reused.


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

Yes, thanks.


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

* Re: [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2
  2022-08-23  1:34 ` [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2 Patrick Palka
@ 2022-08-23 12:03   ` Jonathan Wakely
  2022-08-23 15:14     ` Patrick Palka
  0 siblings, 1 reply; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-23 12:03 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

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.


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

* Re: [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple
  2022-08-23  9:15 ` [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Jonathan Wakely
@ 2022-08-23 13:44   ` Patrick Palka
  2022-08-23 14:53     ` Jonathan Wakely
  0 siblings, 1 reply; 11+ messages in thread
From: Patrick Palka @ 2022-08-23 13:44 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, gcc-patches, libstdc++

On Tue, 23 Aug 2022, Jonathan Wakely wrote:

> On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> >
> > P2321R2 adds new conditionally explicit constructors to std::tuple which
> > we'll concisely implement in a subsequent patch using explicit(bool), like
> > in our C++20 std::pair implementation.  But before we can do that, this
> > patch first adds members to _TupleConstraints that test for constructibility
> > and convertibility separately; we'll use the first in the new constructors'
> > constraints, and the second in their explicit specifier.
> >
> > In passing, this patch also redefines the existing predicates
> > __is_ex/implicitly_constructible in terms of these new members.  This
> > seems to reduce compile time and memory usage by about 10% for large
> 
> Nice.
> 
> > tuples when using the relevant constructors constrained by
> > _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> > and process is_constructible<_Types, _UTypes>... twice for each pair of
> > such constructors).  In order to retain maximal short circuiting, do
> > this only when constexpr if is available.
> 
> Would we get similar benefits for C++11 and C++14 by doing:
> 
>        return __and_<__and_<is_constructible<_Types, _UTypes>...>,
>                      __and_<is_convertible<_UTypes, _Types>...>
>                      >::value;
> 
> This is slightly more work in total, but if we have __and_<A,B> and
> __and_<A,__not_<B>> then the A and B instantiations will be cached and
> can be reused.

Good idea, it seems we get pretty much the same 10% improvement for
C++11/14 with this approach.  I reckon we might as well then define
__convertible and __constructible as alias templates instead of as
variable templates and use them unconditionally in
__is_im/explicitly_constructible for benefit of all language versions.
Using constexpr if instead of the outer __and_ seems to yield a marginal
improvement at best and __and_v is currently just __and_::value, so it
doesn't seem worth it to have different definitions for C++17 at least
for now.

What do you think about the following?

-- >8 --

libstdc++-v3/ChangeLog:

	* include/std/tuple (_TupleConstraints::__convertible): Define.
	(_TupleConstraints::__constructible): Likewise.
	(_TupleConstraints::__is_explicitly_constructible): Redefine this
	in terms of __convertible and __constructible.
	(_TupleConstraints::__is_implicitly_constructible): Likewise.
---
 libstdc++-v3/include/std/tuple | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 6d0060a191c..f8f48ccc370 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -553,15 +553,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<bool, typename... _Types>
     struct _TupleConstraints
     {
+      template<typename... _UTypes>
+	using __constructible = __and_<is_constructible<_Types, _UTypes>...>;
+
+      template<typename... _UTypes>
+	using __convertible = __and_<is_convertible<_UTypes, _Types>...>;
+
       // Constraint for a non-explicit constructor.
       // True iff each Ti in _Types... can be constructed from Ui in _UTypes...
       // and every Ui is implicitly convertible to Ti.
       template<typename... _UTypes>
 	static constexpr bool __is_implicitly_constructible()
 	{
-	  return __and_<is_constructible<_Types, _UTypes>...,
-			is_convertible<_UTypes, _Types>...
-			>::value;
+	  return __and_<__constructible<_UTypes...>,
+			__convertible<_UTypes...>>::value;
 	}
 
       // Constraint for a non-explicit constructor.
@@ -570,9 +575,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename... _UTypes>
 	static constexpr bool __is_explicitly_constructible()
 	{
-	  return __and_<is_constructible<_Types, _UTypes>...,
-			__not_<__and_<is_convertible<_UTypes, _Types>...>>
-			>::value;
+	  return __and_<__constructible<_UTypes...>,
+			__not_<__convertible<_UTypes...>>>::value;
 	}
 
       static constexpr bool __is_implicitly_default_constructible()
-- 
2.37.2.382.g795ea8776b


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

* Re: [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple
  2022-08-23 13:44   ` Patrick Palka
@ 2022-08-23 14:53     ` Jonathan Wakely
  0 siblings, 0 replies; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-23 14:53 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Tue, 23 Aug 2022 at 14:44, Patrick Palka <ppalka@redhat.com> wrote:
>
> On Tue, 23 Aug 2022, Jonathan Wakely wrote:
>
> > On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
> > <libstdc++@gcc.gnu.org> wrote:
> > >
> > > P2321R2 adds new conditionally explicit constructors to std::tuple which
> > > we'll concisely implement in a subsequent patch using explicit(bool), like
> > > in our C++20 std::pair implementation.  But before we can do that, this
> > > patch first adds members to _TupleConstraints that test for constructibility
> > > and convertibility separately; we'll use the first in the new constructors'
> > > constraints, and the second in their explicit specifier.
> > >
> > > In passing, this patch also redefines the existing predicates
> > > __is_ex/implicitly_constructible in terms of these new members.  This
> > > seems to reduce compile time and memory usage by about 10% for large
> >
> > Nice.
> >
> > > tuples when using the relevant constructors constrained by
> > > _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> > > and process is_constructible<_Types, _UTypes>... twice for each pair of
> > > such constructors).  In order to retain maximal short circuiting, do
> > > this only when constexpr if is available.
> >
> > Would we get similar benefits for C++11 and C++14 by doing:
> >
> >        return __and_<__and_<is_constructible<_Types, _UTypes>...>,
> >                      __and_<is_convertible<_UTypes, _Types>...>
> >                      >::value;
> >
> > This is slightly more work in total, but if we have __and_<A,B> and
> > __and_<A,__not_<B>> then the A and B instantiations will be cached and
> > can be reused.
>
> Good idea, it seems we get pretty much the same 10% improvement for
> C++11/14 with this approach.  I reckon we might as well then define
> __convertible and __constructible as alias templates instead of as
> variable templates and use them unconditionally in
> __is_im/explicitly_constructible for benefit of all language versions.

I had a similar thought after hitting send.


> Using constexpr if instead of the outer __and_ seems to yield a marginal
> improvement at best and __and_v is currently just __and_::value, so it
> doesn't seem worth it to have different definitions for C++17 at least
> for now.
>
> What do you think about the following?

OK for trunk - thanks.


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

* Re: [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2
  2022-08-23 12:03   ` Jonathan Wakely
@ 2022-08-23 15:14     ` Patrick Palka
  0 siblings, 0 replies; 11+ messages in thread
From: Patrick Palka @ 2022-08-23 15:14 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Patrick Palka, gcc-patches, libstdc++

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.


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

* Re: [PATCH 3/3] libstdc++: Implement ranges::zip_view from P2321R2
  2022-08-23  1:35 ` [PATCH 3/3] libstdc++: Implement ranges::zip_view " Patrick Palka
@ 2022-08-24 12:15   ` Jonathan Wakely
  2022-08-26 20:05   ` Jonathan Wakely
  1 sibling, 0 replies; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-24 12:15 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> Tested on 86_64-pc-linux-gnu, does this look OK for trunk?

As discussed privately, please remove the #include <cstdint> (which is
not needed, and adds declarations to the global namespace that we
don't need).

OK with that change, thanks.

N.B. re the TODOs, we already have a constexpr abs in <numeric> but
it's probably not worth including that just for one function, that
you've already managed without.


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

* Re: [PATCH 3/3] libstdc++: Implement ranges::zip_view from P2321R2
  2022-08-23  1:35 ` [PATCH 3/3] libstdc++: Implement ranges::zip_view " Patrick Palka
  2022-08-24 12:15   ` Jonathan Wakely
@ 2022-08-26 20:05   ` Jonathan Wakely
  2022-08-31 10:12     ` Jonathan Wakely
  1 sibling, 1 reply; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-26 20:05 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
> +    template<typename _Fp, typename _Tuple>
> +      constexpr void
> +      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
> +      {
> +       std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> +         (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
> +       }, std::forward<_Tuple>(__tuple));


Nicole Mazzuca noticed a problem with the definition of
tuple-for-each, which I've just added to the issues list:
https://cplusplus.github.io/LWG/issue3755


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

* Re: [PATCH 3/3] libstdc++: Implement ranges::zip_view from P2321R2
  2022-08-26 20:05   ` Jonathan Wakely
@ 2022-08-31 10:12     ` Jonathan Wakely
  0 siblings, 0 replies; 11+ messages in thread
From: Jonathan Wakely @ 2022-08-31 10:12 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, libstdc++

On Fri, 26 Aug 2022 at 21:05, Jonathan Wakely wrote:
>
> On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> > +    template<typename _Fp, typename _Tuple>
> > +      constexpr void
> > +      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
> > +      {
> > +       std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> > +         (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
> > +       }, std::forward<_Tuple>(__tuple));
>
>
> Nicole Mazzuca noticed a problem with the definition of
> tuple-for-each, which I've just added to the issues list:
> https://cplusplus.github.io/LWG/issue3755

Ignore this. The __tuple_for_each function is only ever called by the
library, so there's no way for something like Evil to be passed to it.


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

end of thread, other threads:[~2022-08-31 10:13 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-23  1:34 [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Patrick Palka
2022-08-23  1:34 ` [PATCH 2/3] libstdc++: Implement std::pair/tuple/misc enhancements from P2321R2 Patrick Palka
2022-08-23 12:03   ` Jonathan Wakely
2022-08-23 15:14     ` Patrick Palka
2022-08-23  1:35 ` [PATCH 3/3] libstdc++: Implement ranges::zip_view " Patrick Palka
2022-08-24 12:15   ` Jonathan Wakely
2022-08-26 20:05   ` Jonathan Wakely
2022-08-31 10:12     ` Jonathan Wakely
2022-08-23  9:15 ` [PATCH 1/3] libstdc++: Separate construct/convertibility tests for std::tuple Jonathan Wakely
2022-08-23 13:44   ` Patrick Palka
2022-08-23 14:53     ` Jonathan Wakely

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