diff --git a/libcxx/include/tuple b/libcxx/include/tuple --- a/libcxx/include/tuple +++ b/libcxx/include/tuple @@ -436,7 +436,7 @@ _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))) + _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(__source)) ), void(), 0)...); } @@ -967,8 +967,8 @@ is_nothrow_assignable<_SecondType<_Tp...>&, _Up2> >::value)) { - _VSTD::get<0>(*this) = _VSTD::move(__pair.first); - _VSTD::get<1>(*this) = _VSTD::move(__pair.second); + _VSTD::get<0>(*this) = _VSTD::forward<_Up1>(__pair.first); + _VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second); return *this; } 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 --- 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 @@ -45,19 +45,51 @@ } }; +struct NothrowMoveAssignable { + NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +}; + +struct PotentiallyThrowingMoveAssignable { + PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } +}; + struct NonAssignable { NonAssignable& operator=(NonAssignable const&) = delete; NonAssignable& operator=(NonAssignable&&) = delete; }; -struct NothrowMoveAssignable -{ - NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } +struct MoveAssignable { + MoveAssignable& operator=(MoveAssignable const&) = delete; + MoveAssignable& operator=(MoveAssignable&&) = default; +}; + +struct CopyAssignable { + CopyAssignable& operator=(CopyAssignable const&) = default; + CopyAssignable& operator=(CopyAssignable&&) = delete; }; -struct PotentiallyThrowingMoveAssignable +struct TrackMove { - PotentiallyThrowingMoveAssignable& operator=(PotentiallyThrowingMoveAssignable&&) { return *this; } + TrackMove() : value(0), moved_from(false) { } + explicit TrackMove(int v) : value(v), moved_from(false) { } + TrackMove(TrackMove const& other) : value(other.value), moved_from(false) { } + TrackMove(TrackMove&& other) : value(other.value), moved_from(false) { + other.moved_from = true; + } + TrackMove& operator=(TrackMove const& other) { + value = other.value; + moved_from = false; + return *this; + } + TrackMove& operator=(TrackMove&& other) { + value = other.value; + moved_from = false; + other.moved_from = true; + return *this; + } + + int value; + bool moved_from; }; int main(int, char**) @@ -139,6 +171,82 @@ typedef std::tuple T1; static_assert(!std::is_nothrow_assignable::value, ""); } + { + // We assign through the reference and don't move out of the incoming ref, + // so this doesn't work (but would if the type were CopyAssignable). + { + using T1 = std::tuple; + using T2 = std::tuple; + static_assert(!std::is_assignable::value, ""); + } + + // ... works if it's CopyAssignable + { + using T1 = std::tuple; + using T2 = std::tuple; + static_assert(std::is_assignable::value, ""); + } + + // For rvalue-references, we can move-assign if the type is MoveAssignable + // or CopyAssignable (since in the worst case the move will decay into a copy). + { + using T1 = std::tuple; + using T2 = std::tuple; + static_assert(std::is_assignable::value, ""); + + using T3 = std::tuple; + using T4 = std::tuple; + static_assert(std::is_assignable::value, ""); + } + + // In all cases, we can't move-assign if the types are not assignable, + // since we assign through the reference. + { + using T1 = std::tuple; + using T2 = std::tuple; + static_assert(!std::is_assignable::value, ""); + + using T3 = std::tuple; + using T4 = std::tuple; + static_assert(!std::is_assignable::value, ""); + } + } + { + // Make sure that we don't incorrectly move out of the source's reference. + using Dest = std::tuple; + using Source = std::tuple; + TrackMove track{3}; + Source src(track, 4); + assert(!track.moved_from); + + Dest dst; + dst = std::move(src); // here we should make a copy + assert(!track.moved_from); + assert(std::get<0>(dst).value == 3); + } + { + // But we do move out of the source's reference if it's a rvalue ref + using Dest = std::tuple; + using Source = std::tuple; + TrackMove track{3}; + Source src(std::move(track), 4); + assert(!track.moved_from); // we just took a reference + + Dest dst; + dst = std::move(src); + assert(track.moved_from); + assert(std::get<0>(dst).value == 3); + } + { + // If the source holds a value, then we move out of it too + using Dest = std::tuple; + using Source = std::tuple; + Source src(TrackMove{3}, 4); + Dest dst; + dst = std::move(src); + assert(std::get<0>(src).moved_from); + assert(std::get<0>(dst).value == 3); + } 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 --- 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 @@ -142,6 +142,30 @@ using T = std::tuple; static_assert(!std::is_nothrow_move_assignable::value, ""); } + { + // We assign through the reference and don't move out of the incoming ref, + // so this doesn't work (but would if the type were CopyAssignable). + using T1 = std::tuple; + static_assert(!std::is_move_assignable::value, ""); + + // ... works if it's CopyAssignable + using T2 = std::tuple; + static_assert(std::is_move_assignable::value, ""); + + // For rvalue-references, we can move-assign if the type is MoveAssignable + // or CopyAssignable (since in the worst case the move will decay into a copy). + using T3 = std::tuple; + using T4 = std::tuple; + static_assert(std::is_move_assignable::value, ""); + static_assert(std::is_move_assignable::value, ""); + + // In all cases, we can't move-assign if the types are not assignable, + // since we assign through the reference. + using T5 = std::tuple; + using T6 = std::tuple; + static_assert(!std::is_move_assignable::value, ""); + static_assert(!std::is_move_assignable::value, ""); + } 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 --- 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,12 +37,48 @@ explicit D(int i) : B(i) {} }; +struct TrackMove +{ + TrackMove() : value(0), moved_from(false) { } + explicit TrackMove(int v) : value(v), moved_from(false) { } + TrackMove(TrackMove const& other) : value(other.value), moved_from(false) { } + TrackMove(TrackMove&& other) : value(other.value), moved_from(false) { + other.moved_from = true; + } + TrackMove& operator=(TrackMove const& other) { + value = other.value; + moved_from = false; + return *this; + } + TrackMove& operator=(TrackMove&& other) { + value = other.value; + moved_from = false; + other.moved_from = true; + return *this; + } + + int value; + bool moved_from; +}; + struct NonAssignable { NonAssignable& operator=(NonAssignable const&) = delete; NonAssignable& operator=(NonAssignable&&) = delete; }; +struct MoveAssignable +{ + MoveAssignable& operator=(MoveAssignable const&) = delete; + MoveAssignable& operator=(MoveAssignable&&) = default; +}; + +struct CopyAssignable +{ + CopyAssignable& operator=(CopyAssignable const&) = default; + CopyAssignable& operator=(CopyAssignable&&) = delete; +}; + struct NothrowMoveAssignable { NothrowMoveAssignable& operator=(NothrowMoveAssignable&&) noexcept { return *this; } @@ -87,6 +123,82 @@ static_assert(!std::is_nothrow_assignable::value, ""); static_assert(!std::is_assignable::value, ""); } + { + // We assign through the reference and don't move out of the incoming ref, + // so this doesn't work (but would if the type were CopyAssignable). + { + using T = std::tuple; + using P = std::pair; + static_assert(!std::is_assignable::value, ""); + } + + // ... works if it's CopyAssignable + { + using T = std::tuple; + using P = std::pair; + static_assert(std::is_assignable::value, ""); + } + + // For rvalue-references, we can move-assign if the type is MoveAssignable + // or CopyAssignable (since in the worst case the move will decay into a copy). + { + using T1 = std::tuple; + using P1 = std::pair; + static_assert(std::is_assignable::value, ""); + + using T2 = std::tuple; + using P2 = std::pair; + static_assert(std::is_assignable::value, ""); + } + + // In all cases, we can't move-assign if the types are not assignable, + // since we assign through the reference. + { + using T1 = std::tuple; + using P1 = std::pair; + static_assert(!std::is_assignable::value, ""); + + using T2 = std::tuple; + using P2 = std::pair; + static_assert(!std::is_assignable::value, ""); + } + } + { + // Make sure that we don't incorrectly move out of the source's reference. + using Dest = std::tuple; + using Source = std::pair; + TrackMove track{3}; + Source src(track, 4); + assert(!track.moved_from); + + Dest dst; + dst = std::move(src); // here we should make a copy + assert(!track.moved_from); + assert(std::get<0>(dst).value == 3); + } + { + // But we do move out of the source's reference if it's a rvalue ref + using Dest = std::tuple; + using Source = std::pair; + TrackMove track{3}; + Source src(std::move(track), 4); + assert(!track.moved_from); // we just took a reference + + Dest dst; + dst = std::move(src); + assert(track.moved_from); + assert(std::get<0>(dst).value == 3); + } + { + // If the pair holds a value, then we move out of it too + using Dest = std::tuple; + using Source = std::pair; + Source src(TrackMove{3}, 4); + Dest dst; + dst = std::move(src); + assert(src.first.moved_from); + assert(std::get<0>(dst).value == 3); + } return 0; } diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp --- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp @@ -40,6 +40,16 @@ NotAssignable& operator=(NotAssignable&&) = delete; }; +struct MoveAssignable { + MoveAssignable& operator=(MoveAssignable const&) = delete; + MoveAssignable& operator=(MoveAssignable&&) = default; +}; + +struct CopyAssignable { + CopyAssignable& operator=(CopyAssignable const&) = default; + CopyAssignable& operator=(CopyAssignable&&) = delete; +}; + TEST_CONSTEXPR_CXX20 bool test() { { typedef std::pair P; @@ -89,9 +99,36 @@ assert(p2.first.copied == 0); } { - using T = std::pair; - using P = std::pair; - static_assert(!std::is_assignable::value, ""); + using P1 = std::pair; + using P2 = std::pair; + using P3 = std::pair; + static_assert(!std::is_move_assignable::value, ""); + static_assert(!std::is_move_assignable::value, ""); + static_assert(!std::is_move_assignable::value, ""); + } + { + // We assign through the reference and don't move out of the incoming ref, + // so this doesn't work (but would if the type were CopyAssignable). + using P1 = std::pair; + static_assert(!std::is_move_assignable::value, ""); + + // ... works if it's CopyAssignable + using P2 = std::pair; + static_assert(std::is_move_assignable::value, ""); + + // For rvalue-references, we can move-assign if the type is MoveAssignable + // or CopyAssignable (since in the worst case the move will decay into a copy). + using P3 = std::pair; + using P4 = std::pair; + static_assert(std::is_move_assignable::value, ""); + static_assert(std::is_move_assignable::value, ""); + + // In all cases, we can't move-assign if the types are not assignable, + // since we assign through the reference. + using P5 = std::pair; + using P6 = std::pair; + static_assert(!std::is_move_assignable::value, ""); + static_assert(!std::is_move_assignable::value, ""); } return true; } diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair_U_V.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair_U_V.pass.cpp --- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair_U_V.pass.cpp +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair_U_V.pass.cpp @@ -44,6 +44,21 @@ CopyAssignableInt& operator=(int&) { return *this; } }; +struct NotAssignable { + NotAssignable& operator=(NotAssignable const&) = delete; + NotAssignable& operator=(NotAssignable&&) = delete; +}; + +struct MoveAssignable { + MoveAssignable& operator=(MoveAssignable const&) = delete; + MoveAssignable& operator=(MoveAssignable&&) = default; +}; + +struct CopyAssignable { + CopyAssignable& operator=(CopyAssignable const&) = default; + CopyAssignable& operator=(CopyAssignable&&) = delete; +}; + TEST_CONSTEXPR_CXX20 bool test() { { typedef std::pair P1; @@ -71,6 +86,60 @@ static_assert(!std::is_assignable::value, ""); static_assert(!std::is_assignable::value, ""); } + { + // Make sure we can't move-assign from a pair containing a reference + // if that type isn't copy-constructible (since otherwise we'd be + // stealing the object through the reference). + using P1 = std::pair; + using P2 = std::pair; + static_assert(!std::is_assignable::value, ""); + + // ... but this should work since we're going to steal out of the + // incoming rvalue reference. + using P3 = std::pair; + using P4 = std::pair; + static_assert(std::is_assignable::value, ""); + } + { + // We assign through the reference and don't move out of the incoming ref, + // so this doesn't work (but would if the type were CopyAssignable). + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(!std::is_assignable::value, ""); + } + + // ... works if it's CopyAssignable + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(std::is_assignable::value, ""); + } + + // For rvalue-references, we can move-assign if the type is MoveAssignable, + // or CopyAssignable (since in the worst case the move will decay into a copy). + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(std::is_assignable::value, ""); + + using P3 = std::pair; + using P4 = std::pair; + static_assert(std::is_assignable::value, ""); + } + + // In all cases, we can't move-assign if the types are not assignable, + // since we assign through the reference. + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(!std::is_assignable::value, ""); + + using P3 = std::pair; + using P4 = std::pair; + static_assert(!std::is_assignable::value, ""); + } + } return true; } diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/move_ctor.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/move_ctor.pass.cpp --- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/move_ctor.pass.cpp +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/move_ctor.pass.cpp @@ -25,6 +25,12 @@ Dummy(Dummy &&) = default; }; +struct NotCopyOrMoveConstructible { + NotCopyOrMoveConstructible() = default; + NotCopyOrMoveConstructible(NotCopyOrMoveConstructible const&) = delete; + NotCopyOrMoveConstructible(NotCopyOrMoveConstructible&&) = delete; +}; + int main(int, char**) { { @@ -40,6 +46,31 @@ static_assert(!std::is_copy_constructible

::value, ""); static_assert(std::is_move_constructible

::value, ""); } + { + // When constructing a pair containing a reference, we only bind the + // reference, so it doesn't matter whether the type is or isn't + // copy/move constructible. + { + using P = std::pair; + static_assert(std::is_move_constructible

::value, ""); + + NotCopyOrMoveConstructible obj; + P p2{obj, 3}; + P p1(std::move(p2)); + assert(&p1.first == &obj); + assert(&p2.first == &obj); + } + { + using P = std::pair; + static_assert(std::is_move_constructible

::value, ""); + + NotCopyOrMoveConstructible obj; + P p2{std::move(obj), 3}; + P p1(std::move(p2)); + assert(&p1.first == &obj); + assert(&p2.first == &obj); + } + } - return 0; + return 0; } diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/rv_pair_U_V.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/rv_pair_U_V.pass.cpp --- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/rv_pair_U_V.pass.cpp +++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/rv_pair_U_V.pass.cpp @@ -65,6 +65,17 @@ int value; }; +struct NotCopyOrMoveConstructible { + NotCopyOrMoveConstructible() = default; + NotCopyOrMoveConstructible(NotCopyOrMoveConstructible const&) = delete; + NotCopyOrMoveConstructible(NotCopyOrMoveConstructible&&) = delete; +}; + +struct NonCopyConstructible { + NonCopyConstructible(NonCopyConstructible const&) = delete; + NonCopyConstructible(NonCopyConstructible&&) = default; +}; + int main(int, char**) { { @@ -161,6 +172,41 @@ test_pair_rv(); test_pair_rv(); } + { + // When constructing a pair containing a reference, we only bind the + // reference, so it doesn't matter whether the type is or isn't + // copy/move constructible. + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(std::is_constructible::value, ""); + + NotCopyOrMoveConstructible obj; + P2 p2{obj, 3}; + P1 p1(std::move(p2)); + assert(&p1.first == &obj); + assert(&p2.first == &obj); + } + { + using P1 = std::pair; + using P2 = std::pair; + static_assert(std::is_constructible::value, ""); + + NotCopyOrMoveConstructible obj; + P2 p2{std::move(obj), 3}; + P1 p1(std::move(p2)); + assert(&p1.first == &obj); + assert(&p2.first == &obj); + } + } + { + // Make sure we can't move-construct from a pair containing a reference + // if that type isn't copy-constructible (since otherwise we'd be stealing + // the object through the reference). + using P1 = std::pair; + using P2 = std::pair; + static_assert(!std::is_constructible::value, ""); + } #if TEST_STD_VER > 11 { // explicit constexpr test constexpr std::pair p1(42, 43);