// -*- 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: with_system_cxx_lib=macosx10.12 && !no-exceptions // XFAIL: with_system_cxx_lib=macosx10.11 && !no-exceptions // XFAIL: with_system_cxx_lib=macosx10.10 && !no-exceptions // XFAIL: with_system_cxx_lib=macosx10.9 && !no-exceptions // // template class variant; // void swap(variant& rhs) noexcept(see below) #include #include #include #include #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 void do_throw() {} template <> void do_throw() { #ifndef TEST_HAS_NO_EXCEPTIONS throw 42; #else std::abort(); #endif } template 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(); 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(); value = o.value; o.value = -1; return *this; } int value; }; template int NothrowTypeImp::move_called = 0; template int NothrowTypeImp::move_assign_called = 0; template int NothrowTypeImp::swap_called = 0; template void swap(NothrowTypeImp &lhs, NothrowTypeImp &rhs) noexcept(NT_Swap) { lhs.swap_called++; do_throw(); int tmp = lhs.value; lhs.value = rhs.value; rhs.value = tmp; } // throwing copy, nothrow move ctor/assign, no swap provided using NothrowMoveable = NothrowTypeImp; // throwing copy and move assign, nothrow move ctor, no swap provided using NothrowMoveCtor = NothrowTypeImp; // nothrow move ctor, throwing move assignment, swap provided using NothrowMoveCtorWithThrowingSwap = NothrowTypeImp; // throwing move ctor, nothrow move assignment, no swap provided using ThrowingMoveCtor = NothrowTypeImp; // throwing special members, nothrowing swap using ThrowingTypeWithNothrowSwap = NothrowTypeImp; using NothrowTypeWithThrowingSwap = NothrowTypeImp; // throwing move assign with nothrow move and nothrow swap using ThrowingMoveAssignNothrowMoveCtorWithSwap = NothrowTypeImp; // throwing move assign with nothrow move but no swap. using ThrowingMoveAssignNothrowMoveCtor = NothrowTypeImp; 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(); 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; { // 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::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::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::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::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::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::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::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::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; 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::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::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 constexpr auto has_swap_member_imp(int) -> decltype(std::declval().swap(std::declval()), true) { return true; } template constexpr auto has_swap_member_imp(long) -> bool { return false; } template constexpr bool has_swap_member() { return has_swap_member_imp(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; LIBCPP_STATIC_ASSERT(!has_swap_member(), ""); static_assert(std::is_swappable_v, ""); } { using V = std::variant; LIBCPP_STATIC_ASSERT(!has_swap_member(), ""); static_assert(!std::is_swappable_v, ""); } { using V = std::variant; LIBCPP_STATIC_ASSERT(!has_swap_member(), ""); static_assert(!std::is_swappable_v, ""); } { using V = std::variant; LIBCPP_STATIC_ASSERT(!has_swap_member(), ""); static_assert(!std::is_swappable_v, ""); } } void test_swap_noexcept() { { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(std::is_nothrow_swappable_v, ""); // instantiate swap V v1, v2; v1.swap(v2); swap(v1, v2); } { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(!std::is_nothrow_swappable_v, ""); // instantiate swap V v1, v2; v1.swap(v2); swap(v1, v2); } { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(!std::is_nothrow_swappable_v, ""); // instantiate swap V v1, v2; v1.swap(v2); swap(v1, v2); } { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(!std::is_nothrow_swappable_v, ""); // instantiate swap V v1, v2; v1.swap(v2); swap(v1, v2); } { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(std::is_nothrow_swappable_v, ""); // instantiate swap V v1, v2; v1.swap(v2); swap(v1, v2); } { using V = std::variant; static_assert(std::is_swappable_v && has_swap_member(), ""); static_assert(std::is_nothrow_swappable_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; LIBCPP_STATIC_ASSERT(!has_swap_member(), ""); static_assert(std::is_swappable_v, ""); static_assert(std::is_nothrow_swappable_v, ""); V v1, v2; swap(v1, v2); } } #ifdef _LIBCPP_VERSION // This is why variant should SFINAE member swap. :-) template class std::variant; #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; }