ldionne created this revision.
ldionne added reviewers: EricWF, mclow.lists.
Herald added subscribers: cfe-commits, dexonsmith, christof.

The implementation of operator= for tuple 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) could not be assigned to a tuple with corresponding members.
This commit re-implements operator= in a way much closer to the specification
and gets rid of this bug. Tests have been stolen from EricWF's assignment
tests in https://reviews.llvm.org/D27606.

As a fly-by improvement, tests for noexcept correctness have been added to
all overloads of operator=.

PR17550
rdar://problem/15837420


Repository:
  rCXX libc++

https://reviews.llvm.org/D50106

Files:
  libcxx/include/tuple
  
libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp
  
libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.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/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/derived_from_tuple_like.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
  
libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
  libcxx/test/support/propagate_value_category.hpp

Index: libcxx/test/support/propagate_value_category.hpp
===================================================================
--- /dev/null
+++ libcxx/test/support/propagate_value_category.hpp
@@ -0,0 +1,154 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#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
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
===================================================================
--- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move_pair.pass.cpp
@@ -36,6 +36,16 @@
     explicit D(int i) : B(i) {}
 };
 
+struct NothrowMoveAssignable
+{
+    NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingMoveAssignable
+{
+    PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; }
+};
+
 int main()
 {
     {
@@ -47,4 +57,14 @@
         assert(std::get<0>(t1) == 2);
         assert(std::get<1>(t1)->id_ == 3);
     }
+    {
+        typedef std::tuple<NothrowMoveAssignable, long> Tuple;
+        typedef std::pair<NothrowMoveAssignable, int> Pair;
+        static_assert(std::is_nothrow_assignable<Tuple&, Pair&&>::value, "");
+    }
+    {
+        typedef std::tuple<PotentiallyThrowingMoveAssignable, long> Tuple;
+        typedef std::pair<PotentiallyThrowingMoveAssignable, int> Pair;
+        static_assert(!std::is_nothrow_assignable<Tuple&, Pair&&>::value, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
===================================================================
--- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/move.pass.cpp
@@ -34,7 +34,12 @@
   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;
@@ -47,7 +52,6 @@
 int CountAssign::copied = 0;
 int CountAssign::moved = 0;
 
-
 int main()
 {
     {
@@ -101,15 +105,6 @@
         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>;
-        static_assert(!std::is_move_assignable<T>::value, "");
-    }
-    {
-        using T = std::tuple<int, MoveAssignable>;
-        static_assert(std::is_move_assignable<T>::value, "");
     }
     {
         // The move should decay to a copy.
@@ -122,4 +117,20 @@
         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, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp
===================================================================
--- /dev/null
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/derived_from_tuple_like.pass.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// template <class... UTypes>
+//   tuple& operator=(const tuple<UTypes...>& u);
+
+// UNSUPPORTED: c++98, 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() {
+    do_derived_assign_test<VC_LVal | VC_Const>();
+    do_derived_assign_test<VC_RVal>();
+}
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
===================================================================
--- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/copy.pass.cpp
@@ -35,6 +35,12 @@
   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; }
+};
 
 int main()
 {
@@ -101,4 +107,12 @@
         using T = std::tuple<int, MoveAssignable>;
         static_assert(!std::is_copy_assignable<T>::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, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
===================================================================
--- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_move.pass.cpp
@@ -44,6 +44,16 @@
   }
 };
 
+struct NothrowMoveAssignable
+{
+    NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingMoveAssignable
+{
+    PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; }
+};
+
 int main()
 {
     {
@@ -107,4 +117,14 @@
         assert(std::get<0>(t) == 43);
         assert(&std::get<0>(t) == &x);
     }
+    {
+        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, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_copy.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_copy.pass.cpp
@@ -33,6 +33,16 @@
     explicit D(int i = 0) : B(i) {}
 };
 
+struct NothrowCopyAssignable
+{
+    NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; }
+};
+
+struct PotentiallyThrowingCopyAssignable
+{
+    PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; }
+};
+
 int main()
 {
     {
@@ -86,4 +96,14 @@
         assert(std::get<0>(t) == 43);
         assert(&std::get<0>(t) == &x);
     }
+    {
+        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, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
===================================================================
--- libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/const_pair.pass.cpp
@@ -16,9 +16,18 @@
 
 // UNSUPPORTED: c++98, c++03
 
+#include <cassert>
 #include <tuple>
+#include <type_traits>
 #include <utility>
-#include <cassert>
+
+
+struct NothrowCopyAssignable {
+    NothrowCopyAssignable& operator=(NothrowCopyAssignable const&) noexcept { return *this; }
+};
+struct PotentiallyThrowingCopyAssignable {
+    PotentiallyThrowingCopyAssignable& operator=(PotentiallyThrowingCopyAssignable const&) { return *this; }
+};
 
 int main()
 {
@@ -31,4 +40,14 @@
         assert(std::get<0>(t1) == 2);
         assert(std::get<1>(t1) == short('a'));
     }
+    {
+        typedef std::tuple<NothrowCopyAssignable, long> Tuple;
+        typedef std::pair<NothrowCopyAssignable, int> Pair;
+        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_nothrow_assignable<Tuple&, Pair const&>::value, "");
+    }
 }
Index: libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
===================================================================
--- /dev/null
+++ libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/tuple_array_template_depth.pass.cpp
@@ -1,34 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is dual licensed under the MIT and the University of Illinois Open
-// Source Licenses. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++98, c++03
-
-// <tuple>
-
-// template <class... Types> class tuple;
-
-// template <class Tuple, __tuple_assignable<Tuple, tuple> >
-//   tuple & operator=(Tuple &&);
-
-// This test checks that we do not evaluate __make_tuple_types
-// on the array when it doesn't match the size of the tuple.
-
-#include <array>
-#include <tuple>
-
-// Use 1256 to try and blow the template instantiation depth for all compilers.
-typedef std::array<char, 1256> array_t;
-typedef std::tuple<array_t> tuple_t;
-
-int main()
-{
-    array_t arr;
-    tuple_t tup;
-    tup = arr;
-}
Index: libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.pass.cpp
===================================================================
--- /dev/null
+++ libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/rvalue_array.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// EXTENSION
+// template <class U, size_t N>
+//   tuple& operator=(array<U, N>&& u);
+
+// UNSUPPORTED: c++98, 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() {
+    {
+        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_nothrow_assignable<Tuple&, Array&&>::value, "");
+    }
+}
Index: libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp
===================================================================
--- /dev/null
+++ libcxx/test/libcxx/utilities/tuple/tuple.tuple/tuple.assign/const_array.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// <tuple>
+
+// template <class... Types> class tuple;
+
+// EXTENSION
+// template <class U, size_t N>
+//   tuple& operator=(const array<U, N>& u);
+
+// UNSUPPORTED: c++98, 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() {
+    {
+        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_nothrow_assignable<Tuple&, Array const&>::value, "");
+    }
+}
Index: libcxx/include/tuple
===================================================================
--- libcxx/include/tuple
+++ libcxx/include/tuple
@@ -246,15 +246,6 @@
     __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)
     {
@@ -320,15 +311,6 @@
     __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)
