595 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			595 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===----------------------------------------------------------------------===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| // UNSUPPORTED: c++03, c++11, c++14
 | |
| 
 | |
| // Throwing bad_variant_access is supported starting in macosx10.13
 | |
| // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{9|10|11|12}} && !no-exceptions
 | |
| 
 | |
| // <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.h"
 | |
| #include "test_macros.h"
 | |
| #include "variant_test_helpers.h"
 | |
| 
 | |
| 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(int, char**) {
 | |
|   test_swap_valueless_by_exception();
 | |
|   test_swap_same_alternative();
 | |
|   test_swap_different_alternatives();
 | |
|   test_swap_sfinae();
 | |
|   test_swap_noexcept();
 | |
| 
 | |
|   return 0;
 | |
| }
 |