summaryrefslogtreecommitdiff
path: root/test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp')
-rw-r--r--test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp591
1 files changed, 591 insertions, 0 deletions
diff --git a/test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp b/test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp
new file mode 100644
index 000000000000..416c6b4e334d
--- /dev/null
+++ b/test/std/utilities/variant/variant.variant/variant.swap/swap.pass.cpp
@@ -0,0 +1,591 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14
+
+// <variant>
+
+// template <class ...Types> class variant;
+
+// void swap(variant& rhs) noexcept(see below)
+
+#include <cassert>
+#include <string>
+#include <type_traits>
+#include <variant>
+
+#include "test_convertible.hpp"
+#include "test_macros.h"
+#include "variant_test_helpers.hpp"
+
+struct NotSwappable {};
+void swap(NotSwappable &, NotSwappable &) = delete;
+
+struct NotCopyable {
+ NotCopyable() = default;
+ NotCopyable(const NotCopyable &) = delete;
+ NotCopyable &operator=(const NotCopyable &) = delete;
+};
+
+struct NotCopyableWithSwap {
+ NotCopyableWithSwap() = default;
+ NotCopyableWithSwap(const NotCopyableWithSwap &) = delete;
+ NotCopyableWithSwap &operator=(const NotCopyableWithSwap &) = delete;
+};
+void swap(NotCopyableWithSwap &, NotCopyableWithSwap) {}
+
+struct NotMoveAssignable {
+ NotMoveAssignable() = default;
+ NotMoveAssignable(NotMoveAssignable &&) = default;
+ NotMoveAssignable &operator=(NotMoveAssignable &&) = delete;
+};
+
+struct NotMoveAssignableWithSwap {
+ NotMoveAssignableWithSwap() = default;
+ NotMoveAssignableWithSwap(NotMoveAssignableWithSwap &&) = default;
+ NotMoveAssignableWithSwap &operator=(NotMoveAssignableWithSwap &&) = delete;
+};
+void swap(NotMoveAssignableWithSwap &, NotMoveAssignableWithSwap &) noexcept {}
+
+template <bool Throws> void do_throw() {}
+
+template <> void do_throw<true>() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ throw 42;
+#else
+ std::abort();
+#endif
+}
+
+template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign,
+ bool NT_Swap, bool EnableSwap = true>
+struct NothrowTypeImp {
+ static int move_called;
+ static int move_assign_called;
+ static int swap_called;
+ static void reset() { move_called = move_assign_called = swap_called = 0; }
+ NothrowTypeImp() = default;
+ explicit NothrowTypeImp(int v) : value(v) {}
+ NothrowTypeImp(const NothrowTypeImp &o) noexcept(NT_Copy) : value(o.value) {
+ assert(false);
+ } // never called by test
+ NothrowTypeImp(NothrowTypeImp &&o) noexcept(NT_Move) : value(o.value) {
+ ++move_called;
+ do_throw<!NT_Move>();
+ o.value = -1;
+ }
+ NothrowTypeImp &operator=(const NothrowTypeImp &) noexcept(NT_CopyAssign) {
+ assert(false);
+ return *this;
+ } // never called by the tests
+ NothrowTypeImp &operator=(NothrowTypeImp &&o) noexcept(NT_MoveAssign) {
+ ++move_assign_called;
+ do_throw<!NT_MoveAssign>();
+ value = o.value;
+ o.value = -1;
+ return *this;
+ }
+ int value;
+};
+template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign,
+ bool NT_Swap, bool EnableSwap>
+int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap,
+ EnableSwap>::move_called = 0;
+template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign,
+ bool NT_Swap, bool EnableSwap>
+int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap,
+ EnableSwap>::move_assign_called = 0;
+template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign,
+ bool NT_Swap, bool EnableSwap>
+int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap,
+ EnableSwap>::swap_called = 0;
+
+template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign,
+ bool NT_Swap>
+void swap(NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign,
+ NT_Swap, true> &lhs,
+ NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign,
+ NT_Swap, true> &rhs) noexcept(NT_Swap) {
+ lhs.swap_called++;
+ do_throw<!NT_Swap>();
+ int tmp = lhs.value;
+ lhs.value = rhs.value;
+ rhs.value = tmp;
+}
+
+// throwing copy, nothrow move ctor/assign, no swap provided
+using NothrowMoveable = NothrowTypeImp<false, true, false, true, false, false>;
+// throwing copy and move assign, nothrow move ctor, no swap provided
+using NothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>;
+// nothrow move ctor, throwing move assignment, swap provided
+using NothrowMoveCtorWithThrowingSwap =
+ NothrowTypeImp<false, true, false, false, false, true>;
+// throwing move ctor, nothrow move assignment, no swap provided
+using ThrowingMoveCtor =
+ NothrowTypeImp<false, false, false, true, false, false>;
+// throwing special members, nothrowing swap
+using ThrowingTypeWithNothrowSwap =
+ NothrowTypeImp<false, false, false, false, true, true>;
+using NothrowTypeWithThrowingSwap =
+ NothrowTypeImp<true, true, true, true, false, true>;
+// throwing move assign with nothrow move and nothrow swap
+using ThrowingMoveAssignNothrowMoveCtorWithSwap =
+ NothrowTypeImp<false, true, false, false, true, true>;
+// throwing move assign with nothrow move but no swap.
+using ThrowingMoveAssignNothrowMoveCtor =
+ NothrowTypeImp<false, true, false, false, false, false>;
+
+struct NonThrowingNonNoexceptType {
+ static int move_called;
+ static void reset() { move_called = 0; }
+ NonThrowingNonNoexceptType() = default;
+ NonThrowingNonNoexceptType(int v) : value(v) {}
+ NonThrowingNonNoexceptType(NonThrowingNonNoexceptType &&o) noexcept(false)
+ : value(o.value) {
+ ++move_called;
+ o.value = -1;
+ }
+ NonThrowingNonNoexceptType &
+ operator=(NonThrowingNonNoexceptType &&) noexcept(false) {
+ assert(false); // never called by the tests.
+ return *this;
+ }
+ int value;
+};
+int NonThrowingNonNoexceptType::move_called = 0;
+
+struct ThrowsOnSecondMove {
+ int value;
+ int move_count;
+ ThrowsOnSecondMove(int v) : value(v), move_count(0) {}
+ ThrowsOnSecondMove(ThrowsOnSecondMove &&o) noexcept(false)
+ : value(o.value), move_count(o.move_count + 1) {
+ if (move_count == 2)
+ do_throw<true>();
+ o.value = -1;
+ }
+ ThrowsOnSecondMove &operator=(ThrowsOnSecondMove &&) {
+ assert(false); // not called by test
+ return *this;
+ }
+};
+
+void test_swap_valueless_by_exception() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ using V = std::variant<int, MakeEmptyT>;
+ { // both empty
+ V v1;
+ makeEmpty(v1);
+ V v2;
+ makeEmpty(v2);
+ assert(MakeEmptyT::alive == 0);
+ { // member swap
+ v1.swap(v2);
+ assert(v1.valueless_by_exception());
+ assert(v2.valueless_by_exception());
+ assert(MakeEmptyT::alive == 0);
+ }
+ { // non-member swap
+ swap(v1, v2);
+ assert(v1.valueless_by_exception());
+ assert(v2.valueless_by_exception());
+ assert(MakeEmptyT::alive == 0);
+ }
+ }
+ { // only one empty
+ V v1(42);
+ V v2;
+ makeEmpty(v2);
+ { // member swap
+ v1.swap(v2);
+ assert(v1.valueless_by_exception());
+ assert(std::get<0>(v2) == 42);
+ // swap again
+ v2.swap(v1);
+ assert(v2.valueless_by_exception());
+ assert(std::get<0>(v1) == 42);
+ }
+ { // non-member swap
+ swap(v1, v2);
+ assert(v1.valueless_by_exception());
+ assert(std::get<0>(v2) == 42);
+ // swap again
+ swap(v1, v2);
+ assert(v2.valueless_by_exception());
+ assert(std::get<0>(v1) == 42);
+ }
+ }
+#endif
+}
+
+void test_swap_same_alternative() {
+ {
+ using T = ThrowingTypeWithNothrowSwap;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<0>, 100);
+ v1.swap(v2);
+ assert(T::swap_called == 1);
+ assert(std::get<0>(v1).value == 100);
+ assert(std::get<0>(v2).value == 42);
+ swap(v1, v2);
+ assert(T::swap_called == 2);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<0>(v2).value == 100);
+ }
+ {
+ using T = NothrowMoveable;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<0>, 100);
+ v1.swap(v2);
+ assert(T::swap_called == 0);
+ assert(T::move_called == 1);
+ assert(T::move_assign_called == 2);
+ assert(std::get<0>(v1).value == 100);
+ assert(std::get<0>(v2).value == 42);
+ T::reset();
+ swap(v1, v2);
+ assert(T::swap_called == 0);
+ assert(T::move_called == 1);
+ assert(T::move_assign_called == 2);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<0>(v2).value == 100);
+ }
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ {
+ using T = NothrowTypeWithThrowingSwap;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<0>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T::swap_called == 1);
+ assert(T::move_called == 0);
+ assert(T::move_assign_called == 0);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<0>(v2).value == 100);
+ }
+ {
+ using T = ThrowingMoveCtor;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<0>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T::move_called == 1); // call threw
+ assert(T::move_assign_called == 0);
+ assert(std::get<0>(v1).value ==
+ 42); // throw happened before v1 was moved from
+ assert(std::get<0>(v2).value == 100);
+ }
+ {
+ using T = ThrowingMoveAssignNothrowMoveCtor;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<0>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T::move_called == 1);
+ assert(T::move_assign_called == 1); // call threw and didn't complete
+ assert(std::get<0>(v1).value == -1); // v1 was moved from
+ assert(std::get<0>(v2).value == 100);
+ }
+#endif
+}
+
+void test_swap_different_alternatives() {
+ {
+ using T = NothrowMoveCtorWithThrowingSwap;
+ using V = std::variant<T, int>;
+ T::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ v1.swap(v2);
+ assert(T::swap_called == 0);
+ // The libc++ implementation double copies the argument, and not
+ // the variant swap is called on.
+ LIBCPP_ASSERT(T::move_called == 1);
+ assert(T::move_called <= 2);
+ assert(T::move_assign_called == 0);
+ assert(std::get<1>(v1) == 100);
+ assert(std::get<0>(v2).value == 42);
+ T::reset();
+ swap(v1, v2);
+ assert(T::swap_called == 0);
+ LIBCPP_ASSERT(T::move_called == 2);
+ assert(T::move_called <= 2);
+ assert(T::move_assign_called == 0);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<1>(v2) == 100);
+ }
+#ifndef TEST_HAS_NO_EXCEPTIONS
+ {
+ using T1 = ThrowingTypeWithNothrowSwap;
+ using T2 = NonThrowingNonNoexceptType;
+ using V = std::variant<T1, T2>;
+ T1::reset();
+ T2::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T1::swap_called == 0);
+ assert(T1::move_called == 1); // throws
+ assert(T1::move_assign_called == 0);
+ // FIXME: libc++ shouldn't move from T2 here.
+ LIBCPP_ASSERT(T2::move_called == 1);
+ assert(T2::move_called <= 1);
+ assert(std::get<0>(v1).value == 42);
+ if (T2::move_called != 0)
+ assert(v2.valueless_by_exception());
+ else
+ assert(std::get<1>(v2).value == 100);
+ }
+ {
+ using T1 = NonThrowingNonNoexceptType;
+ using T2 = ThrowingTypeWithNothrowSwap;
+ using V = std::variant<T1, T2>;
+ T1::reset();
+ T2::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ LIBCPP_ASSERT(T1::move_called == 0);
+ assert(T1::move_called <= 1);
+ assert(T2::swap_called == 0);
+ assert(T2::move_called == 1); // throws
+ assert(T2::move_assign_called == 0);
+ if (T1::move_called != 0)
+ assert(v1.valueless_by_exception());
+ else
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<1>(v2).value == 100);
+ }
+// FIXME: The tests below are just very libc++ specific
+#ifdef _LIBCPP_VERSION
+ {
+ using T1 = ThrowsOnSecondMove;
+ using T2 = NonThrowingNonNoexceptType;
+ using V = std::variant<T1, T2>;
+ T2::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ v1.swap(v2);
+ assert(T2::move_called == 2);
+ assert(std::get<1>(v1).value == 100);
+ assert(std::get<0>(v2).value == 42);
+ assert(std::get<0>(v2).move_count == 1);
+ }
+ {
+ using T1 = NonThrowingNonNoexceptType;
+ using T2 = ThrowsOnSecondMove;
+ using V = std::variant<T1, T2>;
+ T1::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T1::move_called == 1);
+ assert(v1.valueless_by_exception());
+ assert(std::get<0>(v2).value == 42);
+ }
+#endif
+// testing libc++ extension. If either variant stores a nothrow move
+// constructible type v1.swap(v2) provides the strong exception safety
+// guarantee.
+#ifdef _LIBCPP_VERSION
+ {
+
+ using T1 = ThrowingTypeWithNothrowSwap;
+ using T2 = NothrowMoveable;
+ using V = std::variant<T1, T2>;
+ T1::reset();
+ T2::reset();
+ V v1(std::in_place_index<0>, 42);
+ V v2(std::in_place_index<1>, 100);
+ try {
+ v1.swap(v2);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T1::swap_called == 0);
+ assert(T1::move_called == 1);
+ assert(T1::move_assign_called == 0);
+ assert(T2::swap_called == 0);
+ assert(T2::move_called == 2);
+ assert(T2::move_assign_called == 0);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<1>(v2).value == 100);
+ // swap again, but call v2's swap.
+ T1::reset();
+ T2::reset();
+ try {
+ v2.swap(v1);
+ assert(false);
+ } catch (int) {
+ }
+ assert(T1::swap_called == 0);
+ assert(T1::move_called == 1);
+ assert(T1::move_assign_called == 0);
+ assert(T2::swap_called == 0);
+ assert(T2::move_called == 2);
+ assert(T2::move_assign_called == 0);
+ assert(std::get<0>(v1).value == 42);
+ assert(std::get<1>(v2).value == 100);
+ }
+#endif // _LIBCPP_VERSION
+#endif
+}
+
+template <class Var>
+constexpr auto has_swap_member_imp(int)
+ -> decltype(std::declval<Var &>().swap(std::declval<Var &>()), true) {
+ return true;
+}
+
+template <class Var> constexpr auto has_swap_member_imp(long) -> bool {
+ return false;
+}
+
+template <class Var> constexpr bool has_swap_member() {
+ return has_swap_member_imp<Var>(0);
+}
+
+void test_swap_sfinae() {
+ {
+ // This variant type does not provide either a member or non-member swap
+ // but is still swappable via the generic swap algorithm, since the
+ // variant is move constructible and move assignable.
+ using V = std::variant<int, NotSwappable>;
+ LIBCPP_STATIC_ASSERT(!has_swap_member<V>());
+ static_assert(std::is_swappable_v<V>, "");
+ }
+ {
+ using V = std::variant<int, NotCopyable>;
+ LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");
+ static_assert(!std::is_swappable_v<V>, "");
+ }
+ {
+ using V = std::variant<int, NotCopyableWithSwap>;
+ LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");
+ static_assert(!std::is_swappable_v<V>, "");
+ }
+ {
+ using V = std::variant<int, NotMoveAssignable>;
+ LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");
+ static_assert(!std::is_swappable_v<V>, "");
+ }
+}
+
+void test_swap_noexcept() {
+ {
+ using V = std::variant<int, NothrowMoveable>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ using V = std::variant<int, NothrowMoveCtor>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(!std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ using V = std::variant<int, ThrowingTypeWithNothrowSwap>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(!std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtor>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(!std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtorWithSwap>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ using V = std::variant<int, NotMoveAssignableWithSwap>;
+ static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");
+ static_assert(std::is_nothrow_swappable_v<V>, "");
+ // instantiate swap
+ V v1, v2;
+ v1.swap(v2);
+ swap(v1, v2);
+ }
+ {
+ // This variant type does not provide either a member or non-member swap
+ // but is still swappable via the generic swap algorithm, since the
+ // variant is move constructible and move assignable.
+ using V = std::variant<int, NotSwappable>;
+ LIBCPP_STATIC_ASSERT(!has_swap_member<V>());
+ static_assert(std::is_swappable_v<V>, "");
+ static_assert(std::is_nothrow_swappable_v<V>, "");
+ V v1, v2;
+ swap(v1, v2);
+ }
+}
+
+#ifdef _LIBCPP_VERSION
+// This is why variant should SFINAE member swap. :-)
+template class std::variant<int, NotSwappable>;
+#endif
+
+int main() {
+ test_swap_valueless_by_exception();
+ test_swap_same_alternative();
+ test_swap_different_alternatives();
+ test_swap_sfinae();
+ test_swap_noexcept();
+}