forked from mirror/libcxx
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
18 KiB
C++
600 lines
18 KiB
C++
// -*- 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();
|
|
}
|