[libcxx-commits] [libcxx] a0839b1 - [libc++] Fix tuple assignment from types derived from a tuple-like

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Mon Feb 22 11:52:24 PST 2021


Author: Louis Dionne
Date: 2021-02-22T14:52:18-05:00
New Revision: a0839b14df6de99fe29bee7cdfff182d50de665d

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

LOG: [libc++] Fix tuple assignment from types derived from a tuple-like

The implementation of tuple's constructors and assignment operators
currently diverges from the way the Standard specifies them, which leads
to subtle cases where the behavior is not as specified. In particular, a
class derived from a tuple-like type (e.g. pair) can't be assigned to a
tuple with corresponding members, when it should. This commit re-implements
the assignment operators (BUT NOT THE CONSTRUCTORS) in a way much closer
to the specification to get rid of this bug. Most of the tests have been
stolen from Eric's patch https://reviews.llvm.org/D27606.

As a fly-by improvement, tests for noexcept correctness have been added
to all overloads of operator=. We should tackle the same issue for the
tuple constructors in a future patch - I'm just trying to make progress
on fixing this long-standing bug.

PR17550
rdar://15837420

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

Added: 
    libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp
    libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp
    libcxx/test/support/propagate_value_category.hpp

Modified: 
    libcxx/include/tuple
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp

Removed: 
    libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp


################################################################################
diff  --git a/libcxx/include/tuple b/libcxx/include/tuple
index c3c7db5ff118..58ae4cfbb8a9 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -55,8 +55,7 @@ public:
         explicit(see-below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&);
 
     tuple& operator=(const tuple&);
-    tuple&
-        operator=(tuple&&) noexcept(AND(is_nothrow_move_assignable<T>::value ...));
+    tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v<T> && ...);
     template <class... U>
         tuple& operator=(const tuple<U...>&);
     template <class... U>
@@ -66,6 +65,11 @@ public:
     template <class U1, class U2>
         tuple& operator=(pair<U1, U2>&&); // iff sizeof...(T) == 2
 
+    template<class U, size_t N>
+        tuple& operator=(array<U, N> const&) // iff sizeof...(T) == N, EXTENSION
+    template<class U, size_t N>
+        tuple& operator=(array<U, N>&&) // iff sizeof...(T) == N, EXTENSION
+
     void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...));
 };
 
@@ -257,15 +261,6 @@ public:
     __tuple_leaf(const __tuple_leaf& __t) = default;
     __tuple_leaf(__tuple_leaf&& __t) = default;
 
-    template <class _Tp>
-        _LIBCPP_INLINE_VISIBILITY
-        __tuple_leaf&
-        operator=(_Tp&& __t) _NOEXCEPT_((is_nothrow_assignable<_Hp&, _Tp>::value))
-        {
-            __value_ = _VSTD::forward<_Tp>(__t);
-            return *this;
-        }
-
     _LIBCPP_INLINE_VISIBILITY
     int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value)
     {
@@ -331,15 +326,6 @@ public:
     __tuple_leaf(__tuple_leaf const &) = default;
     __tuple_leaf(__tuple_leaf &&) = default;
 
-    template <class _Tp>
-        _LIBCPP_INLINE_VISIBILITY
-        __tuple_leaf&
-        operator=(_Tp&& __t) _NOEXCEPT_((is_nothrow_assignable<_Hp&, _Tp>::value))
-        {
-            _Hp::operator=(_VSTD::forward<_Tp>(__t));
-            return *this;
-        }
-
     _LIBCPP_INLINE_VISIBILITY
     int
     swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value)
@@ -429,49 +415,30 @@ struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp.
                                        typename __make_tuple_types<_Tuple>::type>::type>(_VSTD::get<_Indx>(__t)))...
             {}
 
-    template <class _Tuple>
-        _LIBCPP_INLINE_VISIBILITY
-        typename enable_if
-        <
-            __tuple_assignable<_Tuple, tuple<_Tp...> >::value,
-            __tuple_impl&
-        >::type
-        operator=(_Tuple&& __t) _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, typename tuple_element<_Indx,
-                                       typename __make_tuple_types<_Tuple>::type>::type>::value...>::value))
-        {
-            __swallow(__tuple_leaf<_Indx, _Tp>::operator=(_VSTD::forward<typename tuple_element<_Indx,
-                                       typename __make_tuple_types<_Tuple>::type>::type>(_VSTD::get<_Indx>(__t)))...);
-            return *this;
-        }
-
     __tuple_impl(const __tuple_impl&) = default;
     __tuple_impl(__tuple_impl&&) = default;
 
