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