[libcxx-commits] [libcxx] 95733a5 - [libc++] P2321R2 section [tuple.tuple]. Adding C++23 constructors, assignment operators and swaps to `tuple`

Hui Xie via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jun 23 13:29:08 PDT 2022


Author: Hui Xie
Date: 2022-06-23T21:28:57+01:00
New Revision: 95733a55b986e73f4d8f5314e0d4557d8ae0b226

URL: https://github.com/llvm/llvm-project/commit/95733a55b986e73f4d8f5314e0d4557d8ae0b226
DIFF: https://github.com/llvm/llvm-project/commit/95733a55b986e73f4d8f5314e0d4557d8ae0b226.diff

LOG: [libc++] P2321R2 section [tuple.tuple]. Adding C++23 constructors, assignment operators and swaps to `tuple`

1. for constructors that takes cvref variation of tuple<UTypes...>, there
used to be two SFINAE helper _EnableCopyFromOtherTuple,
_EnableMoveFromOtherTuple. And the implementations of these two helpers
seem to slightly differ from the spec. But now, we need 4 variations.
Instead of adding another two, this change refactored it to a single one
_EnableCtrFromUTypesTuple, which directly maps to the spec without
changing the C++11 behaviour. However, we need the helper __copy_cvref_t
to get the type of std::get<i>(cvref tuple<Utypes...>) for different
cvref, so I made __copy_cvref_t to be available in C++11.

2. for constructors that takes variations of std::pair, there used to be
four helpers _EnableExplicitCopyFromPair, _EnableImplicitCopyFromPair,
_EnableImplicitMoveFromPair, _EnableExplicitMoveFromPair. Instead of
adding another four, this change refactored into two helper
_EnableCtrFromPair and _BothImplicitlyConvertible. This also removes the
need to use _nat

3. for const member assignment operator, since the requirement is very
simple, I haven't refactored the old code but instead directly adding
the new c++23 code.

4. for const swap, I pretty much copy pasted the non-const version to make
these overloads look consistent

5. while doing these change, I found two of the old constructors wasn't
marked constexpr for C++20 but they should. fixed them and added unit
tests

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D116621

Added: 
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/types.h
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_const_move_pair.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_const_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_non_const_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_non_const_pair.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/const_move_pair.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_const_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_non_const_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_types.h
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/non_const_pair.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.special/non_member_swap_const.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap_const.pass.cpp

Modified: 
    libcxx/include/tuple
    libcxx/include/type_traits
    libcxx/test/libcxx/utilities/meta/meta_base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.zip/range.concept.compile.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/tuple b/libcxx/include/tuple
index 251b685912b61..d0c159249ede9 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -25,14 +25,24 @@ public:
         explicit(see-below) tuple(U&&...);  // constexpr in C++14
     tuple(const tuple&) = default;
     tuple(tuple&&) = default;
+
+    template<class... UTypes>
+        constexpr explicit(see-below) tuple(tuple<UTypes...>&);  // C++23
     template <class... U>
         explicit(see-below) tuple(const tuple<U...>&);  // constexpr in C++14
     template <class... U>
         explicit(see-below) tuple(tuple<U...>&&);  // constexpr in C++14
+    template<class... UTypes>
+        constexpr explicit(see-below) tuple(const tuple<UTypes...>&&); // C++23
+
+    template<class U1, class U2>
+        constexpr explicit(see-below) tuple(pair<U1, U2>&);  // iff sizeof...(Types) == 2 // C++23
     template <class U1, class U2>
         explicit(see-below) tuple(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++14
     template <class U1, class U2>
         explicit(see-below) tuple(pair<U1, U2>&&); // iff sizeof...(T) == 2  // constexpr in C++14
+    template<class U1, class U2>
+        constexpr explicit(see-below) tuple(const pair<U1, U2>&&);  // iff sizeof...(Types) == 2 // C++23
 
     // allocator-extended constructors
     template <class Alloc>
@@ -45,25 +55,47 @@ public:
         tuple(allocator_arg_t, const Alloc& a, const tuple&);                             // constexpr in C++20
     template <class Alloc>
         tuple(allocator_arg_t, const Alloc& a, tuple&&);                                  // constexpr in C++20
+    template<class Alloc, class... UTypes>
+        constexpr explicit(see-below)
+          tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&);                      // C++23    
     template <class Alloc, class... U>
         explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const tuple<U...>&);   // constexpr in C++20
     template <class Alloc, class... U>
         explicit(see-below) tuple(allocator_arg_t, const Alloc& a, tuple<U...>&&);        // constexpr in C++20
+    template<class Alloc, class... UTypes>
+        constexpr explicit(see-below)
+          tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&);               // C++23
+    template<class Alloc, class U1, class U2>
+        constexpr explicit(see-below)
+          tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&);                          // C++23
     template <class Alloc, class U1, class U2>
         explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&);  // constexpr in C++20
     template <class Alloc, class U1, class U2>
         explicit(see-below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&);       // constexpr in C++20
+    template<class Alloc, class U1, class U2>
+        constexpr explicit(see-below)
+          tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&);                   // C++23
 
     tuple& operator=(const tuple&);                                                       // constexpr in C++20
+    constexpr const tuple& operator=(const tuple&) const;                                 // C++23
     tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v<T> && ...);           // constexpr in C++20
+    constexpr const tuple& operator=(tuple&&) const;                                      // C++23
     template <class... U>
         tuple& operator=(const tuple<U...>&);                                             // constexpr in C++20
+    template<class... UTypes>
+        constexpr const tuple& operator=(const tuple<UTypes...>&) const;                  // C++23
     template <class... U>
         tuple& operator=(tuple<U...>&&);                                                  // constexpr in C++20
+    template<class... UTypes>
+        constexpr const tuple& operator=(tuple<UTypes...>&&) const;                       // C++23
     template <class U1, class U2>
         tuple& operator=(const pair<U1, U2>&); // iff sizeof...(T) == 2                   // constexpr in C++20
+    template<class U1, class U2>
+        constexpr const tuple& operator=(const pair<U1, U2>&) const;   // iff sizeof...(Types) == 2 // C++23
     template <class U1, class U2>
         tuple& operator=(pair<U1, U2>&&); // iff sizeof...(T) == 2                        // constexpr in C++20
+    template<class U1, class U2>
+        constexpr const tuple& operator=(pair<U1, U2>&&) const;  // iff sizeof...(Types) == 2 // C++23
 
     template<class U, size_t N>
         tuple& operator=(array<U, N> const&) // iff sizeof...(T) == N, EXTENSION
@@ -71,6 +103,7 @@ public:
         tuple& operator=(array<U, N>&&) // iff sizeof...(T) == N, EXTENSION
 
     void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...));               // constexpr in C++20
+    constexpr void swap(const tuple&) const noexcept(see-below);                          // C++23
 };
 
 
@@ -161,6 +194,9 @@ template <class... Types>
   void
   swap(tuple<Types...>& x, tuple<Types...>& y) noexcept(noexcept(x.swap(y)));
 
+template <class... Types>
+  constexpr void swap(const tuple<Types...>& x, const tuple<Types...>& y) noexcept(see-below);   // C++23
+
 }  // std
 
 */
@@ -210,6 +246,13 @@ void swap(__tuple_leaf<_Ip, _Hp, _Ep>& __x, __tuple_leaf<_Ip, _Hp, _Ep>& __y)
     swap(__x.get(), __y.get());
 }
 
+template <size_t _Ip, class _Hp, bool _Ep>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11
+void swap(const __tuple_leaf<_Ip, _Hp, _Ep>& __x, const __tuple_leaf<_Ip, _Hp, _Ep>& __y) 
+     _NOEXCEPT_(__is_nothrow_swappable<const _Hp>::value) {
+  swap(__x.get(), __y.get());
+}
+
 template <size_t _Ip, class _Hp, bool>
 class __tuple_leaf
 {
@@ -298,6 +341,12 @@ public:
         return 0;
     }
 
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
+    int swap(const __tuple_leaf& __t) const _NOEXCEPT_(__is_nothrow_swappable<const __tuple_leaf>::value) {
+        _VSTD::swap(*this, __t);
+        return 0;
+    }
+
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11       _Hp& get()       _NOEXCEPT {return __value_;}
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return __value_;}
 };
@@ -364,6 +413,12 @@ public:
         return 0;
     }
 
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
+    int swap(const __tuple_leaf& __rhs) const _NOEXCEPT_(__is_nothrow_swappable<const __tuple_leaf>::value) {
+        _VSTD::swap(*this, __rhs);
+        return 0;
+    }
+
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11       _Hp& get()       _NOEXCEPT {return static_cast<_Hp&>(*this);}
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return static_cast<const _Hp&>(*this);}
 };
@@ -454,6 +509,13 @@ struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp.
     {
         _VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...);
     }
+
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
+    void swap(const __tuple_impl& __t) const
+        _NOEXCEPT_(__all<__is_nothrow_swappable<const _Tp>::value...>::value)
+    {
+        _VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<const __tuple_leaf<_Indx, _Tp>&>(__t))...);
+    }
 };
 
 template<class _Dest, class _Source, size_t ..._Np>
@@ -689,6 +751,7 @@ public:
     template <class _Alloc, template<class...> class _And = _And, __enable_if_t<
         _And<is_copy_constructible<_Tp>...>::value
     , int> = 0>
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple(allocator_arg_t, const _Alloc& __alloc, const tuple& __t)
         : __base_(allocator_arg_t(), __alloc, __t)
     { }
@@ -696,30 +759,39 @@ public:
     template <class _Alloc, template<class...> class _And = _And, __enable_if_t<
         _And<is_move_constructible<_Tp>...>::value
     , int> = 0>
+    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple(allocator_arg_t, const _Alloc& __alloc, tuple&& __t)
         : __base_(allocator_arg_t(), __alloc, _VSTD::move(__t))
     { }
 
     // tuple(const tuple<U...>&) constructors (including allocator_arg_t variants)
-    template <class ..._Up>
-    struct _EnableCopyFromOtherTuple : _And<
-        _Not<is_same<tuple<_Tp...>, tuple<_Up...> > >,
-        _Lazy<_Or,
-            _BoolConstant<sizeof...(_Tp) != 1>,
+    
+    template <class _OtherTuple, class _DecayedOtherTuple = __uncvref_t<_OtherTuple>, class = void>
+    struct _EnableCtorFromUTypesTuple : false_type {};
+
+    template <class _OtherTuple, class... _Up>
+    struct _EnableCtorFromUTypesTuple<_OtherTuple, tuple<_Up...>, 
+              // the length of the packs needs to checked first otherwise the 2 packs cannot be expanded simultaneously below
+               __enable_if_t<sizeof...(_Up) == sizeof...(_Tp)>> : _And<
+        // the two conditions below are not in spec. The purpose is to disable the UTypes Ctor when copy/move Ctor can work.
+        // Otherwise, is_constructible can trigger hard error in those cases https://godbolt.org/z/M94cGdKcE
+        _Not<is_same<_OtherTuple, const tuple&> >,
+        _Not<is_same<_OtherTuple, tuple&&> >,
+        is_constructible<_Tp, __copy_cvref_t<_OtherTuple, _Up> >...,
+        _Lazy<_Or, _BoolConstant<sizeof...(_Tp) != 1>,
             // _Tp and _Up are 1-element packs - the pack expansions look
             // weird to avoid tripping up the type traits in degenerate cases
             _Lazy<_And,
-                _Not<is_convertible<const tuple<_Up>&, _Tp> >...,
-                _Not<is_constructible<_Tp, const tuple<_Up>&> >...
+                _Not<is_same<_Tp, _Up> >...,
+                _Not<is_convertible<_OtherTuple, _Tp> >...,
+                _Not<is_constructible<_Tp, _OtherTuple> >...
             >
-        >,
-        is_constructible<_Tp, const _Up&>...
-    > { };
+        >
+    > {};
 
     template <class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableCopyFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<const tuple<_Up...>&>,
             is_convertible<const _Up&, _Tp>... // explicit check
         >::value
     , int> = 0>
@@ -731,8 +803,7 @@ public:
 
     template <class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableCopyFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<const tuple<_Up...>&>,
             _Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check
         >::value
     , int> = 0>
@@ -744,8 +815,7 @@ public:
 
     template <class ..._Up, class _Alloc, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableCopyFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<const tuple<_Up...>&>,
             is_convertible<const _Up&, _Tp>... // explicit check
         >::value
     , int> = 0>
@@ -756,8 +826,7 @@ public:
 
     template <class ..._Up, class _Alloc, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableCopyFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<const tuple<_Up...>&>,
             _Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check
         >::value
     , int> = 0>
@@ -766,26 +835,27 @@ public:
         : __base_(allocator_arg_t(), __a, __t)
     { }
 