-    _LIBCPP_INLINE_VISIBILITY
-    __tuple_impl&
-    operator=(const __tuple_impl& __t) _NOEXCEPT_((__all<is_nothrow_copy_assignable<_Tp>::value...>::value))
-    {
-        __swallow(__tuple_leaf<_Indx, _Tp>::operator=(static_cast<const __tuple_leaf<_Indx, _Tp>&>(__t).get())...);
-        return *this;
-    }
-
-    _LIBCPP_INLINE_VISIBILITY
-    __tuple_impl&
-    operator=(__tuple_impl&& __t) _NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::value...>::value))
-    {
-        __swallow(__tuple_leaf<_Indx, _Tp>::operator=(_VSTD::forward<_Tp>(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t).get()))...);
-        return *this;
-    }
-
     _LIBCPP_INLINE_VISIBILITY
     void swap(__tuple_impl& __t)
         _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value)
     {
-        __swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...);
+        _VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...);
     }
 };
 
+template<class _Dest, class _Source, size_t ..._Np>
+_LIBCPP_INLINE_VISIBILITY
+void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) {
+    _VSTD::__swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), 0)...);
+}
 
+template<class _Dest, class _Source, class ..._Up, size_t ..._Np>
+_LIBCPP_INLINE_VISIBILITY
+void __memberwise_forward_assign(_Dest& __dest, _Source&& __source, __tuple_types<_Up...>, __tuple_indices<_Np...>) {
+    _VSTD::__swallow(((
+        _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(_VSTD::forward<_Source>(__source)))
+    ), void(), 0)...);
+}
 
 template <class ..._Tp>
 class _LIBCPP_TEMPLATE_VIS tuple
@@ -916,39 +883,129 @@ public:
         tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
             : __base_(allocator_arg_t(), __a, _VSTD::forward<_Tuple>(__t)) {}
 
-    using _CanCopyAssign = __all<is_copy_assignable<_Tp>::value...>;
-    using _CanMoveAssign = __all<is_move_assignable<_Tp>::value...>;
+    // [tuple.assign]
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple)
+        _NOEXCEPT_((_And<is_nothrow_copy_assignable<_Tp>...>::value))
+    {
+        _VSTD::__memberwise_copy_assign(*this, __tuple,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple)
+        _NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value))
+    {
+        _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple),
+            __tuple_types<_Tp...>(),
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
 
+    template<class... _Up, _EnableIf<
+        _And<
+            _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>,
+            is_assignable<_Tp&, _Up const&>...
+        >::value
+    ,int> = 0>
     _LIBCPP_INLINE_VISIBILITY
-    tuple& operator=(typename conditional<_CanCopyAssign::value, tuple, __nat>::type const& __t)
-        _NOEXCEPT_((__all<is_nothrow_copy_assignable<_Tp>::value...>::value))
+    tuple& operator=(tuple<_Up...> const& __tuple)
+        _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value))
     {
-        __base_.operator=(__t.__base_);
+        _VSTD::__memberwise_copy_assign(*this, __tuple,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
         return *this;
     }
 
+    template<class... _Up, _EnableIf<
+        _And<
+            _BoolConstant<sizeof...(_Tp) == sizeof...(_Up)>,
+            is_assignable<_Tp&, _Up>...
+        >::value
+    ,int> = 0>
     _LIBCPP_INLINE_VISIBILITY
-    tuple& operator=(typename conditional<_CanMoveAssign::value, tuple, __nat>::type&& __t)
-        _NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::value...>::value))
+    tuple& operator=(tuple<_Up...>&& __tuple)
+        _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value))
     {
-        __base_.operator=(static_cast<_BaseT&&>(__t.__base_));
+        _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple),
+            __tuple_types<_Up...>(),
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
         return *this;
     }
 
