123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- // -*- 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++98, c++03, c++11, c++14
- // XFAIL: dylib-has-no-bad_variant_access && !libcpp-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;
- }
|