617 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9// UNSUPPORTED: c++03, c++11, c++1410 11// <variant>12 13// template <class ...Types> class variant;14 15// void swap(variant& rhs) noexcept(see below)16 17#include <cassert>18#include <cstdlib>19#include <string>20#include <type_traits>21#include <variant>22 23#include "test_convertible.h"24#include "test_macros.h"25#include "variant_test_helpers.h"26 27struct NotSwappable {};28void swap(NotSwappable&, NotSwappable&) = delete;29 30struct NotCopyable {31 NotCopyable() = default;32 NotCopyable(const NotCopyable&) = delete;33 NotCopyable& operator=(const NotCopyable&) = delete;34};35 36struct NotCopyableWithSwap {37 NotCopyableWithSwap() = default;38 NotCopyableWithSwap(const NotCopyableWithSwap&) = delete;39 NotCopyableWithSwap& operator=(const NotCopyableWithSwap&) = delete;40};41constexpr void swap(NotCopyableWithSwap&, NotCopyableWithSwap) {}42 43struct NotMoveAssignable {44 NotMoveAssignable() = default;45 NotMoveAssignable(NotMoveAssignable&&) = default;46 NotMoveAssignable& operator=(NotMoveAssignable&&) = delete;47};48 49struct NotMoveAssignableWithSwap {50 NotMoveAssignableWithSwap() = default;51 NotMoveAssignableWithSwap(NotMoveAssignableWithSwap&&) = default;52 NotMoveAssignableWithSwap& operator=(NotMoveAssignableWithSwap&&) = delete;53};54constexpr void swap(NotMoveAssignableWithSwap&, NotMoveAssignableWithSwap&) noexcept {}55 56template <bool Throws>57constexpr void do_throw() {}58 59template <>60void do_throw<true>() {61#ifndef TEST_HAS_NO_EXCEPTIONS62 throw 42;63#else64 std::abort();65#endif66}67 68template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, bool NT_Swap, bool EnableSwap = true>69struct NothrowTypeImp {70 int value;71 int* move_called;72 int* move_assign_called;73 int* swap_called;74 75 constexpr NothrowTypeImp(int v, int* mv_ctr, int* mv_assign, int* swap)76 : value(v), move_called(mv_ctr), move_assign_called(mv_assign), swap_called(swap) {}77 78 NothrowTypeImp(const NothrowTypeImp& o) noexcept(NT_Copy) : value(o.value) { assert(false); } // never called by test79 80 constexpr NothrowTypeImp(NothrowTypeImp&& o) noexcept(NT_Move)81 : value(o.value),82 move_called(o.move_called),83 move_assign_called(o.move_assign_called),84 swap_called(o.swap_called) {85 ++*move_called;86 do_throw<!NT_Move>();87 o.value = -1;88 }89 90 NothrowTypeImp& operator=(const NothrowTypeImp&) noexcept(NT_CopyAssign) {91 assert(false);92 return *this;93 } // never called by the tests94 95 constexpr NothrowTypeImp& operator=(NothrowTypeImp&& o) noexcept(NT_MoveAssign) {96 ++*move_assign_called;97 do_throw<!NT_MoveAssign>();98 value = o.value;99 o.value = -1;100 return *this;101 }102};103 104template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, bool NT_Swap>105constexpr void106swap(NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap, true>& lhs,107 NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap, true>& rhs) noexcept(NT_Swap) {108 ++*lhs.swap_called;109 do_throw<!NT_Swap>();110 std::swap(lhs.value, rhs.value);111}112 113// throwing copy, nothrow move ctor/assign, no swap provided114using NothrowMoveable = NothrowTypeImp<false, true, false, true, false, false>;115// throwing copy and move assign, nothrow move ctor, no swap provided116using NothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>;117// nothrow move ctor, throwing move assignment, swap provided118using NothrowMoveCtorWithThrowingSwap = NothrowTypeImp<false, true, false, false, false, true>;119// throwing move ctor, nothrow move assignment, no swap provided120using ThrowingMoveCtor = NothrowTypeImp<false, false, false, true, false, false>;121// throwing special members, nothrowing swap122using ThrowingTypeWithNothrowSwap = NothrowTypeImp<false, false, false, false, true, true>;123using NothrowTypeWithThrowingSwap = NothrowTypeImp<true, true, true, true, false, true>;124// throwing move assign with nothrow move and nothrow swap125using ThrowingMoveAssignNothrowMoveCtorWithSwap = NothrowTypeImp<false, true, false, false, true, true>;126// throwing move assign with nothrow move but no swap.127using ThrowingMoveAssignNothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>;128 129struct NonThrowingNonNoexceptType {130 int value;131 int* move_called;132 constexpr NonThrowingNonNoexceptType(int v, int* mv_called) : value(v), move_called(mv_called) {}133 constexpr NonThrowingNonNoexceptType(NonThrowingNonNoexceptType&& o) noexcept(false)134 : value(o.value), move_called(o.move_called) {135 ++*move_called;136 o.value = -1;137 }138 NonThrowingNonNoexceptType& operator=(NonThrowingNonNoexceptType&&) noexcept(false) {139 assert(false); // never called by the tests.140 return *this;141 }142};143 144struct ThrowsOnSecondMove {145 int value;146 int move_count;147 ThrowsOnSecondMove(int v) : value(v), move_count(0) {}148 ThrowsOnSecondMove(ThrowsOnSecondMove&& o) noexcept(false) : value(o.value), move_count(o.move_count + 1) {149 if (move_count == 2)150 do_throw<true>();151 o.value = -1;152 }153 ThrowsOnSecondMove& operator=(ThrowsOnSecondMove&&) {154 assert(false); // not called by test155 return *this;156 }157};158 159void test_swap_valueless_by_exception() {160#ifndef TEST_HAS_NO_EXCEPTIONS161 using V = std::variant<int, MakeEmptyT>;162 { // both empty163 V v1;164 makeEmpty(v1);165 V v2;166 makeEmpty(v2);167 assert(MakeEmptyT::alive == 0);168 { // member swap169 v1.swap(v2);170 assert(v1.valueless_by_exception());171 assert(v2.valueless_by_exception());172 assert(MakeEmptyT::alive == 0);173 }174 { // non-member swap175 swap(v1, v2);176 assert(v1.valueless_by_exception());177 assert(v2.valueless_by_exception());178 assert(MakeEmptyT::alive == 0);179 }180 }181 { // only one empty182 V v1(42);183 V v2;184 makeEmpty(v2);185 { // member swap186 v1.swap(v2);187 assert(v1.valueless_by_exception());188 assert(std::get<0>(v2) == 42);189 // swap again190 v2.swap(v1);191 assert(v2.valueless_by_exception());192 assert(std::get<0>(v1) == 42);193 }194 { // non-member swap195 swap(v1, v2);196 assert(v1.valueless_by_exception());197 assert(std::get<0>(v2) == 42);198 // swap again199 swap(v1, v2);200 assert(v2.valueless_by_exception());201 assert(std::get<0>(v1) == 42);202 }203 }204#endif205}206 207TEST_CONSTEXPR_CXX20 void test_swap_same_alternative() {208 {209 using V = std::variant<ThrowingTypeWithNothrowSwap, int>;210 int move_called = 0;211 int move_assign_called = 0;212 int swap_called = 0;213 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);214 V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);215 v1.swap(v2);216 assert(swap_called == 1);217 assert(std::get<0>(v1).value == 100);218 assert(std::get<0>(v2).value == 42);219 swap(v1, v2);220 assert(swap_called == 2);221 assert(std::get<0>(v1).value == 42);222 assert(std::get<0>(v2).value == 100);223 224 assert(move_called == 0);225 assert(move_assign_called == 0);226 }227 {228 using V = std::variant<NothrowMoveable, int>;229 int move_called = 0;230 int move_assign_called = 0;231 int swap_called = 0;232 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);233 V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);234 v1.swap(v2);235 assert(swap_called == 0);236 assert(move_called == 1);237 assert(move_assign_called == 2);238 assert(std::get<0>(v1).value == 100);239 assert(std::get<0>(v2).value == 42);240 241 move_called = 0;242 move_assign_called = 0;243 swap_called = 0;244 245 swap(v1, v2);246 assert(swap_called == 0);247 assert(move_called == 1);248 assert(move_assign_called == 2);249 assert(std::get<0>(v1).value == 42);250 assert(std::get<0>(v2).value == 100);251 }252}253 254void test_swap_same_alternative_throws(){255#ifndef TEST_HAS_NO_EXCEPTIONS256 {using V = std::variant<NothrowTypeWithThrowingSwap, int>;257int move_called = 0;258int move_assign_called = 0;259int swap_called = 0;260V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);261V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);262try {263 v1.swap(v2);264 assert(false);265} catch (int) {266}267assert(swap_called == 1);268assert(move_called == 0);269assert(move_assign_called == 0);270assert(std::get<0>(v1).value == 42);271assert(std::get<0>(v2).value == 100);272}273 274{275 using V = std::variant<ThrowingMoveCtor, int>;276 int move_called = 0;277 int move_assign_called = 0;278 int swap_called = 0;279 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);280 V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);281 try {282 v1.swap(v2);283 assert(false);284 } catch (int) {285 }286 assert(move_called == 1); // call threw287 assert(move_assign_called == 0);288 assert(swap_called == 0);289 assert(std::get<0>(v1).value == 42); // throw happened before v1 was moved from290 assert(std::get<0>(v2).value == 100);291}292{293 using V = std::variant<ThrowingMoveAssignNothrowMoveCtor, int>;294 int move_called = 0;295 int move_assign_called = 0;296 int swap_called = 0;297 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);298 V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);299 try {300 v1.swap(v2);301 assert(false);302 } catch (int) {303 }304 assert(move_called == 1);305 assert(move_assign_called == 1); // call threw and didn't complete306 assert(swap_called == 0);307 assert(std::get<0>(v1).value == -1); // v1 was moved from308 assert(std::get<0>(v2).value == 100);309}310#endif311}312 313TEST_CONSTEXPR_CXX20 void test_swap_different_alternatives() {314 {315 using V = std::variant<NothrowMoveCtorWithThrowingSwap, int>;316 int move_called = 0;317 int move_assign_called = 0;318 int swap_called = 0;319 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);320 V v2(std::in_place_index<1>, 100);321 v1.swap(v2);322 assert(swap_called == 0);323 // The libc++ implementation double copies the argument, and not324 // the variant swap is called on.325 LIBCPP_ASSERT(move_called == 1);326 assert(move_called <= 2);327 assert(move_assign_called == 0);328 assert(std::get<1>(v1) == 100);329 assert(std::get<0>(v2).value == 42);330 331 move_called = 0;332 move_assign_called = 0;333 swap_called = 0;334 335 swap(v1, v2);336 assert(swap_called == 0);337 LIBCPP_ASSERT(move_called == 2);338 assert(move_called <= 2);339 assert(move_assign_called == 0);340 assert(std::get<0>(v1).value == 42);341 assert(std::get<1>(v2) == 100);342 }343}344 345void test_swap_different_alternatives_throws() {346#ifndef TEST_HAS_NO_EXCEPTIONS347 {348 using V = std::variant<ThrowingTypeWithNothrowSwap, NonThrowingNonNoexceptType>;349 int move_called1 = 0;350 int move_assign_called1 = 0;351 int swap_called1 = 0;352 int move_called2 = 0;353 V v1(std::in_place_index<0>, 42, &move_called1, &move_assign_called1, &swap_called1);354 V v2(std::in_place_index<1>, 100, &move_called2);355 try {356 v1.swap(v2);357 assert(false);358 } catch (int) {359 }360 assert(swap_called1 == 0);361 assert(move_called1 == 1); // throws362 assert(move_assign_called1 == 0);363 // FIXME: libc++ shouldn't move from T2 here.364 LIBCPP_ASSERT(move_called2 == 1);365 assert(move_called2 <= 1);366 assert(std::get<0>(v1).value == 42);367 if (move_called2 != 0)368 assert(v2.valueless_by_exception());369 else370 assert(std::get<1>(v2).value == 100);371 }372 {373 using V = std::variant<NonThrowingNonNoexceptType, ThrowingTypeWithNothrowSwap>;374 int move_called1 = 0;375 int move_called2 = 0;376 int move_assign_called2 = 0;377 int swap_called2 = 0;378 V v1(std::in_place_index<0>, 42, &move_called1);379 V v2(std::in_place_index<1>, 100, &move_called2, &move_assign_called2, &swap_called2);380 try {381 v1.swap(v2);382 assert(false);383 } catch (int) {384 }385 LIBCPP_ASSERT(move_called1 == 0);386 assert(move_called1 <= 1);387 assert(swap_called2 == 0);388 assert(move_called2 == 1); // throws389 assert(move_assign_called2 == 0);390 if (move_called1 != 0)391 assert(v1.valueless_by_exception());392 else393 assert(std::get<0>(v1).value == 42);394 assert(std::get<1>(v2).value == 100);395 }396// FIXME: The tests below are just very libc++ specific397# ifdef _LIBCPP_VERSION398 {399 using V = std::variant<ThrowsOnSecondMove, NonThrowingNonNoexceptType>;400 int move_called = 0;401 V v1(std::in_place_index<0>, 42);402 V v2(std::in_place_index<1>, 100, &move_called);403 v1.swap(v2);404 assert(move_called == 2);405 assert(std::get<1>(v1).value == 100);406 assert(std::get<0>(v2).value == 42);407 assert(std::get<0>(v2).move_count == 1);408 }409 {410 using V = std::variant<NonThrowingNonNoexceptType, ThrowsOnSecondMove>;411 int move_called = 0;412 V v1(std::in_place_index<0>, 42, &move_called);413 V v2(std::in_place_index<1>, 100);414 try {415 v1.swap(v2);416 assert(false);417 } catch (int) {418 }419 assert(move_called == 1);420 assert(v1.valueless_by_exception());421 assert(std::get<0>(v2).value == 42);422 }423# endif424 // testing libc++ extension. If either variant stores a nothrow move425 // constructible type v1.swap(v2) provides the strong exception safety426 // guarantee.427# ifdef _LIBCPP_VERSION428 {429 using V = std::variant<ThrowingTypeWithNothrowSwap, NothrowMoveable>;430 int move_called1 = 0;431 int move_assign_called1 = 0;432 int swap_called1 = 0;433 int move_called2 = 0;434 int move_assign_called2 = 0;435 int swap_called2 = 0;436 V v1(std::in_place_index<0>, 42, &move_called1, &move_assign_called1, &swap_called1);437 V v2(std::in_place_index<1>, 100, &move_called2, &move_assign_called2, &swap_called2);438 try {439 v1.swap(v2);440 assert(false);441 } catch (int) {442 }443 assert(swap_called1 == 0);444 assert(move_called1 == 1);445 assert(move_assign_called1 == 0);446 assert(swap_called2 == 0);447 assert(move_called2 == 2);448 assert(move_assign_called2 == 0);449 assert(std::get<0>(v1).value == 42);450 assert(std::get<1>(v2).value == 100);451 // swap again, but call v2's swap.452 453 move_called1 = 0;454 move_assign_called1 = 0;455 swap_called1 = 0;456 move_called2 = 0;457 move_assign_called2 = 0;458 swap_called2 = 0;459 460 try {461 v2.swap(v1);462 assert(false);463 } catch (int) {464 }465 assert(swap_called1 == 0);466 assert(move_called1 == 1);467 assert(move_assign_called1 == 0);468 assert(swap_called2 == 0);469 assert(move_called2 == 2);470 assert(move_assign_called2 == 0);471 assert(std::get<0>(v1).value == 42);472 assert(std::get<1>(v2).value == 100);473 }474# endif // _LIBCPP_VERSION475#endif476}477 478template <class Var>479constexpr auto has_swap_member_imp(int) -> decltype(std::declval<Var&>().swap(std::declval<Var&>()), true) {480 return true;481}482 483template <class Var>484constexpr auto has_swap_member_imp(long) -> bool {485 return false;486}487 488template <class Var>489constexpr bool has_swap_member() {490 return has_swap_member_imp<Var>(0);491}492 493constexpr void test_swap_sfinae() {494 {495 // This variant type does not provide either a member or non-member swap496 // but is still swappable via the generic swap algorithm, since the497 // variant is move constructible and move assignable.498 using V = std::variant<int, NotSwappable>;499 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");500 static_assert(std::is_swappable_v<V>, "");501 }502 {503 using V = std::variant<int, NotCopyable>;504 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");505 static_assert(!std::is_swappable_v<V>, "");506 }507 {508 using V = std::variant<int, NotCopyableWithSwap>;509 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");510 static_assert(!std::is_swappable_v<V>, "");511 }512 {513 using V = std::variant<int, NotMoveAssignable>;514 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");515 static_assert(!std::is_swappable_v<V>, "");516 }517}518 519TEST_CONSTEXPR_CXX20 void test_swap_noexcept() {520 {521 using V = std::variant<int, NothrowMoveable>;522 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");523 static_assert(std::is_nothrow_swappable_v<V>, "");524 // instantiate swap525 V v1, v2;526 v1.swap(v2);527 swap(v1, v2);528 }529 {530 using V = std::variant<int, NothrowMoveCtor>;531 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");532 static_assert(!std::is_nothrow_swappable_v<V>, "");533 // instantiate swap534 V v1, v2;535 v1.swap(v2);536 swap(v1, v2);537 }538 {539 using V = std::variant<int, ThrowingTypeWithNothrowSwap>;540 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");541 static_assert(!std::is_nothrow_swappable_v<V>, "");542 // instantiate swap543 V v1, v2;544 v1.swap(v2);545 swap(v1, v2);546 }547 {548 using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtor>;549 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");550 static_assert(!std::is_nothrow_swappable_v<V>, "");551 // instantiate swap552 V v1, v2;553 v1.swap(v2);554 swap(v1, v2);555 }556 {557 using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtorWithSwap>;558 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");559 static_assert(std::is_nothrow_swappable_v<V>, "");560 // instantiate swap561 V v1, v2;562 v1.swap(v2);563 swap(v1, v2);564 }565 {566 using V = std::variant<int, NotMoveAssignableWithSwap>;567 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), "");568 static_assert(std::is_nothrow_swappable_v<V>, "");569 // instantiate swap570 V v1, v2;571 v1.swap(v2);572 swap(v1, v2);573 }574 {575 // This variant type does not provide either a member or non-member swap576 // but is still swappable via the generic swap algorithm, since the577 // variant is move constructible and move assignable.578 using V = std::variant<int, NotSwappable>;579 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), "");580 static_assert(std::is_swappable_v<V>, "");581 static_assert(std::is_nothrow_swappable_v<V>, "");582 V v1, v2;583 swap(v1, v2);584 }585}586 587#ifdef _LIBCPP_VERSION588// This is why variant should SFINAE member swap. :-)589template class std::variant<int, NotSwappable>;590#endif591 592void non_constexpr_test() {593 test_swap_valueless_by_exception();594 test_swap_same_alternative_throws();595 test_swap_different_alternatives_throws();596}597 598TEST_CONSTEXPR_CXX20 bool test() {599 test_swap_same_alternative();600 test_swap_different_alternatives();601 test_swap_sfinae();602 test_swap_noexcept();603 604 return true;605}606 607int main(int, char**) {608 non_constexpr_test();609 test();610 611#if TEST_STD_VER >= 20612 static_assert(test());613#endif614 615 return 0;616}617