1// -*- C++ -*- 2//===----------------------------------------------------------------------===// 3// 4// The LLVM Compiler Infrastructure 5// 6// This file is dual licensed under the MIT and the University of Illinois Open 7// Source Licenses. See LICENSE.TXT for details. 8// 9//===----------------------------------------------------------------------===// 10 11// UNSUPPORTED: c++98, c++03, c++11, c++14 12 13// <variant> 14 15// template <class ...Types> class variant; 16 17// void swap(variant& rhs) noexcept(see below) 18 19#include <cassert> 20#include <string> 21#include <type_traits> 22#include <variant> 23 24#include "test_convertible.hpp" 25#include "test_macros.h" 26#include "variant_test_helpers.hpp" 27 28struct NotSwappable {}; 29void swap(NotSwappable &, NotSwappable &) = delete; 30 31struct NotCopyable { 32 NotCopyable() = default; 33 NotCopyable(const NotCopyable &) = delete; 34 NotCopyable &operator=(const NotCopyable &) = delete; 35}; 36 37struct NotCopyableWithSwap { 38 NotCopyableWithSwap() = default; 39 NotCopyableWithSwap(const NotCopyableWithSwap &) = delete; 40 NotCopyableWithSwap &operator=(const NotCopyableWithSwap &) = delete; 41}; 42void swap(NotCopyableWithSwap &, NotCopyableWithSwap) {} 43 44struct NotMoveAssignable { 45 NotMoveAssignable() = default; 46 NotMoveAssignable(NotMoveAssignable &&) = default; 47 NotMoveAssignable &operator=(NotMoveAssignable &&) = delete; 48}; 49 50struct NotMoveAssignableWithSwap { 51 NotMoveAssignableWithSwap() = default; 52 NotMoveAssignableWithSwap(NotMoveAssignableWithSwap &&) = default; 53 NotMoveAssignableWithSwap &operator=(NotMoveAssignableWithSwap &&) = delete; 54}; 55void swap(NotMoveAssignableWithSwap &, NotMoveAssignableWithSwap &) noexcept {} 56 57template <bool Throws> void do_throw() {} 58 59template <> void do_throw<true>() { 60#ifndef TEST_HAS_NO_EXCEPTIONS 61 throw 42; 62#else 63 std::abort(); 64#endif 65} 66 67template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, 68 bool NT_Swap, bool EnableSwap = true> 69struct NothrowTypeImp { 70 static int move_called; 71 static int move_assign_called; 72 static int swap_called; 73 static void reset() { move_called = move_assign_called = swap_called = 0; } 74 NothrowTypeImp() = default; 75 explicit NothrowTypeImp(int v) : value(v) {} 76 NothrowTypeImp(const NothrowTypeImp &o) noexcept(NT_Copy) : value(o.value) { 77 assert(false); 78 } // never called by test 79 NothrowTypeImp(NothrowTypeImp &&o) noexcept(NT_Move) : value(o.value) { 80 ++move_called; 81 do_throw<!NT_Move>(); 82 o.value = -1; 83 } 84 NothrowTypeImp &operator=(const NothrowTypeImp &) noexcept(NT_CopyAssign) { 85 assert(false); 86 return *this; 87 } // never called by the tests 88 NothrowTypeImp &operator=(NothrowTypeImp &&o) noexcept(NT_MoveAssign) { 89 ++move_assign_called; 90 do_throw<!NT_MoveAssign>(); 91 value = o.value; 92 o.value = -1; 93 return *this; 94 } 95 int value; 96}; 97template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, 98 bool NT_Swap, bool EnableSwap> 99int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap, 100 EnableSwap>::move_called = 0; 101template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, 102 bool NT_Swap, bool EnableSwap> 103int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap, 104 EnableSwap>::move_assign_called = 0; 105template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, 106 bool NT_Swap, bool EnableSwap> 107int NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, NT_Swap, 108 EnableSwap>::swap_called = 0; 109 110template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, 111 bool NT_Swap> 112void swap(NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, 113 NT_Swap, true> &lhs, 114 NothrowTypeImp<NT_Copy, NT_Move, NT_CopyAssign, NT_MoveAssign, 115 NT_Swap, true> &rhs) noexcept(NT_Swap) { 116 lhs.swap_called++; 117 do_throw<!NT_Swap>(); 118 int tmp = lhs.value; 119 lhs.value = rhs.value; 120 rhs.value = tmp; 121} 122 123// throwing copy, nothrow move ctor/assign, no swap provided 124using NothrowMoveable = NothrowTypeImp<false, true, false, true, false, false>; 125// throwing copy and move assign, nothrow move ctor, no swap provided 126using NothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>; 127// nothrow move ctor, throwing move assignment, swap provided 128using NothrowMoveCtorWithThrowingSwap = 129 NothrowTypeImp<false, true, false, false, false, true>; 130// throwing move ctor, nothrow move assignment, no swap provided 131using ThrowingMoveCtor = 132 NothrowTypeImp<false, false, false, true, false, false>; 133// throwing special members, nothrowing swap 134using ThrowingTypeWithNothrowSwap = 135 NothrowTypeImp<false, false, false, false, true, true>; 136using NothrowTypeWithThrowingSwap = 137 NothrowTypeImp<true, true, true, true, false, true>; 138// throwing move assign with nothrow move and nothrow swap 139using ThrowingMoveAssignNothrowMoveCtorWithSwap = 140 NothrowTypeImp<false, true, false, false, true, true>; 141// throwing move assign with nothrow move but no swap. 142using ThrowingMoveAssignNothrowMoveCtor = 143 NothrowTypeImp<false, true, false, false, false, false>; 144 145struct NonThrowingNonNoexceptType { 146 static int move_called; 147 static void reset() { move_called = 0; } 148 NonThrowingNonNoexceptType() = default; 149 NonThrowingNonNoexceptType(int v) : value(v) {} 150 NonThrowingNonNoexceptType(NonThrowingNonNoexceptType &&o) noexcept(false) 151 : value(o.value) { 152 ++move_called; 153 o.value = -1; 154 } 155 NonThrowingNonNoexceptType & 156 operator=(NonThrowingNonNoexceptType &&) noexcept(false) { 157 assert(false); // never called by the tests. 158 return *this; 159 } 160 int value; 161}; 162int NonThrowingNonNoexceptType::move_called = 0; 163 164struct ThrowsOnSecondMove { 165 int value; 166 int move_count; 167 ThrowsOnSecondMove(int v) : value(v), move_count(0) {} 168 ThrowsOnSecondMove(ThrowsOnSecondMove &&o) noexcept(false) 169 : value(o.value), move_count(o.move_count + 1) { 170 if (move_count == 2) 171 do_throw<true>(); 172 o.value = -1; 173 } 174 ThrowsOnSecondMove &operator=(ThrowsOnSecondMove &&) { 175 assert(false); // not called by test 176 return *this; 177 } 178}; 179 180void test_swap_valueless_by_exception() { 181#ifndef TEST_HAS_NO_EXCEPTIONS 182 using V = std::variant<int, MakeEmptyT>; 183 { // both empty 184 V v1; 185 makeEmpty(v1); 186 V v2; 187 makeEmpty(v2); 188 assert(MakeEmptyT::alive == 0); 189 { // member swap 190 v1.swap(v2); 191 assert(v1.valueless_by_exception()); 192 assert(v2.valueless_by_exception()); 193 assert(MakeEmptyT::alive == 0); 194 } 195 { // non-member swap 196 swap(v1, v2); 197 assert(v1.valueless_by_exception()); 198 assert(v2.valueless_by_exception()); 199 assert(MakeEmptyT::alive == 0); 200 } 201 } 202 { // only one empty 203 V v1(42); 204 V v2; 205 makeEmpty(v2); 206 { // member swap 207 v1.swap(v2); 208 assert(v1.valueless_by_exception()); 209 assert(std::get<0>(v2) == 42); 210 // swap again 211 v2.swap(v1); 212 assert(v2.valueless_by_exception()); 213 assert(std::get<0>(v1) == 42); 214 } 215 { // non-member swap 216 swap(v1, v2); 217 assert(v1.valueless_by_exception()); 218 assert(std::get<0>(v2) == 42); 219 // swap again 220 swap(v1, v2); 221 assert(v2.valueless_by_exception()); 222 assert(std::get<0>(v1) == 42); 223 } 224 } 225#endif 226} 227 228void test_swap_same_alternative() { 229 { 230 using T = ThrowingTypeWithNothrowSwap; 231 using V = std::variant<T, int>; 232 T::reset(); 233 V v1(std::in_place_index<0>, 42); 234 V v2(std::in_place_index<0>, 100); 235 v1.swap(v2); 236 assert(T::swap_called == 1); 237 assert(std::get<0>(v1).value == 100); 238 assert(std::get<0>(v2).value == 42); 239 swap(v1, v2); 240 assert(T::swap_called == 2); 241 assert(std::get<0>(v1).value == 42); 242 assert(std::get<0>(v2).value == 100); 243 } 244 { 245 using T = NothrowMoveable; 246 using V = std::variant<T, int>; 247 T::reset(); 248 V v1(std::in_place_index<0>, 42); 249 V v2(std::in_place_index<0>, 100); 250 v1.swap(v2); 251 assert(T::swap_called == 0); 252 assert(T::move_called == 1); 253 assert(T::move_assign_called == 2); 254 assert(std::get<0>(v1).value == 100); 255 assert(std::get<0>(v2).value == 42); 256 T::reset(); 257 swap(v1, v2); 258 assert(T::swap_called == 0); 259 assert(T::move_called == 1); 260 assert(T::move_assign_called == 2); 261 assert(std::get<0>(v1).value == 42); 262 assert(std::get<0>(v2).value == 100); 263 } 264#ifndef TEST_HAS_NO_EXCEPTIONS 265 { 266 using T = NothrowTypeWithThrowingSwap; 267 using V = std::variant<T, int>; 268 T::reset(); 269 V v1(std::in_place_index<0>, 42); 270 V v2(std::in_place_index<0>, 100); 271 try { 272 v1.swap(v2); 273 assert(false); 274 } catch (int) { 275 } 276 assert(T::swap_called == 1); 277 assert(T::move_called == 0); 278 assert(T::move_assign_called == 0); 279 assert(std::get<0>(v1).value == 42); 280 assert(std::get<0>(v2).value == 100); 281 } 282 { 283 using T = ThrowingMoveCtor; 284 using V = std::variant<T, int>; 285 T::reset(); 286 V v1(std::in_place_index<0>, 42); 287 V v2(std::in_place_index<0>, 100); 288 try { 289 v1.swap(v2); 290 assert(false); 291 } catch (int) { 292 } 293 assert(T::move_called == 1); // call threw 294 assert(T::move_assign_called == 0); 295 assert(std::get<0>(v1).value == 296 42); // throw happened before v1 was moved from 297 assert(std::get<0>(v2).value == 100); 298 } 299 { 300 using T = ThrowingMoveAssignNothrowMoveCtor; 301 using V = std::variant<T, int>; 302 T::reset(); 303 V v1(std::in_place_index<0>, 42); 304 V v2(std::in_place_index<0>, 100); 305 try { 306 v1.swap(v2); 307 assert(false); 308 } catch (int) { 309 } 310 assert(T::move_called == 1); 311 assert(T::move_assign_called == 1); // call threw and didn't complete 312 assert(std::get<0>(v1).value == -1); // v1 was moved from 313 assert(std::get<0>(v2).value == 100); 314 } 315#endif 316} 317 318void test_swap_different_alternatives() { 319 { 320 using T = NothrowMoveCtorWithThrowingSwap; 321 using V = std::variant<T, int>; 322 T::reset(); 323 V v1(std::in_place_index<0>, 42); 324 V v2(std::in_place_index<1>, 100); 325 v1.swap(v2); 326 assert(T::swap_called == 0); 327 // The libc++ implementation double copies the argument, and not 328 // the variant swap is called on. 329 LIBCPP_ASSERT(T::move_called == 1); 330 assert(T::move_called <= 2); 331 assert(T::move_assign_called == 0); 332 assert(std::get<1>(v1) == 100); 333 assert(std::get<0>(v2).value == 42); 334 T::reset(); 335 swap(v1, v2); 336 assert(T::swap_called == 0); 337 LIBCPP_ASSERT(T::move_called == 2); 338 assert(T::move_called <= 2); 339 assert(T::move_assign_called == 0); 340 assert(std::get<0>(v1).value == 42); 341 assert(std::get<1>(v2) == 100); 342 } 343#ifndef TEST_HAS_NO_EXCEPTIONS 344 { 345 using T1 = ThrowingTypeWithNothrowSwap; 346 using T2 = NonThrowingNonNoexceptType; 347 using V = std::variant<T1, T2>; 348 T1::reset(); 349 T2::reset(); 350 V v1(std::in_place_index<0>, 42); 351 V v2(std::in_place_index<1>, 100); 352 try { 353 v1.swap(v2); 354 assert(false); 355 } catch (int) { 356 } 357 assert(T1::swap_called == 0); 358 assert(T1::move_called == 1); // throws 359 assert(T1::move_assign_called == 0); 360 // FIXME: libc++ shouldn't move from T2 here. 361 LIBCPP_ASSERT(T2::move_called == 1); 362 assert(T2::move_called <= 1); 363 assert(std::get<0>(v1).value == 42); 364 if (T2::move_called != 0) 365 assert(v2.valueless_by_exception()); 366 else 367 assert(std::get<1>(v2).value == 100); 368 } 369 { 370 using T1 = NonThrowingNonNoexceptType; 371 using T2 = ThrowingTypeWithNothrowSwap; 372 using V = std::variant<T1, T2>; 373 T1::reset(); 374 T2::reset(); 375 V v1(std::in_place_index<0>, 42); 376 V v2(std::in_place_index<1>, 100); 377 try { 378 v1.swap(v2); 379 assert(false); 380 } catch (int) { 381 } 382 LIBCPP_ASSERT(T1::move_called == 0); 383 assert(T1::move_called <= 1); 384 assert(T2::swap_called == 0); 385 assert(T2::move_called == 1); // throws 386 assert(T2::move_assign_called == 0); 387 if (T1::move_called != 0) 388 assert(v1.valueless_by_exception()); 389 else 390 assert(std::get<0>(v1).value == 42); 391 assert(std::get<1>(v2).value == 100); 392 } 393// FIXME: The tests below are just very libc++ specific 394#ifdef _LIBCPP_VERSION 395 { 396 using T1 = ThrowsOnSecondMove; 397 using T2 = NonThrowingNonNoexceptType; 398 using V = std::variant<T1, T2>; 399 T2::reset(); 400 V v1(std::in_place_index<0>, 42); 401 V v2(std::in_place_index<1>, 100); 402 v1.swap(v2); 403 assert(T2::move_called == 2); 404 assert(std::get<1>(v1).value == 100); 405 assert(std::get<0>(v2).value == 42); 406 assert(std::get<0>(v2).move_count == 1); 407 } 408 { 409 using T1 = NonThrowingNonNoexceptType; 410 using T2 = ThrowsOnSecondMove; 411 using V = std::variant<T1, T2>; 412 T1::reset(); 413 V v1(std::in_place_index<0>, 42); 414 V v2(std::in_place_index<1>, 100); 415 try { 416 v1.swap(v2); 417 assert(false); 418 } catch (int) { 419 } 420 assert(T1::move_called == 1); 421 assert(v1.valueless_by_exception()); 422 assert(std::get<0>(v2).value == 42); 423 } 424#endif 425// testing libc++ extension. If either variant stores a nothrow move 426// constructible type v1.swap(v2) provides the strong exception safety 427// guarantee. 428#ifdef _LIBCPP_VERSION 429 { 430 431 using T1 = ThrowingTypeWithNothrowSwap; 432 using T2 = NothrowMoveable; 433 using V = std::variant<T1, T2>; 434 T1::reset(); 435 T2::reset(); 436 V v1(std::in_place_index<0>, 42); 437 V v2(std::in_place_index<1>, 100); 438 try { 439 v1.swap(v2); 440 assert(false); 441 } catch (int) { 442 } 443 assert(T1::swap_called == 0); 444 assert(T1::move_called == 1); 445 assert(T1::move_assign_called == 0); 446 assert(T2::swap_called == 0); 447 assert(T2::move_called == 2); 448 assert(T2::move_assign_called == 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 T1::reset(); 453 T2::reset(); 454 try { 455 v2.swap(v1); 456 assert(false); 457 } catch (int) { 458 } 459 assert(T1::swap_called == 0); 460 assert(T1::move_called == 1); 461 assert(T1::move_assign_called == 0); 462 assert(T2::swap_called == 0); 463 assert(T2::move_called == 2); 464 assert(T2::move_assign_called == 0); 465 assert(std::get<0>(v1).value == 42); 466 assert(std::get<1>(v2).value == 100); 467 } 468#endif // _LIBCPP_VERSION 469#endif 470} 471 472template <class Var> 473constexpr auto has_swap_member_imp(int) 474 -> decltype(std::declval<Var &>().swap(std::declval<Var &>()), true) { 475 return true; 476} 477 478template <class Var> constexpr auto has_swap_member_imp(long) -> bool { 479 return false; 480} 481 482template <class Var> constexpr bool has_swap_member() { 483 return has_swap_member_imp<Var>(0); 484} 485 486void test_swap_sfinae() { 487 { 488 // This variant type does not provide either a member or non-member swap 489 // but is still swappable via the generic swap algorithm, since the 490 // variant is move constructible and move assignable. 491 using V = std::variant<int, NotSwappable>; 492 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), ""); 493 static_assert(std::is_swappable_v<V>, ""); 494 } 495 { 496 using V = std::variant<int, NotCopyable>; 497 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), ""); 498 static_assert(!std::is_swappable_v<V>, ""); 499 } 500 { 501 using V = std::variant<int, NotCopyableWithSwap>; 502 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), ""); 503 static_assert(!std::is_swappable_v<V>, ""); 504 } 505 { 506 using V = std::variant<int, NotMoveAssignable>; 507 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), ""); 508 static_assert(!std::is_swappable_v<V>, ""); 509 } 510} 511 512void test_swap_noexcept() { 513 { 514 using V = std::variant<int, NothrowMoveable>; 515 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 516 static_assert(std::is_nothrow_swappable_v<V>, ""); 517 // instantiate swap 518 V v1, v2; 519 v1.swap(v2); 520 swap(v1, v2); 521 } 522 { 523 using V = std::variant<int, NothrowMoveCtor>; 524 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 525 static_assert(!std::is_nothrow_swappable_v<V>, ""); 526 // instantiate swap 527 V v1, v2; 528 v1.swap(v2); 529 swap(v1, v2); 530 } 531 { 532 using V = std::variant<int, ThrowingTypeWithNothrowSwap>; 533 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 534 static_assert(!std::is_nothrow_swappable_v<V>, ""); 535 // instantiate swap 536 V v1, v2; 537 v1.swap(v2); 538 swap(v1, v2); 539 } 540 { 541 using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtor>; 542 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 543 static_assert(!std::is_nothrow_swappable_v<V>, ""); 544 // instantiate swap 545 V v1, v2; 546 v1.swap(v2); 547 swap(v1, v2); 548 } 549 { 550 using V = std::variant<int, ThrowingMoveAssignNothrowMoveCtorWithSwap>; 551 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 552 static_assert(std::is_nothrow_swappable_v<V>, ""); 553 // instantiate swap 554 V v1, v2; 555 v1.swap(v2); 556 swap(v1, v2); 557 } 558 { 559 using V = std::variant<int, NotMoveAssignableWithSwap>; 560 static_assert(std::is_swappable_v<V> && has_swap_member<V>(), ""); 561 static_assert(std::is_nothrow_swappable_v<V>, ""); 562 // instantiate swap 563 V v1, v2; 564 v1.swap(v2); 565 swap(v1, v2); 566 } 567 { 568 // This variant type does not provide either a member or non-member swap 569 // but is still swappable via the generic swap algorithm, since the 570 // variant is move constructible and move assignable. 571 using V = std::variant<int, NotSwappable>; 572 LIBCPP_STATIC_ASSERT(!has_swap_member<V>(), ""); 573 static_assert(std::is_swappable_v<V>, ""); 574 static_assert(std::is_nothrow_swappable_v<V>, ""); 575 V v1, v2; 576 swap(v1, v2); 577 } 578} 579 580#ifdef _LIBCPP_VERSION 581// This is why variant should SFINAE member swap. :-) 582template class std::variant<int, NotSwappable>; 583#endif 584 585int main() { 586 test_swap_valueless_by_exception(); 587 test_swap_same_alternative(); 588 test_swap_different_alternatives(); 589 test_swap_sfinae(); 590 test_swap_noexcept(); 591} 592