+#if _LIBCPP_STD_VER > 20
+    // tuple(tuple<U...>&) constructors (including allocator_arg_t variants)
+
+    template <class... _Up, enable_if_t<
+        _EnableCtorFromUTypesTuple<tuple<_Up...>&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!(is_convertible_v<_Up&, _Tp> && ...))
+    tuple(tuple<_Up...>& __t) : __base_(__t) {}
+
+    template <class _Alloc, class... _Up, enable_if_t<
+        _EnableCtorFromUTypesTuple<tuple<_Up...>&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!(is_convertible_v<_Up&, _Tp> && ...))
+    tuple(allocator_arg_t, const _Alloc& __alloc, tuple<_Up...>& __t) : __base_(allocator_arg_t(), __alloc, __t) {}
+#endif // _LIBCPP_STD_VER > 20
+
     // tuple(tuple<U...>&&) constructors (including allocator_arg_t variants)
-    template <class ..._Up>
-    struct _EnableMoveFromOtherTuple : _And<
-        _Not<is_same<tuple<_Tp...>, tuple<_Up...> > >,
-        _Lazy<_Or,
-            _BoolConstant<sizeof...(_Tp) != 1>,
-            // _Tp and _Up are 1-element packs - the pack expansions look
-            // weird to avoid tripping up the type traits in degenerate cases
-            _Lazy<_And,
-                _Not<is_convertible<tuple<_Up>, _Tp> >...,
-                _Not<is_constructible<_Tp, tuple<_Up> > >...
-            >
-        >,
-        is_constructible<_Tp, _Up>...
-    > { };
 
     template <class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableMoveFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<tuple<_Up...>&&>,
             is_convertible<_Up, _Tp>... // explicit check
         >::value
     , int> = 0>
@@ -797,8 +867,7 @@ public:
 
     template <class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableMoveFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<tuple<_Up...>&&>,
             _Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check
         >::value
     , int> = 0>
@@ -810,8 +879,7 @@ public:
 
     template <class _Alloc, class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableMoveFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<tuple<_Up...>&&>,
             is_convertible<_Up, _Tp>... // explicit check
         >::value
     , int> = 0>
@@ -822,8 +890,7 @@ public:
 
     template <class _Alloc, class ..._Up, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>,
-            _EnableMoveFromOtherTuple<_Up...>,
+            _EnableCtorFromUTypesTuple<tuple<_Up...>&&>,
             _Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check
         >::value
     , int> = 0>
@@ -832,57 +899,77 @@ public:
         : __base_(allocator_arg_t(), __a, _VSTD::move(__t))
     { }
 
+#if _LIBCPP_STD_VER > 20
+    // tuple(const tuple<U...>&&) constructors (including allocator_arg_t variants)
+
+    template <class... _Up, enable_if_t<
+        _EnableCtorFromUTypesTuple<const tuple<_Up...>&&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!(is_convertible_v<const _Up&&, _Tp> && ...))
+    tuple(const tuple<_Up...>&& __t) : __base_(std::move(__t)) {}
+
+    template <class _Alloc, class... _Up, enable_if_t<
+        _EnableCtorFromUTypesTuple<const tuple<_Up...>&&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!(is_convertible_v<const _Up&&, _Tp> && ...))
+    tuple(allocator_arg_t, const _Alloc& __alloc, const tuple<_Up...>&& __t)
+        : __base_(allocator_arg_t(), __alloc, std::move(__t)) {}
+#endif // _LIBCPP_STD_VER > 20
+
     // tuple(const pair<U1, U2>&) constructors (including allocator_arg_t variants)
-    template <class _Up1, class _Up2, class ..._DependentTp>
-    struct _EnableImplicitCopyFromPair : _And<
-        is_constructible<_FirstType<_DependentTp...>, const _Up1&>,
-        is_constructible<_SecondType<_DependentTp...>, const _Up2&>,
-        is_convertible<const _Up1&, _FirstType<_DependentTp...> >, // explicit check
-        is_convertible<const _Up2&, _SecondType<_DependentTp...> >
-    > { };
 
-    template <class _Up1, class _Up2, class ..._DependentTp>
-    struct _EnableExplicitCopyFromPair : _And<
-        is_constructible<_FirstType<_DependentTp...>, const _Up1&>,
-        is_constructible<_SecondType<_DependentTp...>, const _Up2&>,
-        _Not<is_convertible<const _Up1&, _FirstType<_DependentTp...> > >, // explicit check
-        _Not<is_convertible<const _Up2&, _SecondType<_DependentTp...> > >
-    > { };
+    template <template <class...> class Pred, class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple>
+    struct _CtorPredicateFromPair : false_type{};
+
+    template <template <class...> class Pred, class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2>
+    struct _CtorPredicateFromPair<Pred, _Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : _And<
+        Pred<_Tp1, __copy_cvref_t<_Pair, _Up1> >,
+        Pred<_Tp2, __copy_cvref_t<_Pair, _Up2> >
+    > {};
+
+    template <class _Pair>
+    struct _EnableCtorFromPair : _CtorPredicateFromPair<is_constructible, _Pair>{};
+
+    template <class _Pair>
+    struct _NothrowConstructibleFromPair : _CtorPredicateFromPair<is_nothrow_constructible, _Pair>{};
+
+    template <class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple>
+    struct _BothImplicitlyConvertible : false_type{};
+
+    template <class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2>
+    struct _BothImplicitlyConvertible<_Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : _And<
+        is_convertible<__copy_cvref_t<_Pair, _Up1>, _Tp1>,
+        is_convertible<__copy_cvref_t<_Pair, _Up2>, _Tp2>
+    > {};
 
     template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableImplicitCopyFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<const pair<_Up1, _Up2>&>,
+            _BothImplicitlyConvertible<const pair<_Up1, _Up2>&> // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
     tuple(const pair<_Up1, _Up2>& __p)
-        _NOEXCEPT_((_And<
-            is_nothrow_constructible<_FirstType<_Tp...>, const _Up1&>,
-            is_nothrow_constructible<_SecondType<_Tp...>, const _Up2&>
-        >::value))
+        _NOEXCEPT_((_NothrowConstructibleFromPair<const pair<_Up1, _Up2>&>::value))
         : __base_(__p)
     { }
 
     template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableExplicitCopyFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<const pair<_Up1, _Up2>&>,
+            _Not<_BothImplicitlyConvertible<const pair<_Up1, _Up2>&> > // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
     explicit tuple(const pair<_Up1, _Up2>& __p)
-        _NOEXCEPT_((_And<
-            is_nothrow_constructible<_FirstType<_Tp...>, const _Up1&>,
-            is_nothrow_constructible<_SecondType<_Tp...>, const _Up2&>
-        >::value))
+        _NOEXCEPT_((_NothrowConstructibleFromPair<const pair<_Up1, _Up2>&>::value))
         : __base_(__p)
     { }
 
     template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableImplicitCopyFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<const pair<_Up1, _Up2>&>,
+            _BothImplicitlyConvertible<const pair<_Up1, _Up2>&> // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
@@ -892,8 +979,8 @@ public:
 
     template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableExplicitCopyFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<const pair<_Up1, _Up2>&>,
+            _Not<_BothImplicitlyConvertible<const pair<_Up1, _Up2>&> > // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
@@ -901,57 +988,52 @@ public:
         : __base_(allocator_arg_t(), __a, __p)
     { }
 
-    // tuple(pair<U1, U2>&&) constructors (including allocator_arg_t variants)
-    template <class _Up1, class _Up2, class ..._DependentTp>
-    struct _EnableImplicitMoveFromPair : _And<
-        is_constructible<_FirstType<_DependentTp...>, _Up1>,
-        is_constructible<_SecondType<_DependentTp...>, _Up2>,
-        is_convertible<_Up1, _FirstType<_DependentTp...> >, // explicit check
-        is_convertible<_Up2, _SecondType<_DependentTp...> >
-    > { };
+#if _LIBCPP_STD_VER > 20
+    // tuple(pair<U1, U2>&) constructors (including allocator_arg_t variants)
+
+    template <class _U1, class _U2, enable_if_t<
+        _EnableCtorFromPair<pair<_U1, _U2>&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!_BothImplicitlyConvertible<pair<_U1, _U2>&>::value)
+    tuple(pair<_U1, _U2>& __p) : __base_(__p) {}
+
+    template <class _Alloc, class _U1, class _U2, enable_if_t<
+        _EnableCtorFromPair<std::pair<_U1, _U2>&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!_BothImplicitlyConvertible<pair<_U1, _U2>&>::value)
+    tuple(allocator_arg_t, const _Alloc& __alloc, pair<_U1, _U2>& __p) : __base_(allocator_arg_t(), __alloc, __p) {}
+#endif
 
-    template <class _Up1, class _Up2, class ..._DependentTp>
-    struct _EnableExplicitMoveFromPair : _And<
-        is_constructible<_FirstType<_DependentTp...>, _Up1>,
-        is_constructible<_SecondType<_DependentTp...>, _Up2>,
-        _Not<is_convertible<_Up1, _FirstType<_DependentTp...> > >, // explicit check
-        _Not<is_convertible<_Up2, _SecondType<_DependentTp...> > >
-    > { };
+    // tuple(pair<U1, U2>&&) constructors (including allocator_arg_t variants)
 
     template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableImplicitMoveFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<pair<_Up1, _Up2>&&>,
+            _BothImplicitlyConvertible<pair<_Up1, _Up2>&&> // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
     tuple(pair<_Up1, _Up2>&& __p)
-        _NOEXCEPT_((_And<
-            is_nothrow_constructible<_FirstType<_Tp...>, _Up1>,
-            is_nothrow_constructible<_SecondType<_Tp...>, _Up2>
-        >::value))
+        _NOEXCEPT_((_NothrowConstructibleFromPair<pair<_Up1, _Up2>&&>::value))
         : __base_(_VSTD::move(__p))
     { }
 
     template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableExplicitMoveFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<pair<_Up1, _Up2>&&>,
+            _Not<_BothImplicitlyConvertible<pair<_Up1, _Up2>&&> > // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
     explicit tuple(pair<_Up1, _Up2>&& __p)
-        _NOEXCEPT_((_And<
-            is_nothrow_constructible<_FirstType<_Tp...>, _Up1>,
-            is_nothrow_constructible<_SecondType<_Tp...>, _Up2>
-        >::value))
+        _NOEXCEPT_((_NothrowConstructibleFromPair<pair<_Up1, _Up2>&&>::value))
         : __base_(_VSTD::move(__p))
     { }
 
     template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableImplicitMoveFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<pair<_Up1, _Up2>&&>,
+            _BothImplicitlyConvertible<pair<_Up1, _Up2>&&> // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
@@ -961,8 +1043,8 @@ public:
 
     template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t<
         _And<
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            _EnableExplicitMoveFromPair<_Up1, _Up2, _Tp...>
+            _EnableCtorFromPair<pair<_Up1, _Up2>&&>,
+            _Not<_BothImplicitlyConvertible<pair<_Up1, _Up2>&&> > // explicit check
         >::value
     , int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
@@ -970,6 +1052,23 @@ public:
         : __base_(allocator_arg_t(), __a, _VSTD::move(__p))
     { }
 
+#if _LIBCPP_STD_VER > 20
+    // tuple(const pair<U1, U2>&&) constructors (including allocator_arg_t variants)
+
+    template <class _U1, class _U2, enable_if_t<
+        _EnableCtorFromPair<const pair<_U1, _U2>&&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!_BothImplicitlyConvertible<const pair<_U1, _U2>&&>::value)
+    tuple(const pair<_U1, _U2>&& __p) : __base_(std::move(__p)) {}
+
+    template <class _Alloc, class _U1, class _U2, enable_if_t<
+        _EnableCtorFromPair<const pair<_U1, _U2>&&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+        explicit(!_BothImplicitlyConvertible<const pair<_U1, _U2>&&>::value)
+    tuple(allocator_arg_t, const _Alloc& __alloc, const pair<_U1, _U2>&& __p)
+        : __base_(allocator_arg_t(), __alloc, std::move(__p)) {}
+#endif // _LIBCPP_STD_VER > 20
+
     // [tuple.assign]
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple)
@@ -980,6 +1079,25 @@ public:
         return *this;
     }
 
+#if _LIBCPP_STD_VER > 20
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(tuple const& __tuple) const
+      requires (_And<is_copy_assignable<const _Tp>...>::value) {
+        std::__memberwise_copy_assign(*this, __tuple, typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(tuple&& __tuple) const
+      requires (_And<is_assignable<const _Tp&, _Tp>...>::value) {
+        std::__memberwise_forward_assign(*this,
+                                         std::move(__tuple),
+                                         __tuple_types<_Tp...>(),
+                                         typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+#endif // _LIBCPP_STD_VER > 20
+
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple)
         _NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value))
@@ -1021,38 +1139,89 @@ public:
         return *this;
     }
 
