[libcxx-commits] [libcxx] 618862e - [libc++] Fix incorrect forwarding in tuple's assignment operator
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Feb 25 11:56:08 PST 2021
Author: Louis Dionne
Date: 2021-02-25T14:56:16-05:00
New Revision: 618862e89a022b2e8f73a62bed7c91654060dbab
URL: https://github.com/llvm/llvm-project/commit/618862e89a022b2e8f73a62bed7c91654060dbab
DIFF: https://github.com/llvm/llvm-project/commit/618862e89a022b2e8f73a62bed7c91654060dbab.diff
LOG: [libc++] Fix incorrect forwarding in tuple's assignment operator
Also, add a bunch of tests for tuple and pair's assignment operators
involving reference types.
Differential Revision: https://reviews.llvm.org/D97419
Added:
Modified:
libcxx/include/tuple
libcxx/test/std/utilities/tuple/tuple.tuple/tuple.assign/convert_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_pair.pass.cpp
libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair.pass.cpp
libcxx/test/std/utilities/utility/pairs/pairs.pair/assign_rv_pair_U_V.pass.cpp
libcxx/test/std/utilities/utility/pairs/pairs.pair/move_ctor.pass.cpp
libcxx/test/std/utilities/utility/pairs/pairs.pair/rv_pair_U_V.pass.cpp
Removed:
################################################################################
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 58ae4cfbb8a9..b3d3bae69019 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -436,7 +436,7 @@ 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)))
+ _VSTD::get<_Np>(__dest) = _VSTD::forward<_Up>(_VSTD::get<_Np>(__source))
), void(), 0)...);
}
@@ -967,8 +967,8 @@ public:
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
index 04bec1a67feb..0d09c3be0b60 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
@@ -45,19 +45,51 @@ struct E {
}
};
+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 @@ int main(int, char**)
typedef std::tuple<PotentiallyThrowingMoveAssignable, int> T1;
static_assert(!std::is_nothrow_assignable<T0&, T1&&>::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<MoveAssignable&, long>;
+ using T2 = std::tuple<MoveAssignable&, int>;
+ static_assert(!std::is_assignable<T1&, T2&&>::value, "");
+ }
+
+ // ... works if it's CopyAssignable
+ {
+ using T1 = std::tuple<CopyAssignable&, long>;
+ using T2 = std::tuple<CopyAssignable&, int>;
+ static_assert(std::is_assignable<T1&, T2&&>::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<MoveAssignable&&, long>;
+ using T2 = std::tuple<MoveAssignable&&, int>;
+ static_assert(std::is_assignable<T1&, T2&&>::value, "");
+
+ using T3 = std::tuple<CopyAssignable&&, long>;
+ using T4 = std::tuple<CopyAssignable&&, int>;
+ static_assert(std::is_assignable<T3&, T4&&>::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<NonAssignable&, long>;
+ using T2 = std::tuple<NonAssignable&, int>;
+ static_assert(!std::is_assignable<T1&, T2&&>::value, "");
+
+ using T3 = std::tuple<NonAssignable&&, long>;
+ using T4 = std::tuple<NonAssignable&&, int>;
+ static_assert(!std::is_assignable<T3&, T4&&>::value, "");
+ }
+ }
+ {
+ // Make sure that we don't incorrectly move out of the source's reference.
+ using Dest = std::tuple<TrackMove, long>;
+ using Source = std::tuple<TrackMove&, int>;
+ 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<TrackMove, long>;
+ using Source = std::tuple<TrackMove&&, int>;
+ 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<TrackMove, long>;
+ using Source = std::tuple<TrackMove, int>;
+ 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
index cabed38568f2..a89c4eab900e 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
@@ -142,6 +142,30 @@ int main(int, char**)
using T = std::tuple<PotentiallyThrowingMoveAssignable, int>;
static_assert(!std::is_nothrow_move_assignable<T>::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<MoveAssignable&, int>;
+ static_assert(!std::is_move_assignable<T1>::value, "");
+
+ // ... works if it's CopyAssignable
+ using T2 = std::tuple<CopyAssignable&, int>;
+ static_assert(std::is_move_assignable<T2>::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<MoveAssignable&&, int>;
+ using T4 = std::tuple<CopyAssignable&&, int>;
+ static_assert(std::is_move_assignable<T3>::value, "");
+ static_assert(std::is_move_assignable<T4>::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<NonAssignable&, int>;
+ using T6 = std::tuple<NonAssignable&&, int>;
+ static_assert(!std::is_move_assignable<T5>::value, "");
+ static_assert(!std::is_move_assignable<T6>::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
index 3e083f5f8ea0..aaf93971ace0 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,12 +37,48 @@ struct D
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 @@ int main(int, char**)
static_assert(!std::is_nothrow_assignable<Tuple&, Pair&&>::value, "");
static_assert(!std::is_assignable<Tuple&, Pair const&&>::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<MoveAssignable&, int>;
+ using P = std::pair<MoveAssignable&, int>;
+ static_assert(!std::is_assignable<T&, P&&>::value, "");
+ }
+
+ // ... works if it's CopyAssignable
+ {
+ using T = std::tuple<CopyAssignable&, int>;
+ using P = std::pair<CopyAssignable&, int>;
+ static_assert(std::is_assignable<T&, P&&>::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<MoveAssignable&&, int>;
+ using P1 = std::pair<MoveAssignable&&, int>;
+ static_assert(std::is_assignable<T1&, P1&&>::value, "");
+
+ using T2 = std::tuple<CopyAssignable&&, int>;
+ using P2 = std::pair<CopyAssignable&&, int>;
+ static_assert(std::is_assignable<T2&, P2&&>::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<NonAssignable&, int>;
+ using P1 = std::pair<NonAssignable&, int>;
+ static_assert(!std::is_assignable<T1&, P1&&>::value, "");
+
+ using T2 = std::tuple<NonAssignable&&, int>;
+ using P2 = std::pair<NonAssignable&&, int>;
+ static_assert(!std::is_assignable<T2&, P2&&>::value, "");
+ }
+ }
+ {
+ // Make sure that we don't incorrectly move out of the source's reference.
+ using Dest = std::tuple<TrackMove, int>;
+ using Source = std::pair<TrackMove&, int>;
+ 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<TrackMove, int>;
+ using Source = std::pair<TrackMove&&, int>;
+ 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<TrackMove, int>;
+ using Source = std::pair<TrackMove, int>;
+ 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
index 0172692c895e..8e5d9c39ae88 100644
--- 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 @@ struct NotAssignable {
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<ConstexprTestTypes::MoveOnly, int> P;
@@ -89,9 +99,36 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(p2.first.copied == 0);
}
{
- using T = std::pair<int, NotAssignable>;
- using P = std::pair<int, NotAssignable>;
- static_assert(!std::is_assignable<T&, P&&>::value, "");
+ using P1 = std::pair<int, NotAssignable>;
+ using P2 = std::pair<NotAssignable, int>;
+ using P3 = std::pair<NotAssignable, NotAssignable>;
+ static_assert(!std::is_move_assignable<P1>::value, "");
+ static_assert(!std::is_move_assignable<P2>::value, "");
+ static_assert(!std::is_move_assignable<P3>::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<MoveAssignable&, int>;
+ static_assert(!std::is_move_assignable<P1>::value, "");
+
+ // ... works if it's CopyAssignable
+ using P2 = std::pair<CopyAssignable&, int>;
+ static_assert(std::is_move_assignable<P2>::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<MoveAssignable&&, int>;
+ using P4 = std::pair<CopyAssignable&&, int>;
+ static_assert(std::is_move_assignable<P3>::value, "");
+ static_assert(std::is_move_assignable<P4>::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<NotAssignable&, int>;
+ using P6 = std::pair<NotAssignable&&, int>;
+ static_assert(!std::is_move_assignable<P5>::value, "");
+ static_assert(!std::is_move_assignable<P6>::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
index 4e58e0b9ea10..581ec33320bd 100644
--- 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 @@ struct CopyAssignableInt {
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<Derived, short> P1;
@@ -71,6 +86,60 @@ TEST_CONSTEXPR_CXX20 bool test() {
static_assert(!std::is_assignable<T&, P&&>::value, "");
static_assert(!std::is_assignable<P&, T&&>::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<MoveAssignable, long>;
+ using P2 = std::pair<MoveAssignable&, int>;
+ static_assert(!std::is_assignable<P1&, P2&&>::value, "");
+
+ // ... but this should work since we're going to steal out of the
+ // incoming rvalue reference.
+ using P3 = std::pair<MoveAssignable, long>;
+ using P4 = std::pair<MoveAssignable&&, int>;
+ static_assert(std::is_assignable<P3&, P4&&>::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<MoveAssignable&, long>;
+ using P2 = std::pair<MoveAssignable&, int>;
+ static_assert(!std::is_assignable<P1&, P2&&>::value, "");
+ }
+
+ // ... works if it's CopyAssignable
+ {
+ using P1 = std::pair<CopyAssignable&, long>;
+ using P2 = std::pair<CopyAssignable&, int>;
+ static_assert(std::is_assignable<P1&, P2&&>::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<MoveAssignable&&, long>;
+ using P2 = std::pair<MoveAssignable&&, int>;
+ static_assert(std::is_assignable<P1&, P2&&>::value, "");
+
+ using P3 = std::pair<CopyAssignable&&, long>;
+ using P4 = std::pair<CopyAssignable&&, int>;
+ static_assert(std::is_assignable<P3&, P4&&>::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<NotAssignable&, long>;
+ using P2 = std::pair<NotAssignable&, int>;
+ static_assert(!std::is_assignable<P1&, P2&&>::value, "");
+
+ using P3 = std::pair<NotAssignable&&, long>;
+ using P4 = std::pair<NotAssignable&&, int>;
+ static_assert(!std::is_assignable<P3&, P4&&>::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
index 0165c37411ab..e7f19dddc4c9 100644
--- 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 @@ struct Dummy {
Dummy(Dummy &&) = default;
};
+struct NotCopyOrMoveConstructible {
+ NotCopyOrMoveConstructible() = default;
+ NotCopyOrMoveConstructible(NotCopyOrMoveConstructible const&) = delete;
+ NotCopyOrMoveConstructible(NotCopyOrMoveConstructible&&) = delete;
+};
+
int main(int, char**)
{
{
@@ -40,6 +46,31 @@ int main(int, char**)
static_assert(!std::is_copy_constructible<P>::value, "");
static_assert(std::is_move_constructible<P>::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<NotCopyOrMoveConstructible&, int>;
+ static_assert(std::is_move_constructible<P>::value, "");
+
+ NotCopyOrMoveConstructible obj;
+ P p2{obj, 3};
+ P p1(std::move(p2));
+ assert(&p1.first == &obj);
+ assert(&p2.first == &obj);
+ }
+ {
+ using P = std::pair<NotCopyOrMoveConstructible&&, int>;
+ static_assert(std::is_move_constructible<P>::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
index 3628e9dd1bbf..593df9002a7d 100644
--- 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 @@ struct ImplicitT {
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 @@ int main(int, char**)
test_pair_rv<ExplicitTypes::ConvertingType, ExplicitTypes::ConvertingType&, true, false>();
test_pair_rv<ExplicitTypes::ConvertingType, ExplicitTypes::ConvertingType&&, true, false>();
}
+ {
+ // 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<NotCopyOrMoveConstructible&, long>;
+ using P2 = std::pair<NotCopyOrMoveConstructible&, int>;
+ static_assert(std::is_constructible<P1, P2&&>::value, "");
+
+ NotCopyOrMoveConstructible obj;
+ P2 p2{obj, 3};
+ P1 p1(std::move(p2));
+ assert(&p1.first == &obj);
+ assert(&p2.first == &obj);
+ }
+ {
+ using P1 = std::pair<NotCopyOrMoveConstructible&&, long>;
+ using P2 = std::pair<NotCopyOrMoveConstructible&&, int>;
+ static_assert(std::is_constructible<P1, P2&&>::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<NonCopyConstructible, long>;
+ using P2 = std::pair<NonCopyConstructible&, int>;
+ static_assert(!std::is_constructible<P1, P2&&>::value, "");
+ }
#if TEST_STD_VER > 11
{ // explicit constexpr test
constexpr std::pair<int, int> p1(42, 43);
More information about the libcxx-commits
mailing list