-    template <class _Tuple,
-              class = typename enable_if
-                      <
-                         __tuple_assignable<_Tuple, tuple>::value
-                      >::type
-             >
-        _LIBCPP_INLINE_VISIBILITY
-        tuple&
-        operator=(_Tuple&& __t) _NOEXCEPT_((is_nothrow_assignable<_BaseT&, _Tuple>::value))
-        {
-            __base_.operator=(_VSTD::forward<_Tuple>(__t));
-            return *this;
-        }
+    template<class _Up1, class _Up2, class _Dep = true_type, _EnableIf<
+        _And<_Dep,
+            _BoolConstant<sizeof...(_Tp) == 2>,
+            is_assignable<_FirstType<_Tp..., _Dep>&, _Up1 const&>,
+            is_assignable<_SecondType<_Tp..., _Dep>&, _Up2 const&>
+        >::value
+    ,int> = 0>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(pair<_Up1, _Up2> const& __pair)
+        _NOEXCEPT_((_And<
+            is_nothrow_assignable<_FirstType<_Tp...>&, _Up1 const&>,
+            is_nothrow_assignable<_SecondType<_Tp...>&, _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, _EnableIf<
+        _And<_Dep,
+            _BoolConstant<sizeof...(_Tp) == 2>,
+            is_assignable<_FirstType<_Tp..., _Dep>&, _Up1>,
+            is_assignable<_SecondType<_Tp..., _Dep>&, _Up2>
+        >::value
+    ,int> = 0>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(pair<_Up1, _Up2>&& __pair)
+        _NOEXCEPT_((_And<
+            is_nothrow_assignable<_FirstType<_Tp...>&, _Up1>,
+            is_nothrow_assignable<_SecondType<_Tp...>&, _Up2>
+        >::value))
+    {
+        _VSTD::get<0>(*this) = _VSTD::move(__pair.first);
+        _VSTD::get<1>(*this) = _VSTD::move(__pair.second);
+        return *this;
+    }
+
+    // EXTENSION
+    template<class _Up, size_t _Np, class = _EnableIf<
+        _And<
+            _BoolConstant<_Np == sizeof...(_Tp)>,
+            is_assignable<_Tp&, _Up const&>...
+        >::value
+    > >
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(array<_Up, _Np> const& __array)
+        _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up const&>...>::value))
+    {
+        _VSTD::__memberwise_copy_assign(*this, __array,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    // EXTENSION
+    template<class _Up, size_t _Np, class = void, class = _EnableIf<
+        _And<
+            _BoolConstant<_Np == sizeof...(_Tp)>,
+            is_assignable<_Tp&, _Up>...
+        >::value
+    > >
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(array<_Up, _Np>&& __array)
+        _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value))
+    {
+        _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__array),
+            __tuple_types<_If<true, _Up, _Tp>...>(),
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
 
+    // [tuple.swap]
     _LIBCPP_INLINE_VISIBILITY
     void swap(tuple& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value)
         {__base_.swap(__t.__base_);}

diff  --git a/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp
new file mode 100644
index 000000000000..c7847e3d4a78
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/array.extension.pass.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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;
+
+// EXTENSION
+// template <class U, size_t N>
+//   tuple& operator=(const array<U, N>& u);
+//
+// template <class U, size_t N>
+//   tuple& operator=(array<U, N>&& u);
+
+// UNSUPPORTED: c++03
+
+#include <array>
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+
+template <class T>
+struct NothrowAssignableFrom {
+    NothrowAssignableFrom& operator=(T) noexcept { return *this; }
+};
+
+template <class T>
+struct PotentiallyThrowingAssignableFrom {
+    PotentiallyThrowingAssignableFrom& operator=(T) { return *this; }
+};
+
+int main(int, char**) {
+    // Tests for the array const& overload
+    {
+        std::array<long, 3> array = {1l, 2l, 3l};
+        std::tuple<int, int, int> tuple;
+        tuple = array;
+        assert(std::get<0>(tuple) == 1);
+        assert(std::get<1>(tuple) == 2);
+        assert(std::get<2>(tuple) == 3);
+    }
+    {
+        typedef std::tuple<NothrowAssignableFrom<int>> Tuple;
+        typedef std::array<int, 1> Array;
+        static_assert(std::is_nothrow_assignable<Tuple&, Array const&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingAssignableFrom<int>> Tuple;
+        typedef std::array<int, 1> Array;
+        static_assert(std::is_assignable<Tuple&, Array const&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Array const&>::value, "");
+    }
+
+    // Tests for the array&& overload
+    {
+        std::array<long, 3> array = {1l, 2l, 3l};
+        std::tuple<int, int, int> tuple;
+        tuple = std::move(array);
+        assert(std::get<0>(tuple) == 1);
+        assert(std::get<1>(tuple) == 2);
+        assert(std::get<2>(tuple) == 3);
+    }
+    {
+        typedef std::tuple<NothrowAssignableFrom<int>> Tuple;
+        typedef std::array<int, 1> Array;
+        static_assert(std::is_nothrow_assignable<Tuple&, Array&&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingAssignableFrom<int>> Tuple;
+        typedef std::array<int, 1> Array;
+        static_assert(std::is_assignable<Tuple&, Array&&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Array&&>::value, "");
+    }
+
+    // Test lvalue-refs and const rvalue-ref
+    {
+        typedef std::tuple<NothrowAssignableFrom<int>> Tuple;
+        typedef std::array<int, 1> Array;
+        static_assert(std::is_nothrow_assignable<Tuple&, Array&>::value, "");
+        static_assert(std::is_nothrow_assignable<Tuple&, const Array&&>::value, "");
+    }
+
+    {
+        typedef std::tuple<NothrowAssignableFrom<int>> Tuple;
+        static_assert(!std::is_assignable<Tuple&, std::array<long, 2>&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, std::array<long, 2>&&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, const std::array<long, 2>&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, const std::array<long, 2>&&>::value, "");
+
+        static_assert(!std::is_assignable<Tuple&, std::array<long, 4>&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, std::array<long, 4>&&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, const std::array<long, 4>&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, const std::array<long, 4>&&>::value, "");
+    }
+
+    return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp b/libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
similarity index 100%
rename from libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
rename to libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
index 8edb818d1976..e865244f2b7a 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
@@ -15,10 +15,18 @@
 
 // UNSUPPORTED: c++03
 
+#include <cassert>
+#include <memory>
 #include <tuple>
+#include <type_traits>
 #include <utility>
-#include <memory>
-#include <cassert>
+
+struct NothrowCopyAssignable {
+    NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; }
+};
+struct PotentiallyThrowingCopyAssignable {
+    PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; }
+};
 
 #include "test_macros.h"
 
@@ -40,6 +48,25 @@ int main(int, char**)
         using P = std::tuple<std::unique_ptr<int>, std::unique_ptr<int>>;
         static_assert(!std::is_assignable<T, const P &>::value, "");
     }
