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 };
swap(NotCopyableWithSwap &,NotCopyableWithSwap)41 constexpr 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 };
swap(NotMoveAssignableWithSwap &,NotMoveAssignableWithSwap &)54 constexpr void swap(NotMoveAssignableWithSwap&, NotMoveAssignableWithSwap&) noexcept {}
55
56 template <bool Throws>
do_throw()57 constexpr void do_throw() {}
58
59 template <>
do_throw()60 void do_throw<true>() {
61 #ifndef TEST_HAS_NO_EXCEPTIONS
62 throw 42;
63 #else
64 std::abort();
65 #endif
66 }
67
68 template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, bool NT_Swap, bool EnableSwap = true>
69 struct NothrowTypeImp {
70 int value;
71 int* move_called;
72 int* move_assign_called;
73 int* swap_called;
74
NothrowTypeImpNothrowTypeImp75 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
NothrowTypeImpNothrowTypeImp78 NothrowTypeImp(const NothrowTypeImp& o) noexcept(NT_Copy) : value(o.value) { assert(false); } // never called by test
79
NothrowTypeImpNothrowTypeImp80 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
operator =NothrowTypeImp90 NothrowTypeImp& operator=(const NothrowTypeImp&) noexcept(NT_CopyAssign) {
91 assert(false);
92 return *this;
93 } // never called by the tests
94
operator =NothrowTypeImp95 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
104 template <bool NT_Copy, bool NT_Move, bool NT_CopyAssign, bool NT_MoveAssign, bool NT_Swap>
105 constexpr 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)106 swap(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 provided
114 using NothrowMoveable = NothrowTypeImp<false, true, false, true, false, false>;
115 // throwing copy and move assign, nothrow move ctor, no swap provided
116 using NothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>;
117 // nothrow move ctor, throwing move assignment, swap provided
118 using NothrowMoveCtorWithThrowingSwap = NothrowTypeImp<false, true, false, false, false, true>;
119 // throwing move ctor, nothrow move assignment, no swap provided
120 using ThrowingMoveCtor = NothrowTypeImp<false, false, false, true, false, false>;
121 // throwing special members, nothrowing swap
122 using ThrowingTypeWithNothrowSwap = NothrowTypeImp<false, false, false, false, true, true>;
123 using NothrowTypeWithThrowingSwap = NothrowTypeImp<true, true, true, true, false, true>;
124 // throwing move assign with nothrow move and nothrow swap
125 using ThrowingMoveAssignNothrowMoveCtorWithSwap = NothrowTypeImp<false, true, false, false, true, true>;
126 // throwing move assign with nothrow move but no swap.
127 using ThrowingMoveAssignNothrowMoveCtor = NothrowTypeImp<false, true, false, false, false, false>;
128
129 struct NonThrowingNonNoexceptType {
130 int value;
131 int* move_called;
NonThrowingNonNoexceptTypeNonThrowingNonNoexceptType132 constexpr NonThrowingNonNoexceptType(int v, int* mv_called) : value(v), move_called(mv_called) {}
NonThrowingNonNoexceptTypeNonThrowingNonNoexceptType133 constexpr NonThrowingNonNoexceptType(NonThrowingNonNoexceptType&& o) noexcept(false)
134 : value(o.value), move_called(o.move_called) {
135 ++*move_called;
136 o.value = -1;
137 }
operator =NonThrowingNonNoexceptType138 NonThrowingNonNoexceptType& operator=(NonThrowingNonNoexceptType&&) noexcept(false) {
139 assert(false); // never called by the tests.
140 return *this;
141 }
142 };
143
144 struct ThrowsOnSecondMove {
145 int value;
146 int move_count;
ThrowsOnSecondMoveThrowsOnSecondMove147 ThrowsOnSecondMove(int v) : value(v), move_count(0) {}
ThrowsOnSecondMoveThrowsOnSecondMove148 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 }
operator =ThrowsOnSecondMove153 ThrowsOnSecondMove& operator=(ThrowsOnSecondMove&&) {
154 assert(false); // not called by test
155 return *this;
156 }
157 };
158
test_swap_valueless_by_exception()159 void test_swap_valueless_by_exception() {
160 #ifndef TEST_HAS_NO_EXCEPTIONS
161 using V = std::variant<int, MakeEmptyT>;
162 { // both empty
163 V v1;
164 makeEmpty(v1);
165 V v2;
166 makeEmpty(v2);
167 assert(MakeEmptyT::alive == 0);
168 { // member swap
169 v1.swap(v2);
170 assert(v1.valueless_by_exception());
171 assert(v2.valueless_by_exception());
172 assert(MakeEmptyT::alive == 0);
173 }
174 { // non-member swap
175 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 empty
182 V v1(42);
183 V v2;
184 makeEmpty(v2);
185 { // member swap
186 v1.swap(v2);
187 assert(v1.valueless_by_exception());
188 assert(std::get<0>(v2) == 42);
189 // swap again
190 v2.swap(v1);
191 assert(v2.valueless_by_exception());
192 assert(std::get<0>(v1) == 42);
193 }
194 { // non-member swap
195 swap(v1, v2);
196 assert(v1.valueless_by_exception());
197 assert(std::get<0>(v2) == 42);
198 // swap again
199 swap(v1, v2);
200 assert(v2.valueless_by_exception());
201 assert(std::get<0>(v1) == 42);
202 }
203 }
204 #endif
205 }
206
test_swap_same_alternative()207 TEST_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
test_swap_same_alternative_throws()254 void test_swap_same_alternative_throws(){
255 #ifndef TEST_HAS_NO_EXCEPTIONS
256 {using V = std::variant<NothrowTypeWithThrowingSwap, int>;
257 int move_called = 0;
258 int move_assign_called = 0;
259 int swap_called = 0;
260 V v1(std::in_place_index<0>, 42, &move_called, &move_assign_called, &swap_called);
261 V v2(std::in_place_index<0>, 100, &move_called, &move_assign_called, &swap_called);
262 try {
263 v1.swap(v2);
264 assert(false);
265 } catch (int) {
266 }
267 assert(swap_called == 1);
268 assert(move_called == 0);
269 assert(move_assign_called == 0);
270 assert(std::get<0>(v1).value == 42);
271 assert(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 threw
287 assert(move_assign_called == 0);
288 assert(swap_called == 0);
289 assert(std::get<0>(v1).value == 42); // throw happened before v1 was moved from
290 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 complete
306 assert(swap_called == 0);
307 assert(std::get<0>(v1).value == -1); // v1 was moved from
308 assert(std::get<0>(v2).value == 100);
309 }
310 #endif
311 }
312
test_swap_different_alternatives()313 TEST_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 not
324 // 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
test_swap_different_alternatives_throws()345 void test_swap_different_alternatives_throws() {
346 #ifndef TEST_HAS_NO_EXCEPTIONS
347 {
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); // throws
362 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 else
370 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); // throws
389 assert(move_assign_called2 == 0);
390 if (move_called1 != 0)
391 assert(v1.valueless_by_exception());
392 else
393 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++ specific
397 # ifdef _LIBCPP_VERSION
398 {
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 # 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 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_VERSION
475 #endif
476 }
477
478 template <class Var>
has_swap_member_imp(int)479 constexpr auto has_swap_member_imp(int) -> decltype(std::declval<Var&>().swap(std::declval<Var&>()), true) {
480 return true;
481 }
482
483 template <class Var>
has_swap_member_imp(long)484 constexpr auto has_swap_member_imp(long) -> bool {
485 return false;
486 }
487
488 template <class Var>
has_swap_member()489 constexpr bool has_swap_member() {
490 return has_swap_member_imp<Var>(0);
491 }
492
test_swap_sfinae()493 constexpr void test_swap_sfinae() {
494 {
495 // This variant type does not provide either a member or non-member swap
496 // but is still swappable via the generic swap algorithm, since the
497 // 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
test_swap_noexcept()519 TEST_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 swap
525 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 swap
534 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 swap
543 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 swap
552 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 swap
561 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 swap
570 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 swap
576 // but is still swappable via the generic swap algorithm, since the
577 // 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_VERSION
588 // This is why variant should SFINAE member swap. :-)
589 template class std::variant<int, NotSwappable>;
590 #endif
591
non_constexpr_test()592 void non_constexpr_test() {
593 test_swap_valueless_by_exception();
594 test_swap_same_alternative_throws();
595 test_swap_different_alternatives_throws();
596 }
597
test()598 TEST_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
main(int,char **)607 int main(int, char**) {
608 non_constexpr_test();
609 test();
610
611 #if TEST_STD_VER >= 20
612 static_assert(test());
613 #endif
614
615 return 0;
616 }
617