// -*- 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 // XFAIL: availability=macosx10.13 // XFAIL: availability=macosx10.12 // XFAIL: availability=macosx10.11 // XFAIL: availability=macosx10.10 // XFAIL: availability=macosx10.9 // XFAIL: availability=macosx10.8 // XFAIL: availability=macosx10.7 // <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(); }