-    template<class _Up1, class _Up2, class _Dep = true_type, __enable_if_t<
-        _And<_Dep,
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            is_assignable<_FirstType<_Tp..., _Dep>&, _Up1 const&>,
-            is_assignable<_SecondType<_Tp..., _Dep>&, _Up2 const&>
-        >::value
+
+#if _LIBCPP_STD_VER > 20
+    template <class... _UTypes, enable_if_t<
+        _And<_BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)>,
+             is_assignable<const _Tp&, const _UTypes&>...>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(const tuple<_UTypes...>& __u) const {
+        std::__memberwise_copy_assign(*this,
+                                      __u,
+                                      typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    template <class... _UTypes, enable_if_t<
+        _And<_BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)>,
+             is_assignable<const _Tp&, _UTypes>...>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(tuple<_UTypes...>&& __u) const {
+        std::__memberwise_forward_assign(*this,
+                                         __u,
+                                         __tuple_types<_UTypes...>(),
+                                         typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+#endif // _LIBCPP_STD_VER > 20
+
+    template <template<class...> class Pred, bool _Const, 
+              class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple>
+    struct _AssignPredicateFromPair : false_type {};
+
+    template <template<class...> class Pred, bool _Const, 
+              class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2>
+    struct _AssignPredicateFromPair<Pred, _Const, _Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : 
+        _And<Pred<__maybe_const<_Const, _Tp1>&, __copy_cvref_t<_Pair, _Up1> >,
+             Pred<__maybe_const<_Const, _Tp2>&, __copy_cvref_t<_Pair, _Up2> >
+            > {};
+
+    template <bool _Const, class _Pair>
+    struct _EnableAssignFromPair : _AssignPredicateFromPair<is_assignable, _Const, _Pair> {};
+
+    template <bool _Const, class _Pair>
+    struct _NothrowAssignFromPair : _AssignPredicateFromPair<is_nothrow_assignable, _Const, _Pair> {};
+
+#if _LIBCPP_STD_VER > 20
+    template <class _U1, class _U2, enable_if_t<
+        _EnableAssignFromPair<true, const pair<_U1, _U2>&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(const pair<_U1, _U2>& __pair) const
+      noexcept(_NothrowAssignFromPair<true, const pair<_U1, _U2>&>::value) {
+        std::get<0>(*this) = __pair.first;
+        std::get<1>(*this) = __pair.second;
+        return *this;
+    }
+
+    template <class _U1, class _U2, enable_if_t<
+        _EnableAssignFromPair<true, pair<_U1, _U2>&&>::value>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    const tuple& operator=(pair<_U1, _U2>&& __pair) const
+      noexcept(_NothrowAssignFromPair<true, pair<_U1, _U2>&&>::value) {
+        std::get<0>(*this) = std::move(__pair.first);
+        std::get<1>(*this) = std::move(__pair.second);
+        return *this;
+    }
+#endif // _LIBCPP_STD_VER > 20
+
+    template<class _Up1, class _Up2, __enable_if_t<
+        _EnableAssignFromPair<false, pair<_Up1, _Up2> const&>::value
     ,int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple& operator=(pair<_Up1, _Up2> const& __pair)
-        _NOEXCEPT_((_And<
-            is_nothrow_assignable<_FirstType<_Tp...>&, _Up1 const&>,
-            is_nothrow_assignable<_SecondType<_Tp...>&, _Up2 const&>
-        >::value))
+        _NOEXCEPT_((_NothrowAssignFromPair<false, pair<_Up1, _Up2> const&>::value))
     {
         _VSTD::get<0>(*this) = __pair.first;
         _VSTD::get<1>(*this) = __pair.second;
         return *this;
     }
 
-    template<class _Up1, class _Up2, class _Dep = true_type, __enable_if_t<
-        _And<_Dep,
-            _BoolConstant<sizeof...(_Tp) == 2>,
-            is_assignable<_FirstType<_Tp..., _Dep>&, _Up1>,
-            is_assignable<_SecondType<_Tp..., _Dep>&, _Up2>
-        >::value
+    template<class _Up1, class _Up2, __enable_if_t<
+        _EnableAssignFromPair<false, pair<_Up1, _Up2>&&>::value
     ,int> = 0>
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     tuple& operator=(pair<_Up1, _Up2>&& __pair)
-        _NOEXCEPT_((_And<
-            is_nothrow_assignable<_FirstType<_Tp...>&, _Up1>,
-            is_nothrow_assignable<_SecondType<_Tp...>&, _Up2>
-        >::value))
+        _NOEXCEPT_((_NothrowAssignFromPair<false, pair<_Up1, _Up2>&&>::value))
     {
         _VSTD::get<0>(*this) = _VSTD::forward<_Up1>(__pair.first);
         _VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second);
@@ -1096,6 +1265,13 @@ public:
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     void swap(tuple& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value)
         {__base_.swap(__t.__base_);}
+
+#if _LIBCPP_STD_VER > 20
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    void swap(const tuple& __t) const noexcept(__all<is_nothrow_swappable_v<const _Tp&>...>::value) {
+        __base_.swap(__t.__base_);
+    }
+#endif // _LIBCPP_STD_VER > 20
 };
 
 template <>
@@ -1118,6 +1294,9 @@ public:
         tuple(allocator_arg_t, const _Alloc&, array<_Up, 0>) _NOEXCEPT {}
     _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
     void swap(tuple&) _NOEXCEPT {}
+#if _LIBCPP_STD_VER > 20
+    _LIBCPP_HIDE_FROM_ABI constexpr void swap(const tuple&) const noexcept {}
+#endif
 };
 
 #if _LIBCPP_STD_VER > 20
@@ -1158,6 +1337,16 @@ swap(tuple<_Tp...>& __t, tuple<_Tp...>& __u)
                  _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value)
     {__t.swap(__u);}
 
+#if _LIBCPP_STD_VER > 20
+template <class... _Tp>
+_LIBCPP_HIDE_FROM_ABI constexpr
+enable_if_t<__all<is_swappable_v<const _Tp>...>::value, void>
+swap(const tuple<_Tp...>& __lhs, const tuple<_Tp...>& __rhs)
+        noexcept(__all<is_nothrow_swappable_v<const _Tp>...>::value) {
+    __lhs.swap(__rhs);
+}
+#endif
+
 // get
 
 template <size_t _Ip, class ..._Tp>

diff  --git a/libcxx/include/type_traits b/libcxx/include/type_traits
index 7a0c22539015c..8cc261c82af5b 100644
--- a/libcxx/include/type_traits
+++ b/libcxx/include/type_traits
@@ -536,10 +536,6 @@ struct _MetaBase<true> {
   using _SelectImpl _LIBCPP_NODEBUG = _Tp;
   template <template <class...> class _FirstFn, template <class...> class, class ..._Args>
   using _SelectApplyImpl _LIBCPP_NODEBUG = _FirstFn<_Args...>;
-  template <class _First, class...>
-  using _FirstImpl _LIBCPP_NODEBUG = _First;
-  template <class, class _Second, class...>
-  using _SecondImpl _LIBCPP_NODEBUG = _Second;
   template <class _Result, class _First, class ..._Rest>
   using _OrImpl _LIBCPP_NODEBUG = typename _MetaBase<_First::value != true && sizeof...(_Rest) != 0>::template _OrImpl<_First, _Rest...>;
 };
@@ -557,10 +553,6 @@ template <bool _Cond, class _IfRes, class _ElseRes>
 using _If _LIBCPP_NODEBUG = typename _MetaBase<_Cond>::template _SelectImpl<_IfRes, _ElseRes>;
 template <class ..._Rest>
 using _Or _LIBCPP_NODEBUG = typename _MetaBase< sizeof...(_Rest) != 0 >::template _OrImpl<false_type, _Rest...>;
-template <class ..._Args>
-using _FirstType _LIBCPP_NODEBUG = typename _MetaBase<(sizeof...(_Args) >= 1)>::template _FirstImpl<_Args...>;
-template <class ..._Args>
-using _SecondType _LIBCPP_NODEBUG = typename _MetaBase<(sizeof...(_Args) >= 2)>::template _SecondImpl<_Args...>;
 
 template <class ...> using __expand_to_true = true_type;
 template <class ..._Pred>
@@ -1697,10 +1689,8 @@ using _IsCharLikeType = _And<is_standard_layout<_CharT>, is_trivial<_CharT> >;
 template<class _Tp>
 using __make_const_lvalue_ref = const typename remove_reference<_Tp>::type&;
 
-#if _LIBCPP_STD_VER > 17
 template<bool _Const, class _Tp>
-using __maybe_const = conditional_t<_Const, const _Tp, _Tp>;
-#endif // _LIBCPP_STD_VER > 17
+using __maybe_const = typename conditional<_Const, const _Tp, _Tp>::type;
 
 _LIBCPP_END_NAMESPACE_STD
 

diff  --git a/libcxx/test/libcxx/utilities/meta/meta_base.pass.cpp b/libcxx/test/libcxx/utilities/meta/meta_base.pass.cpp
index f04ae608614d9..7357c222e17bc 100644
--- a/libcxx/test/libcxx/utilities/meta/meta_base.pass.cpp
+++ b/libcxx/test/libcxx/utilities/meta/meta_base.pass.cpp
@@ -79,13 +79,6 @@ void test_is_valid_trait() {
   static_assert(!std::_IsValidExpansion<FuncCallable, MemberTest, void*>::value, "");
 }
 