+    {
+        typedef std::tuple<NothrowCopyAssignable, long> Tuple;
+        typedef std::pair<NothrowCopyAssignable, int> Pair;
+        static_assert(std::is_nothrow_assignable<Tuple&, Pair const&>::value, "");
+        static_assert(std::is_nothrow_assignable<Tuple&, Pair&>::value, "");
+        static_assert(std::is_nothrow_assignable<Tuple&, Pair const&&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingCopyAssignable, long> Tuple;
+        typedef std::pair<PotentiallyThrowingCopyAssignable, int> Pair;
+        static_assert(std::is_assignable<Tuple&, Pair const&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Pair const&>::value, "");
+
+        static_assert(std::is_assignable<Tuple&, Pair&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Pair&>::value, "");
+
+        static_assert(std::is_assignable<Tuple&, Pair const&&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Pair const&&>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp
index e02fc842ca22..1c78e59f0faa 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.pass.cpp
@@ -39,6 +39,16 @@ struct NonAssignable {
   NonAssignable& operator=(NonAssignable&&) = delete;
 };
 
+struct NothrowCopyAssignable
+{
+    NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingCopyAssignable
+{
+    PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; }
+};
+
 int main(int, char**)
 {
     {
@@ -98,6 +108,16 @@ int main(int, char**)
       static_assert(!std::is_assignable<T, U const&>::value, "");
       static_assert(!std::is_assignable<U, T const&>::value, "");
     }
+    {
+        typedef std::tuple<NothrowCopyAssignable, long> T0;
+        typedef std::tuple<NothrowCopyAssignable, int> T1;
+        static_assert(std::is_nothrow_assignable<T0&, T1 const&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingCopyAssignable, long> T0;
+        typedef std::tuple<PotentiallyThrowingCopyAssignable, int> T1;
+        static_assert(!std::is_nothrow_assignable<T0&, T1 const&>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
index 39bcc5dd3609..9465d9928d2a 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
@@ -50,6 +50,16 @@ struct NonAssignable {
   NonAssignable& operator=(NonAssignable&&) = delete;
 };
 
+struct NothrowMoveAssignable
+{
+    NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingMoveAssignable
+{
+    PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; }
+};
+
 int main(int, char**)
 {
     {
@@ -119,6 +129,16 @@ int main(int, char**)
       static_assert(!std::is_assignable<T, U&&>::value, "");
       static_assert(!std::is_assignable<U, T&&>::value, "");
     }
+    {
+        typedef std::tuple<NothrowMoveAssignable, long> T0;
+        typedef std::tuple<NothrowMoveAssignable, int> T1;
+        static_assert(std::is_nothrow_assignable<T0&, T1&&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingMoveAssignable, long> T0;
+        typedef std::tuple<PotentiallyThrowingMoveAssignable, int> T1;
+        static_assert(!std::is_nothrow_assignable<T0&, T1&&>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
index bbccd6754078..71882a4587b2 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
@@ -34,6 +34,12 @@ struct MoveAssignable {
   MoveAssignable& operator=(MoveAssignable const&) = delete;
   MoveAssignable& operator=(MoveAssignable&&) = default;
 };
+struct NothrowCopyAssignable {
+  NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; }
+};
+struct PotentiallyThrowingCopyAssignable {
+  PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; }
+};
 
 struct CopyAssignableInt {
   CopyAssignableInt& operator=(int&) { return *this; }
@@ -119,6 +125,14 @@ int main(int, char**)
       using P = std::pair<int, MoveAssignable>;
       static_assert(!std::is_assignable<T&, P&>::value, "");
     }
+    {
+        using T = std::tuple<NothrowCopyAssignable, int>;
+        static_assert(std::is_nothrow_copy_assignable<T>::value, "");
+    }
+    {
+        using T = std::tuple<PotentiallyThrowingCopyAssignable, int>;
+        static_assert(!std::is_nothrow_copy_assignable<T>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp
new file mode 100644
index 000000000000..373b30aa9588
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// 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... UTypes>
+//   tuple& operator=(const tuple<UTypes...>& u);
+
+// UNSUPPORTED: c++03
+
+#include <tuple>
+#include <array>
+#include <string>
+#include <utility>
+#include <cassert>
+
+#include "propagate_value_category.hpp"
+
+struct TracksIntQuals {
+  TracksIntQuals() : value(-1), value_category(VC_None), assigned(false) {}
+
+  template <class Tp,
+            class = typename std::enable_if<!std::is_same<
+                typename std::decay<Tp>::type, TracksIntQuals>::value>::type>
+  TracksIntQuals(Tp &&x)
+      : value(x), value_category(getValueCategory<Tp &&>()), assigned(false) {
+    static_assert(std::is_same<UnCVRef<Tp>, int>::value, "");
+  }
+
+  template <class Tp,
+            class = typename std::enable_if<!std::is_same<
+                typename std::decay<Tp>::type, TracksIntQuals>::value>::type>
+  TracksIntQuals &operator=(Tp &&x) {
+    static_assert(std::is_same<UnCVRef<Tp>, int>::value, "");
+    value = x;
+    value_category = getValueCategory<Tp &&>();
+    assigned = true;
+    return *this;
+  }
+
+  void reset() {
+    value = -1;
+    value_category = VC_None;
+    assigned = false;
+  }
+
+  bool checkConstruct(int expect, ValueCategory expect_vc) const {
+    return value != 1 && value == expect && value_category == expect_vc &&
+           assigned == false;
+  }
+
+  bool checkAssign(int expect, ValueCategory expect_vc) const {
+    return value != 1 && value == expect && value_category == expect_vc &&
+           assigned == true;
+  }
+
+  int value;
+  ValueCategory value_category;
+  bool assigned;
+};
+
+template <class Tup>
+struct DerivedFromTup : Tup {
+  using Tup::Tup;
+};
+
+template <ValueCategory VC>
+void do_derived_assign_test() {
+  using Tup1 = std::tuple<long, TracksIntQuals>;
+  Tup1 t;
+  auto reset = [&]() {
+    std::get<0>(t) = -1;
+    std::get<1>(t).reset();
+  };
+  {
+    DerivedFromTup<std::tuple<int, int>> d;
+    std::get<0>(d) = 42;
+    std::get<1>(d) = 101;
+
+    t = ValueCategoryCast<VC>(d);
+    assert(std::get<0>(t) == 42);
+    assert(std::get<1>(t).checkAssign(101, VC));
+  }
+  reset();
+  {
+    DerivedFromTup<std::pair<int, int>> d;
+    std::get<0>(d) = 42;
+    std::get<1>(d) = 101;
+
+    t = ValueCategoryCast<VC>(d);
+    assert(std::get<0>(t) == 42);
+    assert(std::get<1>(t).checkAssign(101, VC));
+  }
+  reset();
+  {
+#ifdef _LIBCPP_VERSION // assignment from std::array is a libc++ extension
+    DerivedFromTup<std::array<int, 2>> d;
+    std::get<0>(d) = 42;
+    std::get<1>(d) = 101;
+
+    t = ValueCategoryCast<VC>(d);
+    assert(std::get<0>(t) == 42);
+    assert(std::get<1>(t).checkAssign(101, VC));
+#endif
+  }
+}
+
+int main(int, char**) {
+    do_derived_assign_test<VC_LVal | VC_Const>();
+    do_derived_assign_test<VC_RVal>();
+
+    return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.pass.cpp
new file mode 100644
index 000000000000..267b8763733d
--- /dev/null
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/laziness.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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03
+
+// This test ensures that std::tuple is lazy when it comes to checking whether
+// the elements it is assigned from can be used to assign to the types in
+// the tuple.
+
+#include <tuple>
+#include <array>
+
+template <bool Enable, class ...Class>
+constexpr typename std::enable_if<Enable, bool>::type BlowUp() {
+  static_assert(Enable && sizeof...(Class) != sizeof...(Class), "");
+  return true;
+}
+
+template<class T>
+struct Fail {
+  static_assert(sizeof(T) != sizeof(T), "");
+  using type = void;
+};
+
+struct NoAssign {
+  NoAssign() = default;
+  NoAssign(NoAssign const&) = default;
+  template <class T, class = typename std::enable_if<sizeof(T) != sizeof(T)>::type>
+  NoAssign& operator=(T) { return *this; }
+};
+
+template <int>
+struct DieOnAssign {
+  DieOnAssign() = default;
+  template <class T, class X = typename std::enable_if<!std::is_same<T, DieOnAssign>::value>::type,
+                     class = typename Fail<X>::type>
+  DieOnAssign& operator=(T) {
+    return *this;
+  }
+};
+
+void test_arity_checks() {
+  {
+    using T = std::tuple<int, DieOnAssign<0>, int>;
+    using P = std::pair<int, int>;
+    static_assert(!std::is_assignable<T&, P const&>::value, "");
+  }
+  {
+    using T = std::tuple<int, int, DieOnAssign<1> >;
+    using A = std::array<int, 1>;
+    static_assert(!std::is_assignable<T&, A const&>::value, "");
+  }
+}
+
+void test_assignability_checks() {
+  {
+    using T1 = std::tuple<int, NoAssign, DieOnAssign<2> >;
+    using T2 = std::tuple<long, long, long>;
+    static_assert(!std::is_assignable<T1&, T2 const&>::value, "");
+  }
+  {
+    using T1 = std::tuple<NoAssign, DieOnAssign<3> >;
+    using T2 = std::pair<long, double>;
+    static_assert(!std::is_assignable<T1&, T2 const&>::value, "");
+  }
+}
+
+int main(int, char**) {
+  test_arity_checks();
+  test_assignability_checks();
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
index 292cfc2ba321..cabed38568f2 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
@@ -35,7 +35,12 @@ struct MoveAssignable {
   MoveAssignable& operator=(MoveAssignable const&) = delete;
   MoveAssignable& operator=(MoveAssignable&&) = default;
 };
-
+struct NothrowMoveAssignable {
+  NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; }
+};
+struct PotentiallyThrowingMoveAssignable {
+  PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; }
+};
 
 struct CountAssign {
   static int copied;
@@ -48,7 +53,6 @@ struct CountAssign {
 int CountAssign::copied = 0;
 int CountAssign::moved = 0;
 
-
 int main(int, char**)
 {
     {
@@ -102,7 +106,6 @@ int main(int, char**)
         using T = std::tuple<std::unique_ptr<int>>;
         static_assert(std::is_move_assignable<T>::value, "");
         static_assert(!std::is_copy_assignable<T>::value, "");
-
     }
     {
       using T = std::tuple<int, NonAssignable>;
@@ -123,6 +126,22 @@ int main(int, char**)
         assert(CountAssign::copied == 1);
         assert(CountAssign::moved == 0);
     }
+    {
+        using T = std::tuple<int, NonAssignable>;
+        static_assert(!std::is_move_assignable<T>::value, "");
+    }
+    {
+        using T = std::tuple<int, MoveAssignable>;
+        static_assert(std::is_move_assignable<T>::value, "");
+    }
+    {
+        using T = std::tuple<NothrowMoveAssignable, int>;
+        static_assert(std::is_nothrow_move_assignable<T>::value, "");
+    }
+    {
+        using T = std::tuple<PotentiallyThrowingMoveAssignable, int>;
+        static_assert(!std::is_nothrow_move_assignable<T>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
index 20315c01b111..3e083f5f8ea0 100644
--- a/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
+++ b/libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
@@ -37,9 +37,20 @@ struct D
     explicit D(int i) : B(i) {}
 };
 
-struct NonMoveAssignable {
-  NonMoveAssignable& operator=(NonMoveAssignable const&) = default;
-  NonMoveAssignable& operator=(NonMoveAssignable&&) = delete;
+struct NonAssignable
+{
+  NonAssignable& operator=(NonAssignable const&) = delete;
+  NonAssignable& operator=(NonAssignable&&) = delete;
+};
+
+struct NothrowMoveAssignable
+{
+    NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingMoveAssignable
+{
+    PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; }
 };
 
 int main(int, char**)
@@ -54,15 +65,28 @@ int main(int, char**)
         assert(std::get<1>(t1)->id_ == 3);
     }
     {
-      using T = std::tuple<int, NonMoveAssignable>;
-      using P = std::pair<int, NonMoveAssignable>;
-      static_assert(!std::is_assignable<T&, P&&>::value, "");
+        using T = std::tuple<int, NonAssignable>;
+        using P = std::pair<int, NonAssignable>;
+        static_assert(!std::is_assignable<T&, P&&>::value, "");
     }
     {
       using T = std::tuple<int, int, int>;
       using P = std::pair<int, int>;
       static_assert(!std::is_assignable<T&, P&&>::value, "");
     }
+    {
+        typedef std::tuple<NothrowMoveAssignable, long> Tuple;
+        typedef std::pair<NothrowMoveAssignable, int> Pair;
+        static_assert(std::is_nothrow_assignable<Tuple&, Pair&&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, Pair const&&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingMoveAssignable, long> Tuple;
+        typedef std::pair<PotentiallyThrowingMoveAssignable, int> Pair;
+        static_assert(std::is_assignable<Tuple&, Pair&&>::value, "");
+        static_assert(!std::is_nothrow_assignable<Tuple&, Pair&&>::value, "");
+        static_assert(!std::is_assignable<Tuple&, Pair const&&>::value, "");
+    }
 
-  return 0;
+    return 0;
 }

diff  --git a/libcxx/test/support/propagate_value_category.hpp b/libcxx/test/support/propagate_value_category.hpp
new file mode 100644
index 000000000000..02a035ddab32
--- /dev/null
+++ b/libcxx/test/support/propagate_value_category.hpp
@@ -0,0 +1,153 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY
+#define TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY
+
+#include "test_macros.h"
+#include <type_traits>
+
+#if TEST_STD_VER < 11
+#error this header may only be used in C++11
+#endif
+
+using UnderlyingVCType = unsigned;
+enum ValueCategory : UnderlyingVCType {
+  VC_None = 0,
+  VC_LVal = 1 << 0,
+  VC_RVal = 1 << 1,
+  VC_Const = 1 << 2,
+  VC_Volatile = 1 << 3,
+  VC_ConstVolatile = VC_Const | VC_Volatile
+};
+
+inline constexpr ValueCategory operator&(ValueCategory LHS, ValueCategory RHS) {
+  return ValueCategory(LHS & (UnderlyingVCType)RHS);
+}
+
+inline constexpr ValueCategory operator|(ValueCategory LHS, ValueCategory RHS) {
+  return ValueCategory(LHS | (UnderlyingVCType)RHS);
+}
+
+inline constexpr ValueCategory operator^(ValueCategory LHS, ValueCategory RHS) {
+  return ValueCategory(LHS ^ (UnderlyingVCType)RHS);
+}
+
+inline constexpr bool isValidValueCategory(ValueCategory VC) {
+  return (VC & (VC_LVal | VC_RVal)) != (VC_LVal | VC_RVal);
+}
+
+inline constexpr bool hasValueCategory(ValueCategory Arg, ValueCategory Key) {
+  return Arg == Key || ((Arg & Key) == Key);
+}
+
+template <class Tp>
+using UnCVRef =
+    typename std::remove_cv<typename std::remove_reference<Tp>::type>::type;
+
+template <class Tp>
+constexpr ValueCategory getReferenceQuals() {
+  return std::is_lvalue_reference<Tp>::value
+             ? VC_LVal
+             : (std::is_rvalue_reference<Tp>::value ? VC_RVal : VC_None);
+}
+static_assert(getReferenceQuals<int>() == VC_None, "");
+static_assert(getReferenceQuals<int &>() == VC_LVal, "");
+static_assert(getReferenceQuals<int &&>() == VC_RVal, "");
+
+template <class Tp>
+constexpr ValueCategory getCVQuals() {
+  using Vp = typename std::remove_reference<Tp>::type;
+  return std::is_const<Vp>::value && std::is_volatile<Vp>::value
+             ? VC_ConstVolatile
+             : (std::is_const<Vp>::value
+                    ? VC_Const
+                    : (std::is_volatile<Vp>::value ? VC_Volatile : VC_None));
+}
+static_assert(getCVQuals<int>() == VC_None, "");
+static_assert(getCVQuals<int const>() == VC_Const, "");
+static_assert(getCVQuals<int volatile>() == VC_Volatile, "");
+static_assert(getCVQuals<int const volatile>() == VC_ConstVolatile, "");
+static_assert(getCVQuals<int &>() == VC_None, "");
+static_assert(getCVQuals<int const &>() == VC_Const, "");
+
+template <class Tp>
+inline constexpr ValueCategory getValueCategory() {
+  return getReferenceQuals<Tp>() | getCVQuals<Tp>();
+}
+static_assert(getValueCategory<int>() == VC_None, "");
+static_assert(getValueCategory<int const &>() == (VC_LVal | VC_Const), "");
+static_assert(getValueCategory<int const volatile &&>() ==
+                  (VC_RVal | VC_ConstVolatile),
+              "");
+
+template <ValueCategory VC>
+struct ApplyValueCategory {
+private:
+  static_assert(isValidValueCategory(VC), "");
+
+  template <bool Pred, class Then, class Else>
+  using CondT = typename std::conditional<Pred, Then, Else>::type;
+
+public:
+  template <class Tp, class Vp = UnCVRef<Tp>>
+  using ApplyCVQuals = CondT<
+      hasValueCategory(VC, VC_ConstVolatile), typename std::add_cv<Vp>::type,
+      CondT<hasValueCategory(VC, VC_Const), typename std::add_const<Vp>::type,
+            CondT<hasValueCategory(VC, VC_Volatile),
+                  typename std::add_volatile<Vp>::type, Tp>>>;
+
+  template <class Tp, class Vp = typename std::remove_reference<Tp>::type>
+  using ApplyReferenceQuals =
+      CondT<hasValueCategory(VC, VC_LVal),
+            typename std::add_lvalue_reference<Vp>::type,
+            CondT<hasValueCategory(VC, VC_RVal),
+                  typename std::add_rvalue_reference<Vp>::type, Vp>>;
+
+  template <class Tp>
+  using Apply = ApplyReferenceQuals<ApplyCVQuals<UnCVRef<Tp>>>;
+
+  template <class Tp, bool Dummy = true,
+            typename std::enable_if<Dummy && (VC & VC_LVal), bool>::type = true>
+  static Apply<UnCVRef<Tp>> cast(Tp &&t) {
+    using ToType = Apply<UnCVRef<Tp>>;
+    return static_cast<ToType>(t);
+  }
+
+  template <class Tp, bool Dummy = true,
+            typename std::enable_if<Dummy && (VC & VC_RVal), bool>::type = true>
+  static Apply<UnCVRef<Tp>> cast(Tp &&t) {
+    using ToType = Apply<UnCVRef<Tp>>;
+    return static_cast<ToType>(std::move(t));
+  }
+
+  template <
+      class Tp, bool Dummy = true,
+      typename std::enable_if<Dummy && ((VC & (VC_LVal | VC_RVal)) == VC_None),
+                              bool>::type = true>
+  static Apply<UnCVRef<Tp>> cast(Tp &&t) {
+    return t;
+  }
+};
+
+template <ValueCategory VC, class Tp>
+using ApplyValueCategoryT = typename ApplyValueCategory<VC>::template Apply<Tp>;
+
+template <class Tp>
+using PropagateValueCategory = ApplyValueCategory<getValueCategory<Tp>()>;
+
+template <class Tp, class Up>
+using PropagateValueCategoryT =
+    typename ApplyValueCategory<getValueCategory<Tp>()>::template Apply<Up>;
+
+template <ValueCategory VC, class Tp>
+typename ApplyValueCategory<VC>::template Apply<Tp> ValueCategoryCast(Tp &&t) {
+  return ApplyValueCategory<VC>::cast(std::forward<Tp>(t));
+};
+
+#endif // TEST_SUPPORT_PROPAGATE_VALUE_CATEGORY


        


More information about the libcxx-commits mailing list