@@ -421,53 +403,43 @@
                                        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))...);
     }
 };
 
+template<class _Dest, class _Source, size_t ..._Np>
+_LIBCPP_INLINE_VISIBILITY
+void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) {
+    __swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), int())...);
+}
+
+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...>) {
+    __swallow(((
+        _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(_VSTD::forward<_Source>(__source)))
+    ), void(), int())...);
+}
+
+template<size_t _Dummy, class ..._Tp>
+struct __pack_first;
+template<size_t _Dummy, class _Tp1, class ..._Tp>
+struct __pack_first<_Dummy, _Tp1, _Tp...> { typedef _Tp1 type; };
 
+template<size_t _Dummy, class ..._Tp>
+struct __pack_second;
+template<size_t _Dummy, class _Tp1, class _Tp2, class ..._Tp>
+struct __pack_second<_Dummy, _Tp1, _Tp2, _Tp...> { typedef _Tp2 type; };
 
 template <class ..._Tp>
-class _LIBCPP_TEMPLATE_VIS tuple
-{
+class _LIBCPP_TEMPLATE_VIS tuple {
     typedef __tuple_impl<typename __make_tuple_indices<sizeof...(_Tp)>::type, _Tp...> _BaseT;
 
     _BaseT __base_;
@@ -864,39 +836,113 @@
         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=(typename conditional<_CanCopyAssign::value, tuple, __nat>::type const& __t)
+    tuple& operator=(typename conditional<__all<is_copy_assignable<_Tp>::value...>::value, tuple, __nat>::type const& __tuple)
         _NOEXCEPT_((__all<is_nothrow_copy_assignable<_Tp>::value...>::value))
     {
-        __base_.operator=(__t.__base_);
+        __memberwise_copy_assign(*this, __tuple,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
         return *this;
     }
 
     _LIBCPP_INLINE_VISIBILITY
-    tuple& operator=(typename conditional<_CanMoveAssign::value, tuple, __nat>::type&& __t)
+    tuple& operator=(typename conditional<__all<is_move_assignable<_Tp>::value...>::value, tuple, __nat>::type&& __tuple)
         _NOEXCEPT_((__all<is_nothrow_move_assignable<_Tp>::value...>::value))
     {
-        __base_.operator=(static_cast<_BaseT&&>(__t.__base_));
+        __memberwise_forward_assign(*this, _VSTD::move(__tuple),
+            __tuple_types<_Tp...>(),
+            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... _Up, class = typename enable_if<
+        sizeof...(_Tp) == sizeof...(_Up) &&
+        __all<is_assignable<_Tp&, _Up const&>::value...>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(tuple<_Up...> const& __tuple)
+        _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, _Up const&>::value...>::value))
+    {
+        __memberwise_copy_assign(*this, __tuple,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    template<class... _Up, class = typename enable_if<
+        sizeof...(_Tp) == sizeof...(_Up) &&
+        __all<is_assignable<_Tp&, _Up&&>::value...>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(tuple<_Up...>&& __tuple)
+        _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, _Up&&>::value...>::value))
+    {
+        __memberwise_forward_assign(*this, _VSTD::move(__tuple),
+            __tuple_types<_Up...>(),
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    template<class _Up1, class _Up2, class = typename enable_if<
+        sizeof...(_Tp) == 2 &&
+        is_assignable<typename __pack_first<sizeof(_Up1), _Tp...>::type&, _Up1 const&>::value &&
+        is_assignable<typename __pack_second<sizeof(_Up1), _Tp...>::type&, _Up2 const&>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(pair<_Up1, _Up2> const& __pair)
+        _NOEXCEPT_((is_nothrow_assignable<typename __pack_first<sizeof(_Up1), _Tp...>::type&, _Up1 const&>::value &&
+                    is_nothrow_assignable<typename __pack_second<sizeof(_Up1), _Tp...>::type&, _Up2 const&>::value))
+    {
+        _VSTD::get<0>(*this) = __pair.first;
+        _VSTD::get<1>(*this) = __pair.second;
+        return *this;
+    }
+
+    template<class _Up1, class _Up2, class = typename enable_if<
+        sizeof...(_Tp) == 2 &&
+        is_assignable<typename __pack_first<sizeof(_Up1), _Tp...>::type&, _Up1&&>::value &&
+        is_assignable<typename __pack_second<sizeof(_Up1), _Tp...>::type&, _Up2&&>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(pair<_Up1, _Up2>&& __pair)
+        _NOEXCEPT_((is_nothrow_assignable<typename __pack_first<sizeof(_Up1), _Tp...>::type&, _Up1&&>::value &&
+                    is_nothrow_assignable<typename __pack_second<sizeof(_Up1), _Tp...>::type&, _Up2&&>::value))
+    {
+        _VSTD::get<0>(*this) = _VSTD::forward<_Up1>(__pair.first);
+        _VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second);
+        return *this;
+    }
+
+    // EXTENSION
+    template<class _Up, size_t _Np, class = typename enable_if<
+        _Np == sizeof...(_Tp) &&
+        __all<is_assignable<_Tp&, _Up const&>::value...>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(array<_Up, _Np> const& __array)
+        _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, _Up const&>::value...>::value))
+    {
+        __memberwise_copy_assign(*this, __array,
+            typename __make_tuple_indices<sizeof...(_Tp)>::type());
+        return *this;
+    }
+
+    // EXTENSION
+    template<class _Up, size_t _Np, class = typename enable_if<
+        _Np == sizeof...(_Tp) &&
+        __all<is_assignable<_Tp&, _Up&&>::value...>::value
+    >::type>
+    _LIBCPP_INLINE_VISIBILITY
+    tuple& operator=(array<_Up, _Np>&& __array)
+        _NOEXCEPT_((__all<is_nothrow_assignable<_Tp&, _Up&&>::value...>::value))
+    {
+        __memberwise_forward_assign(*this, _VSTD::move(__array),
+            __tuple_types<typename conditional<true, _Up, _Tp>::type...>(),
+            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_);}
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to