-void test_first_and_second_type() {
-  ASSERT_SAME_TYPE(std::_FirstType<int, long, void*>, int);
-  ASSERT_SAME_TYPE(std::_FirstType<char>, char);
-  ASSERT_SAME_TYPE(std::_SecondType<char, long>, long);
-  ASSERT_SAME_TYPE(std::_SecondType<long long, int, void*>, int);
-}
-
 int main(int, char**) {
   return 0;
 }

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.zip/range.concept.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.zip/range.concept.compile.pass.cpp
index d1dcb75199f46..14b787349799b 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.zip/range.concept.compile.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.zip/range.concept.compile.pass.cpp
@@ -165,15 +165,11 @@ void testConceptTuple() {
   int buffer2[3] = {1, 2, 3};
   int buffer3[4] = {1, 2, 3, 4};
 
-  // TODO: uncomment all the static_asserts once [tuple.tuple] section in P2321R2 is implemented
-  // This is because convertible_to<tuple<int&,int&,int&>&, tuple<int,int,int>> is false without
-  // the above implementation, thus the zip iterator does not model indirectly_readable
-
   {
     std::ranges::zip_view v{ContiguousCommonView{buffer1}, ContiguousCommonView{buffer2},
                             ContiguousCommonView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -183,7 +179,7 @@ void testConceptTuple() {
     std::ranges::zip_view v{ContiguousNonCommonView{buffer1}, ContiguousNonCommonView{buffer2},
                             ContiguousNonCommonView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -193,7 +189,7 @@ void testConceptTuple() {
     std::ranges::zip_view v{ContiguousNonCommonSized{buffer1}, ContiguousNonCommonSized{buffer2},
                             ContiguousNonCommonSized{buffer3}};
     using View = decltype(v);
-    //   static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -203,7 +199,7 @@ void testConceptTuple() {
     std::ranges::zip_view v{SizedRandomAccessView{buffer1}, ContiguousCommonView{buffer2},
                             ContiguousCommonView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -213,7 +209,7 @@ void testConceptTuple() {
     std::ranges::zip_view v{SizedRandomAccessView{buffer1}, SizedRandomAccessView{buffer2},
                             SizedRandomAccessView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -223,7 +219,7 @@ void testConceptTuple() {
     std::ranges::zip_view v{NonSizedRandomAccessView{buffer1}, NonSizedRandomAccessView{buffer2},
                             NonSizedRandomAccessView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::random_access_range<View>);
+    static_assert(std::ranges::random_access_range<View>);
     static_assert(!std::ranges::contiguous_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -232,7 +228,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{BidiCommonView{buffer1}, SizedRandomAccessView{buffer2}, SizedRandomAccessView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::bidirectional_range<View>);
+    static_assert(std::ranges::bidirectional_range<View>);
     static_assert(!std::ranges::random_access_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -241,7 +237,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{BidiCommonView{buffer1}, BidiCommonView{buffer2}, BidiCommonView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::bidirectional_range<View>);
+    static_assert(std::ranges::bidirectional_range<View>);
     static_assert(!std::ranges::random_access_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -250,7 +246,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{BidiCommonView{buffer1}, ForwardSizedView{buffer2}, ForwardSizedView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::forward_range<View>);
+    static_assert(std::ranges::forward_range<View>);
     static_assert(!std::ranges::bidirectional_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -259,7 +255,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{BidiNonCommonView{buffer1}, ForwardSizedView{buffer2}, ForwardSizedView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::forward_range<View>);
+    static_assert(std::ranges::forward_range<View>);
     static_assert(!std::ranges::bidirectional_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -268,7 +264,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{ForwardSizedView{buffer1}, ForwardSizedView{buffer2}, ForwardSizedView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::forward_range<View>);
+    static_assert(std::ranges::forward_range<View>);
     static_assert(!std::ranges::bidirectional_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -277,7 +273,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{ForwardSizedNonCommon{buffer1}, ForwardSizedView{buffer2}, ForwardSizedView{buffer3}};
     using View = decltype(v);
-    // static_assert(std::ranges::forward_range<View>);
+    static_assert(std::ranges::forward_range<View>);
     static_assert(!std::ranges::bidirectional_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(std::ranges::sized_range<View>);
@@ -286,7 +282,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{InputCommonView{buffer1}, ForwardSizedView{buffer2}, ForwardSizedView{buffer3}};
     using View = decltype(v);
-    //  static_assert(std::ranges::input_range<View>);
+    static_assert(std::ranges::input_range<View>);
     static_assert(!std::ranges::forward_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -295,7 +291,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{InputCommonView{buffer1}, InputCommonView{buffer2}, InputCommonView{buffer3}};
     using View = decltype(v);
-    //   static_assert(std::ranges::input_range<View>);
+    static_assert(std::ranges::input_range<View>);
     static_assert(!std::ranges::forward_range<View>);
     static_assert(std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);
@@ -304,7 +300,7 @@ void testConceptTuple() {
   {
     std::ranges::zip_view v{InputNonCommonView{buffer1}, InputCommonView{buffer2}, InputCommonView{buffer3}};
     using View = decltype(v);
-    //   static_assert(std::ranges::input_range<View>);
+    static_assert(std::ranges::input_range<View>);
     static_assert(!std::ranges::forward_range<View>);
     static_assert(!std::ranges::common_range<View>);
     static_assert(!std::ranges::sized_range<View>);

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_copy.pass.cpp
new file mode 100644
index 0000000000000..d32bfe45a7784
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_copy.pass.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... UTypes>
+// constexpr const tuple& operator=(const tuple<UTypes...>& u) const;
+//
+// Constraints:
+// - sizeof...(Types) equals sizeof...(UTypes) and
+// - (is_assignable_v<const Types&, const UTypes&> && ...) is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+// test constraints
+
+// sizeof...(Types) equals sizeof...(UTypes)
+static_assert(std::is_assignable_v<const std::tuple<int&>&, const std::tuple<long&>&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int&>&, const std::tuple<long&>&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&>&, const std::tuple<long&, long&>&>);
+
+// (is_assignable_v<const Types&, const UTypes&> && ...) is true
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstCopyAssign>>&, 
+                                   const std::tuple<ConstCopyAssign>&>);
+
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstCopyAssign>, ConstCopyAssign>&,
+                                   const std::tuple<ConstCopyAssign, ConstCopyAssign>&>);
+
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<ConstCopyAssign>, CopyAssign>&,
+                                    const std::tuple<ConstCopyAssign, CopyAssign>&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    long j1 = 3;
+    long j2 = 4;
+    const std::tuple<int&, int&> t1{i1, i2};
+    const std::tuple<long&, long&> t2{j1, j2};
+    t2 = t1;
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // user defined const copy assignment
+  {
+    const std::tuple<ConstCopyAssign> t1{1};
+    const std::tuple<AssignableFrom<ConstCopyAssign>> t2{2};
+    t2 = t1;
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::tuple<TracedAssignment> t1{};
+    const std::tuple<AssignableFrom<TracedAssignment>> t2{};
+    t2 = t1;
+    assert(std::get<0>(t2).v.constCopyAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_move.pass.cpp
new file mode 100644
index 0000000000000..41dfe5424cbe5
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_convert_move.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... UTypes>
+// constexpr const tuple& operator=(tuple<UTypes...>&& u) const;
+//
+// Constraints:
+// - sizeof...(Types) equals sizeof...(UTypes) and
+// - (is_assignable_v<const Types&, UTypes> && ...) is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+// test constraints
+
+// sizeof...(Types) equals sizeof...(UTypes)
+static_assert(std::is_assignable_v<const std::tuple<int&>&, std::tuple<long&>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int&>&, std::tuple<long&>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&>&, std::tuple<long&, long&>&&>);
+
+// (is_assignable_v<const Types&, UTypes&&> && ...) is true
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstMoveAssign>>&, std::tuple<ConstMoveAssign>&&>);
+
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstMoveAssign>, ConstMoveAssign>&,
+                                   std::tuple<ConstMoveAssign, ConstMoveAssign>&&>);
+
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<ConstMoveAssign>, AssignableFrom<MoveAssign>>&,
+                                    std::tuple<ConstMoveAssign, MoveAssign>&&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    long j1 = 3;
+    long j2 = 4;
+    std::tuple<int&, int&> t1{i1, i2};
+    const std::tuple<long&, long&> t2{j1, j2};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // user defined const copy assignment
+  {
+    std::tuple<ConstMoveAssign> t1{1};
+    const std::tuple<AssignableFrom<ConstMoveAssign>> t2{2};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::tuple<TracedAssignment> t1{};
+    const std::tuple<AssignableFrom<TracedAssignment>> t2{};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).v.constMoveAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_copy.pass.cpp
new file mode 100644
index 0000000000000..38449553bd591
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_copy.pass.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// constexpr const tuple& operator=(const tuple&) const;
+//
+// Constraints: (is_copy_assignable_v<const Types> && ...) is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// test constraints
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+static_assert(!std::is_assignable_v<const std::tuple<int>&, const std::tuple<int>&>);
+static_assert(std::is_assignable_v<const std::tuple<int&>&, const std::tuple<int&>&>);
+static_assert(std::is_assignable_v<const std::tuple<int&, int&>&, const std::tuple<int&, int&>&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int>&, const std::tuple<int&, int>&>);
+static_assert(std::is_assignable_v<const std::tuple<ConstCopyAssign>&, const std::tuple<ConstCopyAssign>&>);
+static_assert(!std::is_assignable_v<const std::tuple<CopyAssign>&, const std::tuple<CopyAssign>&>);
+static_assert(!std::is_assignable_v<const std::tuple<ConstMoveAssign>&, const std::tuple<ConstMoveAssign>&>);
+static_assert(!std::is_assignable_v<const std::tuple<MoveAssign>&, const std::tuple<MoveAssign>&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    double d1 = 3.0;
+    double d2 = 5.0;
+    const std::tuple<int&, double&> t1{i1, d1};
+    const std::tuple<int&, double&> t2{i2, d2};
+    t2 = t1;
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 3.0);
+  }
+
+  // user defined const copy assignment
+  {
+    const std::tuple<ConstCopyAssign> t1{1};
+    const std::tuple<ConstCopyAssign> t2{2};
+    t2 = t1;
+    assert(std::get<0>(t2).val == 1);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::tuple<TracedAssignment, const TracedAssignment> t1{};
+    const std::tuple<TracedAssignment, const TracedAssignment> t2{};
+    t2 = t1;
+    assert(std::get<0>(t2).constCopyAssign == 1);
+    assert(std::get<1>(t2).constCopyAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_move.pass.cpp
new file mode 100644
index 0000000000000..aeb71a56c2c43
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_move.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// constexpr const tuple& operator=(tuple&&) const;
+//
+// Constraints: (is_assignable_v<const Types&, Types> && ...) is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// test constraints
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "types.h"
+
+static_assert(!std::is_assignable_v<const std::tuple<int>&, std::tuple<int>&&>);
+static_assert(std::is_assignable_v<const std::tuple<int&>&, std::tuple<int&>&&>);
+static_assert(std::is_assignable_v<const std::tuple<int&, int&>&, std::tuple<int&, int&>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int>&, std::tuple<int&, int>&&>);
+
+// this is fallback to tuple's const copy assignment
+static_assert(std::is_assignable_v<const std::tuple<ConstCopyAssign>&, std::tuple<ConstCopyAssign>&&>);
+
+static_assert(!std::is_assignable_v<const std::tuple<CopyAssign>&, std::tuple<CopyAssign>&&>);
+static_assert(std::is_assignable_v<const std::tuple<ConstMoveAssign>&, std::tuple<ConstMoveAssign>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<MoveAssign>&, std::tuple<MoveAssign>&&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    double d1 = 3.0;
+    double d2 = 5.0;
+    std::tuple<int&, double&> t1{i1, d1};
+    const std::tuple<int&, double&> t2{i2, d2};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 3.0);
+  }
+
+  // user defined const move assignment
+  {
+    std::tuple<ConstMoveAssign> t1{1};
+    const std::tuple<ConstMoveAssign> t2{2};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).val == 1);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::tuple<TracedAssignment, const TracedAssignment> t1{};
+    const std::tuple<TracedAssignment, const TracedAssignment> t2{};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).constMoveAssign == 1);
+    assert(std::get<1>(t2).constCopyAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_copy.pass.cpp
new file mode 100644
index 0000000000000..67d16df26fbbc
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_copy.pass.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template<class U1, class U2>
+// constexpr const tuple& operator=(const pair<U1, U2>& u) const;
+//
+// - sizeof...(Types) is 2,
+// - is_assignable_v<const T1&, const U1&> is true, and
+// - is_assignable_v<const T2&, const U2&> is true
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+#include "types.h"
+
+// test constraints
+
+// sizeof...(Types) != 2,
+static_assert(std::is_assignable_v<const std::tuple<int&, int&>&, const std::pair<int&, int&>&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&>&, const std::pair<int&, int&>&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int&, int&>&, const std::pair<int&, int&>&>);
+
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstCopyAssign>, ConstCopyAssign>&,
+                                   const std::pair<ConstCopyAssign, ConstCopyAssign>&>);
+
+// is_assignable_v<const T1&, const U1&> is false
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<CopyAssign>, ConstCopyAssign>&,
+                                    const std::pair<CopyAssign, ConstCopyAssign>&>);
+
+// is_assignable_v<const T2&, const U2&> is false
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<ConstCopyAssign>, AssignableFrom<CopyAssign>>&,
+                                    const std::tuple<ConstCopyAssign, CopyAssign>&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    long j1 = 3;
+    long j2 = 4;
+    const std::pair<int&, int&> t1{i1, i2};
+    const std::tuple<long&, long&> t2{j1, j2};
+    t2 = t1;
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // user defined const copy assignment
+  {
+    const std::pair<ConstCopyAssign, ConstCopyAssign> t1{1, 2};
+    const std::tuple<AssignableFrom<ConstCopyAssign>, ConstCopyAssign> t2{3, 4};
+    t2 = t1;
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2).val == 2);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::pair<TracedAssignment, TracedAssignment> t1{};
+    const std::tuple<AssignableFrom<TracedAssignment>, AssignableFrom<TracedAssignment>> t2{};
+    t2 = t1;
+    assert(std::get<0>(t2).v.constCopyAssign == 1);
+    assert(std::get<1>(t2).v.constCopyAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_move.pass.cpp
new file mode 100644
index 0000000000000..44dcb7022973b
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair_move.pass.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template<class U1, class U2>
+// constexpr const tuple& operator=(pair<U1, U2>&& u) const;
+//
+// - sizeof...(Types) is 2,
+// - is_assignable_v<const T1&, U1> is true, and
+// - is_assignable_v<const T2&, U2> is true
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+#include "types.h"
+
+// test constraints
+
+// sizeof...(Types) != 2,
+static_assert(std::is_assignable_v<const std::tuple<int&, int&>&, std::pair<int&, int&>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&>&, std::pair<int&, int&>&&>);
+static_assert(!std::is_assignable_v<const std::tuple<int&, int&, int&>&, std::pair<int&, int&>&&>);
+
+static_assert(std::is_assignable_v<const std::tuple<AssignableFrom<ConstMoveAssign>, ConstMoveAssign>&,
+                                   std::pair<ConstMoveAssign, ConstMoveAssign>&&>);
+
+// is_assignable_v<const T1&, U1> is false
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<MoveAssign>, ConstMoveAssign>&,
+                                    std::pair<MoveAssign, ConstMoveAssign>&&>);
+
+// is_assignable_v<const T2&, U2> is false
+static_assert(!std::is_assignable_v<const std::tuple<AssignableFrom<ConstMoveAssign>, AssignableFrom<MoveAssign>>&,
+                                    std::tuple<ConstMoveAssign, MoveAssign>&&>);
+
+constexpr bool test() {
+  // reference types
+  {
+    int i1 = 1;
+    int i2 = 2;
+    long j1 = 3;
+    long j2 = 4;
+    std::pair<int&, int&> t1{i1, i2};
+    const std::tuple<long&, long&> t2{j1, j2};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2) == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // user defined const copy assignment
+  {
+    std::pair<ConstMoveAssign, ConstMoveAssign> t1{1, 2};
+    const std::tuple<AssignableFrom<ConstMoveAssign>, ConstMoveAssign> t2{3, 4};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2).val == 2);
+  }
+
+  // make sure the right assignment operator of the type in the tuple is used
+  {
+    std::pair<TracedAssignment, TracedAssignment> t1{};
+    const std::tuple<AssignableFrom<TracedAssignment>, AssignableFrom<TracedAssignment>> t2{};
+    t2 = std::move(t1);
+    assert(std::get<0>(t2).v.constMoveAssign == 1);
+    assert(std::get<1>(t2).v.constMoveAssign == 1);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/types.h b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/types.h
new file mode 100644
index 0000000000000..b5593d9e3723b
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/types.h
@@ -0,0 +1,138 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef LIBCXX_TEST_STD_UTILITIES_TUPLE_ASSIGN_TYPES_H
+#define LIBCXX_TEST_STD_UTILITIES_TUPLE_ASSIGN_TYPES_H
+
+#include "test_allocator.h"
+#include <type_traits>
+
+struct CopyAssign {
+  int val;
+
+  constexpr CopyAssign() = default;
+  constexpr CopyAssign(int v) : val(v) {}
+
+  constexpr CopyAssign& operator=(const CopyAssign&) = default;
+
+  constexpr const CopyAssign& operator=(const CopyAssign&) const = delete;
+  constexpr CopyAssign& operator=(CopyAssign&&) = delete;
+  constexpr const CopyAssign& operator=(CopyAssign&&) const = delete;
+};
+
+struct ConstCopyAssign {
+  mutable int val;
+
+  constexpr ConstCopyAssign() = default;
+  constexpr ConstCopyAssign(int v) : val(v) {}
+
+  constexpr const ConstCopyAssign& operator=(const ConstCopyAssign& other) const {
+    val = other.val;
+    return *this;
+  }
+
+  constexpr ConstCopyAssign& operator=(const ConstCopyAssign&) = delete;
+  constexpr ConstCopyAssign& operator=(ConstCopyAssign&&) = delete;
+  constexpr const ConstCopyAssign& operator=(ConstCopyAssign&&) const = delete;
+};
+
+struct MoveAssign {
+  int val;
+
+  constexpr MoveAssign() = default;
+  constexpr MoveAssign(int v) : val(v) {}
+
+  constexpr MoveAssign& operator=(MoveAssign&&) = default;
+
+  constexpr MoveAssign& operator=(const MoveAssign&) = delete;
+  constexpr const MoveAssign& operator=(const MoveAssign&) const = delete;
+  constexpr const MoveAssign& operator=(MoveAssign&&) const = delete;
+};
+
+struct ConstMoveAssign {
+  mutable int val;
+
+  constexpr ConstMoveAssign() = default;
+  constexpr ConstMoveAssign(int v) : val(v) {}
+
+  constexpr const ConstMoveAssign& operator=(ConstMoveAssign&& other) const {
+    val = other.val;
+    return *this;
+  }
+
+  constexpr ConstMoveAssign& operator=(const ConstMoveAssign&) = delete;
+  constexpr const ConstMoveAssign& operator=(const ConstMoveAssign&) const = delete;
+  constexpr ConstMoveAssign& operator=(ConstMoveAssign&&) = delete;
+};
+
+template <class T>
+struct AssignableFrom {
+  T v;
+
+  constexpr AssignableFrom() = default;
+
+  template <class U>
+  constexpr AssignableFrom(U&& u)
+    requires std::is_constructible_v<T, U&&>
+  : v(std::forward<U>(u)) {}
+
+  constexpr AssignableFrom& operator=(const T& t)
+    requires std::is_copy_assignable_v<T>
+  {
+    v = t;
+    return *this;
+  }
+
+  constexpr AssignableFrom& operator=(T&& t)
+    requires std::is_move_assignable_v<T>
+  {
+    v = std::move(t);
+    return *this;
+  }
+
+  constexpr const AssignableFrom& operator=(const T& t) const
+    requires std::is_assignable_v<const T&, const T&>
+  {
+    v = t;
+    return *this;
+  }
+
+  constexpr const AssignableFrom& operator=(T&& t) const
+    requires std::is_assignable_v<const T&, T&&>
+  {
+    v = std::move(t);
+    return *this;
+  }
+};
+
+struct TracedAssignment {
+  int copyAssign = 0;
+  mutable int constCopyAssign = 0;
+  int moveAssign = 0;
+  mutable int constMoveAssign = 0;
+
+  constexpr TracedAssignment() = default;
+
+  constexpr TracedAssignment& operator=(const TracedAssignment&) {
+    copyAssign++;
+    return *this;
+  }
+  constexpr const TracedAssignment& operator=(const TracedAssignment&) const {
+    constCopyAssign++;
+    return *this;
+  }
+  constexpr TracedAssignment& operator=(TracedAssignment&&) {
+    moveAssign++;
+    return *this;
+  }
+  constexpr const TracedAssignment& operator=(TracedAssignment&&) const {
+    constMoveAssign++;
+    return *this;
+  }
+};
+
+#endif

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_const_move_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_const_move_pair.pass.cpp
new file mode 100644
index 0000000000000..10b7e2a5dfe13
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_const_move_pair.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class Alloc, class U1, class U2>
+// constexpr explicit(see below)
+//   tuple<Types...>::tuple(allocator_arg_t, const Alloc& a, const pair<U1,
+//   U2>&& u);
+
+// Constraints:
+// - sizeof...(Types) is 2 and
+// - is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// - is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "convert_types.h"
+#include "test_allocator.h"
+
+// test constraints
+// sizeof...(Types) == 2
+static_assert(std::is_constructible_v<std::tuple<ConstMove, int>, std::allocator_arg_t, test_allocator<int>,
+                                      const std::pair<ConstMove, int>&&>);
+
+static_assert(!std::is_constructible_v< std::tuple<ConstMove>, std::allocator_arg_t, test_allocator<int>,
+                                        const std::pair<ConstMove, int>&&>);
+
+static_assert(!std::is_constructible_v< std::tuple<ConstMove, int, int>, std::allocator_arg_t, test_allocator<int>,
+                                        const std::pair<ConstMove, int>&&>);
+
+// test constraints
+// is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+static_assert(std::is_constructible_v<std::tuple<int, int>, std::allocator_arg_t, test_allocator<int>,
+                                      const std::pair<int, int>&&>);
+
+static_assert(!std::is_constructible_v< std::tuple<NoConstructorFromInt, int>, std::allocator_arg_t,
+                                        test_allocator<int>, const std::pair<int, int>&&>);
+
+static_assert(!std::is_constructible_v< std::tuple<int, NoConstructorFromInt>, std::allocator_arg_t,
+                                        test_allocator<int>, const std::pair<int, int>&&>);
+
+static_assert(!std::is_constructible_v< std::tuple<NoConstructorFromInt, NoConstructorFromInt>, std::allocator_arg_t,
+                                        test_allocator<int>, const std::pair<int, int>&&>);
+
+// test: The expression inside explicit is equivalent to:
+// !is_convertible_v<decltype(get<0>(FWD(u))), T0> ||
+// !is_convertible_v<decltype(get<1>(FWD(u))), T1>
+static_assert(
+    ImplicitlyConstructible< std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>, std::allocator_arg_t,
+                             test_allocator<int>, const std::pair<ConstMove, ConstMove>&&>);
+
+static_assert(
+    !ImplicitlyConstructible<std::tuple<ConvertibleFrom<ConstMove>, ExplicitConstructibleFrom<ConstMove>>,
+                             std::allocator_arg_t, test_allocator<int>, const std::pair<ConstMove, ConstMove>&&>);
+
+static_assert(
+    !ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>,
+                             std::allocator_arg_t, test_allocator<int>, const std::pair<ConstMove, ConstMove>&&>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  {
+    const std::pair<ConstMove, int> p{1, 2};
+    std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<int>> t = {std::allocator_arg, test_allocator<int>{},
+                                                                      std::move(p)};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  {
+    const std::pair<ConstMove, int> p{1, 2};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>, ExplicitConstructibleFrom<int>> t{
+        std::allocator_arg, test_allocator<int>{}, std::move(p)};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  // non const overload should be called
+  {
+    const std::pair<TracedCopyMove, TracedCopyMove> p;
+    std::tuple<ConvertibleFrom<TracedCopyMove>, TracedCopyMove> t = {std::allocator_arg, test_allocator<int>{},
+                                                                     std::move(p)};
+    assert(constMoveCtrCalled(std::get<0>(t).v));
+    assert(constMoveCtrCalled(std::get<1>(t)));
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_const_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_const_move.pass.cpp
new file mode 100644
index 0000000000000..a24fee7e79b58
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_const_move.pass.cpp
@@ -0,0 +1,156 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template<class Alloc, class... UTypes>
+//   constexpr explicit(see below)
+//     tuple<Types...>::tuple(allocator_arg_t, const Alloc& a,
+//     const tuple<UTypes...>&&);
+//
+// Constraints:
+//  sizeof...(Types) equals sizeof...(UTypes) &&
+//  (is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...) is true &&
+//  (
+//	sizeof...(Types) is not 1 ||
+//	(
+//		!is_convertible_v<decltype(u), T> &&
+//		!is_constructible_v<T, decltype(u)> &&
+//		!is_same_v<T, U>
+//	)
+//  )
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+
+#include "convert_types.h"
+#include "test_allocator.h"
+#include "test_macros.h"
+
+// test: The expression inside explicit is equivalent to:
+// !(is_convertible_v<decltype(get<I>(FWD(u))), Types> && ...)
+static_assert(ImplicitlyConstructible< std::tuple<ConvertibleFrom<ConstMove>>, std::allocator_arg_t,
+                                       const test_allocator<int>&, const std::tuple<ConstMove>&&>);
+
+static_assert(
+    ImplicitlyConstructible< std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>, std::allocator_arg_t,
+                             const test_allocator<int>&, const std::tuple<ConstMove, ConstMove>&&>);
+
+static_assert(!ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<ConstMove>>, std::allocator_arg_t,
+                                       const test_allocator<int>&, const std::tuple<ConstMove>&&>);
+
+static_assert(!ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>,
+                                       std::allocator_arg_t, const test_allocator<int>&,
+                                       const std::tuple<ConstMove, ConstMove>&&>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  // sizeof...(Types) == 1
+  {
+    const std::tuple<ConstMove> t1{1};
+    std::tuple<ConvertibleFrom<ConstMove>> t2 = {std::allocator_arg, test_allocator<int>{}, std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test implicit conversions.
+  // sizeof...(Types) > 1
+  {
+    const std::tuple<ConstMove, int> t1{1, 2};
+    std::tuple<ConvertibleFrom<ConstMove>, int> t2 = {std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) == 1
+  {
+    const std::tuple<ConstMove> t1{1};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>> t2{std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) > 1
+  {
+    const std::tuple<ConstMove, int> t1{1, 2};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>, int> t2{std::allocator_arg_t{}, test_allocator<int>{},
+                                                             std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test constraints
+
+  // sizeof...(Types) != sizeof...(UTypes)
+  static_assert(!std::is_constructible_v<std::tuple<int, int>, std::allocator_arg_t, const test_allocator<int>&,
+                                         const std::tuple<int>&&>);
+  static_assert(!std::is_constructible_v<std::tuple<int, int, int>, std::allocator_arg_t, const test_allocator<int>&,
+                                         const std::tuple<int, int>&&>);
+
+  // !(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
+  static_assert(!std::is_constructible_v< std::tuple<int, NoConstructorFromInt>, std::allocator_arg_t,
+                                          const test_allocator<int>&, const std::tuple<int, int>&&>);
+
+  // sizeof...(Types) == 1 && other branch of "||" satisfied
+  {
+    const std::tuple<TracedCopyMove> t1{};
+    std::tuple<ConvertibleFrom<TracedCopyMove>> t2{std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(constMoveCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) == 1 && is_same_v<T, U>
+  {
+    const std::tuple<TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove> t2{std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(!constMoveCtrCalled(std::get<0>(t2)));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) != 1
+  {
+    const std::tuple<TracedCopyMove, TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove, TracedCopyMove> t2{std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(constMoveCtrCalled(std::get<0>(std::move(t2))));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // These two test points cause gcc to ICE
+#if !defined(TEST_COMPILER_GCC)
+  // sizeof...(Types) == 1 && is_convertible_v<decltype(u), T>
+  {
+    const std::tuple<CvtFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<CvtFromTupleRef>> t2{std::allocator_arg_t{}, test_allocator<int>{}, std::move(t1)};
+    assert(!constMoveCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) == 1 && is_constructible_v<decltype(u), T>
+  {
+    const std::tuple<ExplicitCtrFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<ExplicitCtrFromTupleRef>> t2{std::allocator_arg_t{}, test_allocator<int>{},
+                                                            std::move(t1)};
+    assert(!constMoveCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+#endif
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_copy.pass.cpp
index 681e2e70c5f1d..4ed413477a8c2 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_copy.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_copy.pass.cpp
@@ -11,6 +11,7 @@
 // template <class... Types> class tuple;
 
 // template <class Alloc, class... UTypes>
+// constexpr                                        // since c++20
 //   tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&);
 
 // UNSUPPORTED: c++03
@@ -21,6 +22,7 @@
 
 #include "test_macros.h"
 #include "allocators.h"
+#include "test_allocator.h"
 #include "../alloc_first.h"
 #include "../alloc_last.h"
 
@@ -34,6 +36,15 @@ struct Implicit {
   Implicit(int x) : value(x) {}
 };
 
+#if _LIBCPP_STD_VER > 17
+constexpr bool alloc_copy_constructor_is_constexpr() {
+  const std::tuple<int> t1 = 1;
+  std::tuple<int> t2 = {std::allocator_arg, test_allocator<int>{}, t1};
+  assert(std::get<0>(t2) == 1);
+  return true;
+}
+#endif
+
 int main(int, char**)
 {
     {
@@ -95,5 +106,8 @@ int main(int, char**)
         std::tuple<long long> t0(derived, A1<int>(), from);
     }
 
+#if _LIBCPP_STD_VER > 17
+    static_assert(alloc_copy_constructor_is_constexpr());
+#endif
     return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_move.pass.cpp
index b823e8f2afe91..1423c6174339d 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_move.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_move.pass.cpp
@@ -22,6 +22,7 @@
 
 #include "test_macros.h"
 #include "allocators.h"
+#include "test_allocator.h"
 #include "../alloc_first.h"
 #include "../alloc_last.h"
 
@@ -50,6 +51,15 @@ struct Implicit {
   Implicit(int x) : value(x) {}
 };
 
+#if _LIBCPP_STD_VER > 17
+constexpr bool alloc_move_constructor_is_constexpr() {
+  std::tuple<int> t1 = 1;
+  std::tuple<int> t2 = {std::allocator_arg, test_allocator<int>{}, std::move(t1)};
+  assert(std::get<0>(t2) == 1);
+  return true;
+}
+#endif
+
 int main(int, char**)
 {
     {
@@ -109,5 +119,9 @@ int main(int, char**)
         std::tuple<long long> t0(derived, A1<int>(), std::move(from));
     }
 
+#if _LIBCPP_STD_VER > 17
+    static_assert(alloc_move_constructor_is_constexpr());
+#endif
+
     return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_non_const_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_non_const_copy.pass.cpp
new file mode 100644
index 0000000000000..5c8249710f274
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_convert_non_const_copy.pass.cpp
@@ -0,0 +1,154 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template<class Alloc, class... UTypes>
+//   constexpr explicit(see below)
+//     tuple<Types...>::tuple(allocator_arg_t, const Alloc& a,
+//     tuple<UTypes...>&);
+//
+// Constraints:
+//  sizeof...(Types) equals sizeof...(UTypes) &&
+//  (is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...) is true &&
+//  (
+//	sizeof...(Types) is not 1 ||
+//	(
+//		!is_convertible_v<decltype(u), T> &&
+//		!is_constructible_v<T, decltype(u)> &&
+//		!is_same_v<T, U>
+//	)
+//  )
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+
+#include "convert_types.h"
+#include "test_allocator.h"
+#include "test_macros.h"
+
+// test: The expression inside explicit is equivalent to:
+// !(is_convertible_v<decltype(get<I>(FWD(u))), Types> && ...)
+static_assert(ImplicitlyConstructible< std::tuple<ConvertibleFrom<MutableCopy>>, std::allocator_arg_t,
+                                       const test_allocator<int>&, std::tuple<MutableCopy>&>);
+
+static_assert(
+    ImplicitlyConstructible< std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>,
+                             std::allocator_arg_t, const test_allocator<int>&, std::tuple<MutableCopy, MutableCopy>&>);
+
+static_assert(!ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<MutableCopy>>, std::allocator_arg_t,
+                                       const test_allocator<int>&, std::tuple<MutableCopy>&>);
+
+static_assert(
+    !ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>,
+                             std::allocator_arg_t, const test_allocator<int>&, std::tuple<MutableCopy, MutableCopy>&>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  // sizeof...(Types) == 1
+  {
+    std::tuple<MutableCopy> t1{1};
+    std::tuple<ConvertibleFrom<MutableCopy>> t2 = {std::allocator_arg, test_allocator<int>{}, t1};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test implicit conversions.
+  // sizeof...(Types) > 1
+  {
+    std::tuple<MutableCopy, int> t1{1, 2};
+    std::tuple<ConvertibleFrom<MutableCopy>, int> t2 = {std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) == 1
+  {
+    std::tuple<MutableCopy> t1{1};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) > 1
+  {
+    std::tuple<MutableCopy, int> t1{1, 2};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>, int> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // test constraints
+
+  // sizeof...(Types) != sizeof...(UTypes)
+  static_assert(!std::is_constructible_v<std::tuple<int, int>, std::allocator_arg_t, const test_allocator<int>&,
+                                         std::tuple<int>&>);
+  static_assert(!std::is_constructible_v<std::tuple<int, int, int>, std::allocator_arg_t, const test_allocator<int>&,
+                                         std::tuple<int, int>&>);
+
+  // !(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
+  static_assert(!std::is_constructible_v< std::tuple<int, NoConstructorFromInt>, std::allocator_arg_t,
+                                          const test_allocator<int>&, std::tuple<int, int>&>);
+
+  // sizeof...(Types) == 1 && other branch of "||" satisfied
+  {
+    std::tuple<TracedCopyMove> t1{};
+    std::tuple<ConvertibleFrom<TracedCopyMove>> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(nonConstCopyCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) == 1 && is_same_v<T, U>
+  {
+    std::tuple<TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2)));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) != 1
+  {
+    std::tuple<TracedCopyMove, TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove, TracedCopyMove> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(nonConstCopyCtrCalled(std::get<0>(t2)));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // These two test points cause gcc to ICE
+#if !defined(TEST_COMPILER_GCC)
+  // sizeof...(Types) == 1 && is_convertible_v<decltype(u), T>
+  {
+    std::tuple<CvtFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<CvtFromTupleRef>> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+
+  // sizeof...(Types) == 1 && is_constructible_v<decltype(u), T>
+  {
+    std::tuple<ExplicitCtrFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<ExplicitCtrFromTupleRef>> t2{std::allocator_arg_t{}, test_allocator<int>{}, t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2).v));
+    assert(std::get<0>(t2).alloc_constructed);
+  }
+#endif
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_non_const_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_non_const_pair.pass.cpp
new file mode 100644
index 0000000000000..86761b5044fb4
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/alloc_non_const_pair.pass.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class Alloc, class U1, class U2>
+// constexpr explicit(see below)
+//	 tuple<Types...>::tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&
+// u);
+
+// Constraints:
+// - sizeof...(Types) is 2 and
+// - is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// - is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "convert_types.h"
+#include "test_allocator.h"
+
+// test constraints
+// sizeof...(Types) == 2
+static_assert(std::is_constructible_v<std::tuple<MutableCopy, int>, std::allocator_arg_t, test_allocator<int>,
+                                      std::pair<MutableCopy, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<MutableCopy>, std::allocator_arg_t, test_allocator<int>,
+                                        std::pair<MutableCopy, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<MutableCopy, int, int>, std::allocator_arg_t, test_allocator<int>,
+                                        std::pair<MutableCopy, int>&>);
+
+// test constraints
+// is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+static_assert(
+    std::is_constructible_v<std::tuple<int, int>, std::allocator_arg_t, test_allocator<int>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<NoConstructorFromInt, int>, std::allocator_arg_t,
+                                        test_allocator<int>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<int, NoConstructorFromInt>, std::allocator_arg_t,
+                                        test_allocator<int>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<NoConstructorFromInt, NoConstructorFromInt>, std::allocator_arg_t,
+                                        test_allocator<int>, std::pair<int, int>&>);
+
+// test: The expression inside explicit is equivalent to:
+// !is_convertible_v<decltype(get<0>(FWD(u))), T0> ||
+// !is_convertible_v<decltype(get<1>(FWD(u))), T1>
+static_assert(ImplicitlyConstructible<std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>,
+                                      std::allocator_arg_t, test_allocator<int>, std::pair<MutableCopy, MutableCopy>&>);
+
+static_assert(
+    !ImplicitlyConstructible<std::tuple<ConvertibleFrom<MutableCopy>, ExplicitConstructibleFrom<MutableCopy>>,
+                             std::allocator_arg_t, test_allocator<int>, std::pair<MutableCopy, MutableCopy>&>);
+
+static_assert(
+    !ImplicitlyConstructible<std::tuple<ExplicitConstructibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>,
+                             std::allocator_arg_t, test_allocator<int>, std::pair<MutableCopy, MutableCopy>&>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  {
+    std::pair<MutableCopy, int> p{1, 2};
+    std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<int>> t = {std::allocator_arg, test_allocator<int>{}, p};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  // test explicit conversions.
+  {
+    std::pair<MutableCopy, int> p{1, 2};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>, ExplicitConstructibleFrom<int>> t{std::allocator_arg,
+                                                                                         test_allocator<int>{}, p};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  // non const overload should be called
+  {
+    std::pair<TracedCopyMove, TracedCopyMove> p;
+    std::tuple<ConvertibleFrom<TracedCopyMove>, TracedCopyMove> t = {std::allocator_arg, test_allocator<int>{}, p};
+    assert(nonConstCopyCtrCalled(std::get<0>(t).v));
+    assert(nonConstCopyCtrCalled(std::get<1>(t)));
+    assert(std::get<0>(t).alloc_constructed);
+    assert(std::get<1>(t).alloc_constructed);
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/const_move_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/const_move_pair.pass.cpp
new file mode 100644
index 0000000000000..5c5b20d6d755d
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/const_move_pair.pass.cpp
@@ -0,0 +1,93 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class U1, class U2>
+// constexpr explicit(see below) tuple<Types...>::tuple(const pair<U1, U2>&& u);
+
+// Constraints:
+// - sizeof...(Types) is 2 and
+// - is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// - is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "convert_types.h"
+
+// test constraints
+// sizeof...(Types) == 2
+static_assert(std::is_constructible_v<std::tuple<ConstMove, int>, const std::pair<ConstMove, int>&&>);
+
+static_assert(!std::is_constructible_v<std::tuple<ConstMove>, const std::pair<ConstMove, int>&&>);
+
+static_assert(!std::is_constructible_v<std::tuple<ConstMove, int, int>, const std::pair<ConstMove, int>&&>);
+
+// test constraints
+// is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+static_assert(std::is_constructible_v<std::tuple<int, int>, const std::pair<int, int>&&>);
+
+static_assert(!std::is_constructible_v<std::tuple<NoConstructorFromInt, int>, const std::pair<int, int>&&>);
+
+static_assert(!std::is_constructible_v<std::tuple<int, NoConstructorFromInt>, const std::pair<int, int>&&>);
+
+static_assert(
+    !std::is_constructible_v< std::tuple<NoConstructorFromInt, NoConstructorFromInt>, const std::pair<int, int>&&>);
+
+// test: The expression inside explicit is equivalent to:
+// !is_convertible_v<decltype(get<0>(FWD(u))), T0> ||
+// !is_convertible_v<decltype(get<1>(FWD(u))), T1>
+static_assert(std::is_convertible_v<const std::pair<ConstMove, ConstMove>&&,
+                                    std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>>);
+
+static_assert(!std::is_convertible_v<const std::pair<ConstMove, ConstMove>&&,
+                                     std::tuple<ExplicitConstructibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>>);
+
+static_assert(!std::is_convertible_v<const std::pair<ConstMove, ConstMove>&&,
+                                     std::tuple<ConvertibleFrom<ConstMove>, ExplicitConstructibleFrom<ConstMove>>>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  {
+    const std::pair<ConstMove, int> p{1, 2};
+    std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<int>> t = std::move(p);
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+  }
+
+  // test explicit conversions.
+  {
+    const std::pair<ConstMove, int> p{1, 2};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>, ExplicitConstructibleFrom<int>> t{std::move(p)};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+  }
+
+  // non const overload should be called
+  {
+    const std::pair<TracedCopyMove, TracedCopyMove> p;
+    std::tuple<ConvertibleFrom<TracedCopyMove>, TracedCopyMove> t = std::move(p);
+    assert(constMoveCtrCalled(std::get<0>(t).v));
+    assert(constMoveCtrCalled(std::get<1>(t)));
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_const_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_const_move.pass.cpp
new file mode 100644
index 0000000000000..30c11b3e22333
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_const_move.pass.cpp
@@ -0,0 +1,138 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class... UTypes>
+//   constexpr explicit(see below) tuple<Types>::tuple(const
+//   tuple<UTypes...>&&);
+//
+// Constraints:
+//  sizeof...(Types) equals sizeof...(UTypes) &&
+//  (is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...) is true &&
+//  (
+//	sizeof...(Types) is not 1 ||
+//	(
+//		!is_convertible_v<decltype(u), T> &&
+//		!is_constructible_v<T, decltype(u)> &&
+//		!is_same_v<T, U>
+//	)
+//  )
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+
+#include "convert_types.h"
+#include "test_macros.h"
+
+// test: The expression inside explicit is equivalent to:
+// !(is_convertible_v<decltype(get<I>(FWD(u))), Types> && ...)
+static_assert(std::is_convertible_v<const std::tuple<ConstMove>&&, std::tuple<ConvertibleFrom<ConstMove>>>);
+
+static_assert(std::is_convertible_v<const std::tuple<ConstMove, ConstMove>&&,
+                                    std::tuple<ConvertibleFrom<ConstMove>, ConvertibleFrom<ConstMove>>>);
+
+static_assert(
+    !std::is_convertible_v<const std::tuple<MutableCopy>&&, std::tuple<ExplicitConstructibleFrom<ConstMove>>>);
+
+static_assert(!std::is_convertible_v<const std::tuple<ConstMove, ConstMove>&&,
+                                     std::tuple<ConvertibleFrom<ConstMove>, ExplicitConstructibleFrom<ConstMove>>>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  // sizeof...(Types) == 1
+  {
+    const std::tuple<ConstMove> t1{1};
+    std::tuple<ConvertibleFrom<ConstMove>> t2 = std::move(t1);
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // test implicit conversions.
+  // sizeof...(Types) > 1
+  {
+    const std::tuple<ConstMove, int> t1{1, 2};
+    std::tuple<ConvertibleFrom<ConstMove>, int> t2 = std::move(t1);
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) == 1
+  {
+    const std::tuple<ConstMove> t1{1};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>> t2{std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) > 1
+  {
+    const std::tuple<ConstMove, int> t1{1, 2};
+    std::tuple<ExplicitConstructibleFrom<ConstMove>, int> t2{std::move(t1)};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // test constraints
+
+  // sizeof...(Types) != sizeof...(UTypes)
+  static_assert(!std::is_constructible_v<std::tuple<int, int>, const std::tuple<int>&&>);
+  static_assert(!std::is_constructible_v<std::tuple<int, int, int>, const std::tuple<int, int>&&>);
+
+  // !(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
+  static_assert(!std::is_constructible_v<std::tuple<int, NoConstructorFromInt>, const std::tuple<int, int>&&>);
+
+  // sizeof...(Types) == 1 && other branch of "||" satisfied
+  {
+    const std::tuple<TracedCopyMove> t1{};
+    std::tuple<ConvertibleFrom<TracedCopyMove>> t2{std::move(t1)};
+    assert(constMoveCtrCalled(std::get<0>(t2).v));
+  }
+
+  // sizeof...(Types) == 1 && is_same_v<T, U>
+  {
+    const std::tuple<TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove> t2{t1};
+    assert(!constMoveCtrCalled(std::get<0>(t2)));
+  }
+
+  // sizeof...(Types) != 1
+  {
+    const std::tuple<TracedCopyMove, TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove, TracedCopyMove> t2{std::move(t1)};
+    assert(constMoveCtrCalled(std::get<0>(t2)));
+  }
+
+  // These two test points cause gcc to ICE
+#if !defined(TEST_COMPILER_GCC)
+  // sizeof...(Types) == 1 && is_convertible_v<decltype(u), T>
+  {
+    const std::tuple<CvtFromConstTupleRefRef> t1{};
+    std::tuple<ConvertibleFrom<CvtFromConstTupleRefRef>> t2{std::move(t1)};
+    assert(!constMoveCtrCalled(std::get<0>(t2).v));
+  }
+
+  // sizeof...(Types) == 1 && is_constructible_v<decltype(u), T>
+  {
+    const std::tuple<ExplicitCtrFromConstTupleRefRef> t1{};
+    std::tuple<ConvertibleFrom<ExplicitCtrFromConstTupleRefRef>> t2{std::move(t1)};
+    assert(!constMoveCtrCalled(std::get<0>(t2).v));
+  }
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_non_const_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_non_const_copy.pass.cpp
new file mode 100644
index 0000000000000..bb2baba578704
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_non_const_copy.pass.cpp
@@ -0,0 +1,136 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class... UTypes>
+//   constexpr explicit(see below) tuple<Types>::tuple(tuple<UTypes...>&);
+//
+// Constraints:
+//  sizeof...(Types) equals sizeof...(UTypes) &&
+//  (is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...) is true &&
+//  (
+//	sizeof...(Types) is not 1 ||
+//	(
+//		!is_convertible_v<decltype(u), T> &&
+//		!is_constructible_v<T, decltype(u)> &&
+//		!is_same_v<T, U>
+//	)
+//  )
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+
+#include "convert_types.h"
+#include "test_macros.h"
+
+// test: The expression inside explicit is equivalent to:
+// !(is_convertible_v<decltype(get<I>(FWD(u))), Types> && ...)
+static_assert(std::is_convertible_v<std::tuple<MutableCopy>&, std::tuple<ConvertibleFrom<MutableCopy>>>);
+
+static_assert(std::is_convertible_v<std::tuple<MutableCopy, MutableCopy>&,
+                                    std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>>);
+
+static_assert(!std::is_convertible_v<std::tuple<MutableCopy>&, std::tuple<ExplicitConstructibleFrom<MutableCopy>>>);
+
+static_assert(!std::is_convertible_v<std::tuple<MutableCopy, MutableCopy>&,
+                                     std::tuple<ConvertibleFrom<MutableCopy>, ExplicitConstructibleFrom<MutableCopy>>>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  // sizeof...(Types) == 1
+  {
+    std::tuple<MutableCopy> t1{1};
+    std::tuple<ConvertibleFrom<MutableCopy>> t2 = t1;
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // test implicit conversions.
+  // sizeof...(Types) > 1
+  {
+    std::tuple<MutableCopy, int> t1{1, 2};
+    std::tuple<ConvertibleFrom<MutableCopy>, int> t2 = t1;
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) == 1
+  {
+    std::tuple<MutableCopy> t1{1};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>> t2{t1};
+    assert(std::get<0>(t2).v.val == 1);
+  }
+
+  // test explicit conversions.
+  // sizeof...(Types) > 1
+  {
+    std::tuple<MutableCopy, int> t1{1, 2};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>, int> t2{t1};
+    assert(std::get<0>(t2).v.val == 1);
+    assert(std::get<1>(t2) == 2);
+  }
+
+  // test constraints
+
+  // sizeof...(Types) != sizeof...(UTypes)
+  static_assert(!std::is_constructible_v<std::tuple<int, int>, std::tuple<int>&>);
+  static_assert(!std::is_constructible_v<std::tuple<int>, std::tuple<int, int>&>);
+  static_assert(!std::is_constructible_v<std::tuple<int, int, int>, std::tuple<int, int>&>);
+
+  // !(is_constructible_v<Types, decltype(get<I>(FWD(u)))> && ...)
+  static_assert(!std::is_constructible_v<std::tuple<int, NoConstructorFromInt>, std::tuple<int, int>&>);
+
+  // sizeof...(Types) == 1 && other branch of "||" satisfied
+  {
+    std::tuple<TracedCopyMove> t1{};
+    std::tuple<ConvertibleFrom<TracedCopyMove>> t2{t1};
+    assert(nonConstCopyCtrCalled(std::get<0>(t2).v));
+  }
+
+  // sizeof...(Types) == 1 && is_same_v<T, U>
+  {
+    std::tuple<TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove> t2{t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2)));
+  }
+
+  // sizeof...(Types) != 1
+  {
+    std::tuple<TracedCopyMove, TracedCopyMove> t1{};
+    std::tuple<TracedCopyMove, TracedCopyMove> t2{t1};
+    assert(nonConstCopyCtrCalled(std::get<0>(t2)));
+  }
+
+  // These two test points cause gcc to ICE
+#if !defined(TEST_COMPILER_GCC)
+  // sizeof...(Types) == 1 && is_convertible_v<decltype(u), T>
+  {
+    std::tuple<CvtFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<CvtFromTupleRef>> t2{t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2).v));
+  }
+
+  // sizeof...(Types) == 1 && is_constructible_v<decltype(u), T>
+  {
+    std::tuple<ExplicitCtrFromTupleRef> t1{};
+    std::tuple<ConvertibleFrom<ExplicitCtrFromTupleRef>> t2{t1};
+    assert(!nonConstCopyCtrCalled(std::get<0>(t2).v));
+  }
+#endif
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_types.h b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_types.h
new file mode 100644
index 0000000000000..0bdfec3cd4e86
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/convert_types.h
@@ -0,0 +1,218 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef LIBCXX_TEST_STD_UTILITIES_TUPLE_CNSTR_TYPES_H
+#define LIBCXX_TEST_STD_UTILITIES_TUPLE_CNSTR_TYPES_H
+
+#include "test_allocator.h"
+#include <type_traits>
+
+struct MutableCopy {
+  int val;
+  bool alloc_constructed{false};
+
+  constexpr MutableCopy() = default;
+  constexpr MutableCopy(int _val) : val(_val) {}
+  constexpr MutableCopy(MutableCopy&) = default;
+  constexpr MutableCopy(const MutableCopy&) = delete;
+
+  constexpr MutableCopy(std::allocator_arg_t, const test_allocator<int>&, MutableCopy& o)
+      : val(o.val), alloc_constructed(true) {}
+};
+
+template <>
+struct std::uses_allocator<MutableCopy, test_allocator<int>> : std::true_type {};
+
+struct ConstCopy {
+  int val;
+  bool alloc_constructed{false};
+
+  constexpr ConstCopy() = default;
+  constexpr ConstCopy(int _val) : val(_val) {}
+  constexpr ConstCopy(const ConstCopy&) = default;
+  constexpr ConstCopy(ConstCopy&) = delete;
+
+  constexpr ConstCopy(std::allocator_arg_t, const test_allocator<int>&, const ConstCopy& o)
+      : val(o.val), alloc_constructed(true) {}
+};
+
+template <>
+struct std::uses_allocator<ConstCopy, test_allocator<int>> : std::true_type {};
+
+struct MutableMove {
+  int val;
+  bool alloc_constructed{false};
+
+  constexpr MutableMove() = default;
+  constexpr MutableMove(int _val) : val(_val) {}
+  constexpr MutableMove(MutableMove&&) = default;
+  constexpr MutableMove(const MutableMove&&) = delete;
+
+  constexpr MutableMove(std::allocator_arg_t, const test_allocator<int>&, MutableMove&& o)
+      : val(o.val), alloc_constructed(true) {}
+};
+
+template <>
+struct std::uses_allocator<MutableMove, test_allocator<int>> : std::true_type {};
+
+struct ConstMove {
+  int val;
+  bool alloc_constructed{false};
+
+  constexpr ConstMove() = default;
+  constexpr ConstMove(int _val) : val(_val) {}
+  constexpr ConstMove(const ConstMove&& o) : val(o.val) {}
+  constexpr ConstMove(ConstMove&&) = delete;
+
+  constexpr ConstMove(std::allocator_arg_t, const test_allocator<int>&, const ConstMove&& o)
+      : val(o.val), alloc_constructed(true) {}
+};
+
+template <>
+struct std::uses_allocator<ConstMove, test_allocator<int>> : std::true_type {};
+
+template <class T>
+struct ConvertibleFrom {
+  T v;
+  bool alloc_constructed{false};
+
+  constexpr ConvertibleFrom() = default;
+  constexpr ConvertibleFrom(T& _v)
+    requires(std::is_constructible_v<T, T&>)
+  : v(_v) {}
+  constexpr ConvertibleFrom(const T& _v)
+    requires(std::is_constructible_v<T, const T&> && !std::is_const_v<T>)
+  : v(_v) {}
+  constexpr ConvertibleFrom(T&& _v)
+    requires(std::is_constructible_v<T, T &&>)
+  : v(std::move(_v)) {}
+  constexpr ConvertibleFrom(const T&& _v)
+    requires(std::is_constructible_v<T, const T &&> && !std::is_const_v<T>)
+  : v(std::move(_v)) {}
+
+  template <class U>
+    requires std::is_constructible_v<ConvertibleFrom, U&&>
+  constexpr ConvertibleFrom(std::allocator_arg_t, const test_allocator<int>&, U&& _u)
+      : ConvertibleFrom{std::forward<U>(_u)} {
+    alloc_constructed = true;
+  }
+};
+
+template <class T>
+struct std::uses_allocator<ConvertibleFrom<T>, test_allocator<int>> : std::true_type {};
+
+template <class T>
+struct ExplicitConstructibleFrom {
+  T v;
+  bool alloc_constructed{false};
+
+  constexpr explicit ExplicitConstructibleFrom() = default;
+  constexpr explicit ExplicitConstructibleFrom(T& _v)
+    requires(std::is_constructible_v<T, T&>)
+  : v(_v) {}
+  constexpr explicit ExplicitConstructibleFrom(const T& _v)
+    requires(std::is_constructible_v<T, const T&> && !std::is_const_v<T>)
+  : v(_v) {}
+  constexpr explicit ExplicitConstructibleFrom(T&& _v)
+    requires(std::is_constructible_v<T, T &&>)
+  : v(std::move(_v)) {}
+  constexpr explicit ExplicitConstructibleFrom(const T&& _v)
+    requires(std::is_constructible_v<T, const T &&> && !std::is_const_v<T>)
+  : v(std::move(_v)) {}
+
+  template <class U>
+    requires std::is_constructible_v<ExplicitConstructibleFrom, U&&>
+  constexpr ExplicitConstructibleFrom(std::allocator_arg_t, const test_allocator<int>&, U&& _u)
+      : ExplicitConstructibleFrom{std::forward<U>(_u)} {
+    alloc_constructed = true;
+  }
+};
+
+template <class T>
+struct std::uses_allocator<ExplicitConstructibleFrom<T>, test_allocator<int>> : std::true_type {};
+
+struct TracedCopyMove {
+  int nonConstCopy = 0;
+  int constCopy = 0;
+  int nonConstMove = 0;
+  int constMove = 0;
+  bool alloc_constructed = false;
+
+  constexpr TracedCopyMove() = default;
+  constexpr TracedCopyMove(const TracedCopyMove& other)
+      : nonConstCopy(other.nonConstCopy), constCopy(other.constCopy + 1), nonConstMove(other.nonConstMove),
+        constMove(other.constMove) {}
+  constexpr TracedCopyMove(TracedCopyMove& other)
+      : nonConstCopy(other.nonConstCopy + 1), constCopy(other.constCopy), nonConstMove(other.nonConstMove),
+        constMove(other.constMove) {}
+
+  constexpr TracedCopyMove(TracedCopyMove&& other)
+      : nonConstCopy(other.nonConstCopy), constCopy(other.constCopy), nonConstMove(other.nonConstMove + 1),
+        constMove(other.constMove) {}
+
+  constexpr TracedCopyMove(const TracedCopyMove&& other)
+      : nonConstCopy(other.nonConstCopy), constCopy(other.constCopy), nonConstMove(other.nonConstMove),
+        constMove(other.constMove + 1) {}
+
+  template <class U>
+    requires std::is_constructible_v<TracedCopyMove, U&&>
+  constexpr TracedCopyMove(std::allocator_arg_t, const test_allocator<int>&, U&& _u)
+      : TracedCopyMove{std::forward<U>(_u)} {
+    alloc_constructed = true;
+  }
+};
+
+template <>
+struct std::uses_allocator<TracedCopyMove, test_allocator<int>> : std::true_type {};
+
+// If the constructor tuple(tuple<UTyles...>&) is not available,
+// the fallback call to `tuple(const tuple&) = default;` or any other
+// constructor that takes const ref would increment the constCopy.
+inline constexpr bool nonConstCopyCtrCalled(const TracedCopyMove& obj) {
+  return obj.nonConstCopy == 1 && obj.constCopy == 0 && obj.constMove == 0 && obj.nonConstMove == 0;
+}
+
+// If the constructor tuple(const tuple<UTyles...>&&) is not available,
+// the fallback call to `tuple(const tuple&) = default;` or any other
+// constructor that takes const ref would increment the constCopy.
+inline constexpr bool constMoveCtrCalled(const TracedCopyMove& obj) {
+  return obj.nonConstMove == 0 && obj.constMove == 1 && obj.constCopy == 0 && obj.nonConstCopy == 0;
+}
+
+struct NoConstructorFromInt {};
+
+struct CvtFromTupleRef : TracedCopyMove {
+  constexpr CvtFromTupleRef() = default;
+  constexpr CvtFromTupleRef(std::tuple<CvtFromTupleRef>& other)
+      : TracedCopyMove(static_cast<TracedCopyMove&>(std::get<0>(other))) {}
+};
+
+struct ExplicitCtrFromTupleRef : TracedCopyMove {
+  constexpr explicit ExplicitCtrFromTupleRef() = default;
+  constexpr explicit ExplicitCtrFromTupleRef(std::tuple<ExplicitCtrFromTupleRef>& other)
+      : TracedCopyMove(static_cast<TracedCopyMove&>(std::get<0>(other))) {}
+};
+
+struct CvtFromConstTupleRefRef : TracedCopyMove {
+  constexpr CvtFromConstTupleRefRef() = default;
+  constexpr CvtFromConstTupleRefRef(const std::tuple<CvtFromConstTupleRefRef>&& other)
+      : TracedCopyMove(static_cast<const TracedCopyMove&&>(std::get<0>(other))) {}
+};
+
+struct ExplicitCtrFromConstTupleRefRef : TracedCopyMove {
+  constexpr explicit ExplicitCtrFromConstTupleRefRef() = default;
+  constexpr explicit ExplicitCtrFromConstTupleRefRef(std::tuple<const ExplicitCtrFromConstTupleRefRef>&& other)
+      : TracedCopyMove(static_cast<const TracedCopyMove&&>(std::get<0>(other))) {}
+};
+
+template <class T>
+void conversion_test(T);
+
+template <class T, class... Args>
+concept ImplicitlyConstructible = requires(Args&&... args) { conversion_test<T>({std::forward<Args>(args)...}); };
+
+#endif

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/non_const_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/non_const_pair.pass.cpp
new file mode 100644
index 0000000000000..18d41b6376e26
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.cnstr/non_const_pair.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types>
+// template <class U1, class U2>
+// constexpr explicit(see below) tuple<Types...>::tuple(pair<U1, U2>& u);
+
+// Constraints:
+// - sizeof...(Types) is 2 and
+// - is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// - is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "convert_types.h"
+
+// test constraints
+// sizeof...(Types) == 2
+static_assert(std::is_constructible_v<std::tuple<MutableCopy, int>, std::pair<MutableCopy, int>&>);
+
+static_assert(!std::is_constructible_v<std::tuple<MutableCopy>, std::pair<MutableCopy, int>&>);
+
+static_assert(!std::is_constructible_v<std::tuple<MutableCopy, int, int>, std::pair<MutableCopy, int>&>);
+
+// test constraints
+// is_constructible_v<T0, decltype(get<0>(FWD(u)))> is true and
+// is_constructible_v<T1, decltype(get<1>(FWD(u)))> is true.
+static_assert(std::is_constructible_v<std::tuple<int, int>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v<std::tuple<NoConstructorFromInt, int>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v<std::tuple<int, NoConstructorFromInt>, std::pair<int, int>&>);
+
+static_assert(!std::is_constructible_v< std::tuple<NoConstructorFromInt, NoConstructorFromInt>, std::pair<int, int>&>);
+
+// test: The expression inside explicit is equivalent to:
+// !is_convertible_v<decltype(get<0>(FWD(u))), T0> ||
+// !is_convertible_v<decltype(get<1>(FWD(u))), T1>
+static_assert(std::is_convertible_v<std::pair<MutableCopy, MutableCopy>&,
+                                    std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>>);
+
+static_assert(!std::is_convertible_v<std::pair<MutableCopy, MutableCopy>&,
+                                     std::tuple<ExplicitConstructibleFrom<MutableCopy>, ConvertibleFrom<MutableCopy>>>);
+
+static_assert(!std::is_convertible_v<std::pair<MutableCopy, MutableCopy>&,
+                                     std::tuple<ConvertibleFrom<MutableCopy>, ExplicitConstructibleFrom<MutableCopy>>>);
+
+constexpr bool test() {
+  // test implicit conversions.
+  {
+    std::pair<MutableCopy, int> p{1, 2};
+    std::tuple<ConvertibleFrom<MutableCopy>, ConvertibleFrom<int>> t = p;
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+  }
+
+  // test explicit conversions.
+  {
+    std::pair<MutableCopy, int> p{1, 2};
+    std::tuple<ExplicitConstructibleFrom<MutableCopy>, ExplicitConstructibleFrom<int>> t{p};
+    assert(std::get<0>(t).v.val == 1);
+    assert(std::get<1>(t).v == 2);
+  }
+
+  // non const overload should be called
+  {
+    std::pair<TracedCopyMove, TracedCopyMove> p;
+    std::tuple<ConvertibleFrom<TracedCopyMove>, TracedCopyMove> t = p;
+    assert(nonConstCopyCtrCalled(std::get<0>(t).v));
+    assert(nonConstCopyCtrCalled(std::get<1>(t)));
+  }
+
+  return true;
+}
+
+int main() {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.special/non_member_swap_const.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.special/non_member_swap_const.pass.cpp
new file mode 100644
index 0000000000000..a342095a0e5c8
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.special/non_member_swap_const.pass.cpp
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// template <class... Types>
+//   void swap(const tuple<Types...>& x, const tuple<Types...>& y);
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <tuple>
+#include <cassert>
+
+struct S {
+  int* calls;
+  friend constexpr void swap(S& a, S& b) {
+    *a.calls += 1;
+    *b.calls += 1;
+  }
+};
+struct CS {
+  int* calls;
+  friend constexpr void swap(const CS& a, const CS& b) {
+    *a.calls += 1;
+    *b.calls += 1;
+  }
+};
+
+static_assert(std::is_swappable_v<std::tuple<>>);
+static_assert(std::is_swappable_v<std::tuple<S>>);
+static_assert(std::is_swappable_v<std::tuple<CS>>);
+static_assert(std::is_swappable_v<std::tuple<S&>>);
+static_assert(std::is_swappable_v<std::tuple<CS, S>>);
+static_assert(std::is_swappable_v<std::tuple<CS, S&>>);
+static_assert(std::is_swappable_v<const std::tuple<>>);
+static_assert(!std::is_swappable_v<const std::tuple<S>>);
+static_assert(std::is_swappable_v<const std::tuple<CS>>);
+static_assert(std::is_swappable_v<const std::tuple<S&>>);
+static_assert(!std::is_swappable_v<const std::tuple<CS, S>>);
+static_assert(std::is_swappable_v<const std::tuple<CS, S&>>);
+
+constexpr bool test() {
+  int cs_calls = 0;
+  int s_calls = 0;
+  S s1{&s_calls};
+  S s2{&s_calls};
+  const std::tuple<CS, S&> t1 = {CS{&cs_calls}, s1};
+  const std::tuple<CS, S&> t2 = {CS{&cs_calls}, s2};
+  swap(t1, t2);
+  assert(cs_calls == 2);
+  assert(s_calls == 2);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap.pass.cpp
index e479a61e49f21..7c70321692321 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap.pass.cpp
@@ -65,7 +65,7 @@ bool test()
 int main(int, char**)
 {
     test();
-#if TEST_STD_VER >= 20
+#if TEST_STD_VER > 17
     static_assert(test());
 #endif
 

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap_const.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap_const.pass.cpp
new file mode 100644
index 0000000000000..5b1c5500247cf
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.swap/member_swap_const.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// void swap(const tuple& rhs);
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+
+#include "test_macros.h"
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+class SwapThrower {
+  void swap(SwapThrower&) = delete;
+  void swap(const SwapThrower&) const = delete;
+};
+
+void swap(const SwapThrower&, const SwapThrower&) { throw 0.f; }
+
+static_assert(std::is_swappable_v<const SwapThrower>);
+static_assert(std::is_swappable_with_v<const SwapThrower&, const SwapThrower&>);
+
+void test_noexcept() {
+  const std::tuple<SwapThrower> t1;
+  const std::tuple<SwapThrower> t2;
+
+  try {
+    t1.swap(t2);
+    std::swap(t1, t2);
+    assert(false);
+  } catch (float) {
+  }
+
+  try {
+    std::swap(std::as_const(t1), std::as_const(t2));
+    assert(false);
+  } catch (float) {
+  }
+}
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+struct ConstSwappable {
+  mutable int i;
+};
+
+constexpr void swap(const ConstSwappable& lhs, const ConstSwappable& rhs) { std::swap(lhs.i, rhs.i); }
+
+constexpr bool test() {
+  {
+    typedef std::tuple<const ConstSwappable> T;
+    const T t0(ConstSwappable{0});
+    T t1(ConstSwappable{1});
+    t0.swap(t1);
+    assert(std::get<0>(t0).i == 1);
+    assert(std::get<0>(t1).i == 0);
+  }
+  {
+    typedef std::tuple<ConstSwappable, ConstSwappable> T;
+    const T t0({0}, {1});
+    const T t1({2}, {3});
+    t0.swap(t1);
+    assert(std::get<0>(t0).i == 2);
+    assert(std::get<1>(t0).i == 3);
+    assert(std::get<0>(t1).i == 0);
+    assert(std::get<1>(t1).i == 1);
+  }
+  {
+    typedef std::tuple<ConstSwappable, const ConstSwappable, const ConstSwappable> T;
+    const T t0({0}, {1}, {2});
+    const T t1({3}, {4}, {5});
+    t0.swap(t1);
+    assert(std::get<0>(t0).i == 3);
+    assert(std::get<1>(t0).i == 4);
+    assert(std::get<2>(t0).i == 5);
+    assert(std::get<0>(t1).i == 0);
+    assert(std::get<1>(t1).i == 1);
+    assert(std::get<2>(t1).i == 2);
+  }
+  return true;
+}
+
+int main(int, char**) {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  test_noexcept();
+#endif
+  test();
+
+// gcc cannot have mutable member in constant expression
+#if !defined(TEST_COMPILER_GCC)
+  static_assert(test());
+#endif
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list