xref: /llvm-project/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp (revision 6a54dfbfe534276d644d7f9c027f0deeb748dd53)
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, c++17, c++20
10 
11 // friend constexpr void swap(expected& x, expected& y) noexcept(noexcept(swap(x,y)));
12 
13 #include <cassert>
14 #include <expected>
15 #include <type_traits>
16 #include <utility>
17 
18 #include "../../types.h"
19 #include "test_macros.h"
20 
21 // Test constraint
22 static_assert(std::is_swappable_v<std::expected<void, int>>);
23 
24 struct NotSwappable {
25   NotSwappable& operator=(const NotSwappable&) = delete;
26 };
27 void swap(NotSwappable&, NotSwappable&) = delete;
28 
29 // !is_swappable_v<E>
30 static_assert(!std::is_swappable_v<std::expected<void, NotSwappable>>);
31 
32 struct NotMoveConstructible {
33   NotMoveConstructible(NotMoveConstructible&&) = delete;
34   friend void swap(NotMoveConstructible&, NotMoveConstructible&) {}
35 };
36 
37 // !is_move_constructible_v<E>
38 static_assert(!std::is_swappable_v<std::expected<void, NotMoveConstructible>>);
39 
40 // Test noexcept
41 struct MoveMayThrow {
42   MoveMayThrow(MoveMayThrow&&) noexcept(false);
43   friend void swap(MoveMayThrow&, MoveMayThrow&) noexcept {}
44 };
45 
46 template <class E>
47 concept FreeSwapNoexcept =
48     requires(std::expected<void, E> x, std::expected<void, E> y) {
49       { swap(x, y) } noexcept;
50     };
51 
52 static_assert(FreeSwapNoexcept<int>);
53 
54 // !is_nothrow_move_constructible_v<E>
55 static_assert(!FreeSwapNoexcept<MoveMayThrow>);
56 
57 struct SwapMayThrow {
58   friend void swap(SwapMayThrow&, SwapMayThrow&) noexcept(false) {}
59 };
60 
61 // !is_nothrow_swappable_v<E>
62 static_assert(!FreeSwapNoexcept<SwapMayThrow>);
63 
64 constexpr bool test() {
65   // this->has_value() && rhs.has_value()
66   {
67     std::expected<void, int> x;
68     std::expected<void, int> y;
69     swap(x, y);
70 
71     assert(x.has_value());
72     assert(y.has_value());
73   }
74 
75   // !this->has_value() && !rhs.has_value()
76   {
77     std::expected<void, ADLSwap> x(std::unexpect, 5);
78     std::expected<void, ADLSwap> y(std::unexpect, 10);
79     swap(x, y);
80 
81     assert(!x.has_value());
82     assert(x.error().i == 10);
83     assert(x.error().adlSwapCalled);
84     assert(!y.has_value());
85     assert(y.error().i == 5);
86     assert(y.error().adlSwapCalled);
87   }
88 
89   // this->has_value() && !rhs.has_value()
90   {
91     Traced::state s{};
92     std::expected<void, Traced> e1(std::in_place);
93     std::expected<void, Traced> e2(std::unexpect, s, 10);
94 
95     swap(e1, e2);
96 
97     assert(!e1.has_value());
98     assert(e1.error().data_ == 10);
99     assert(e2.has_value());
100 
101     assert(s.moveCtorCalled);
102     assert(s.dtorCalled);
103   }
104 
105   // !this->has_value() && rhs.has_value()
106   {
107     Traced::state s{};
108     std::expected<void, Traced> e1(std::unexpect, s, 10);
109     std::expected<void, Traced> e2(std::in_place);
110 
111     swap(e1, e2);
112 
113     assert(e1.has_value());
114     assert(!e2.has_value());
115     assert(e2.error().data_ == 10);
116 
117     assert(s.moveCtorCalled);
118     assert(s.dtorCalled);
119   }
120 
121   // TailClobberer
122   {
123     std::expected<void, TailClobbererNonTrivialMove<1>> x(std::in_place);
124     std::expected<void, TailClobbererNonTrivialMove<1>> y(std::unexpect);
125 
126     swap(x, y);
127 
128     // The next line would fail if adjusting the "has value" flag happened
129     // _before_ constructing the member object inside the `swap`.
130     assert(!x.has_value());
131     assert(y.has_value());
132   }
133 
134   return true;
135 }
136 
137 void testException() {
138 #ifndef TEST_HAS_NO_EXCEPTIONS
139   // !e1.has_value() && e2.has_value()
140   {
141     bool e1Destroyed = false;
142     std::expected<void, ThrowOnMove> e1(std::unexpect, e1Destroyed);
143     std::expected<void, ThrowOnMove> e2(std::in_place);
144     try {
145       swap(e1, e2);
146       assert(false);
147     } catch (Except) {
148       assert(!e1.has_value());
149       assert(e2.has_value());
150       assert(!e1Destroyed);
151     }
152   }
153 
154   // e1.has_value() && !e2.has_value()
155   {
156     bool e2Destroyed = false;
157     std::expected<void, ThrowOnMove> e1(std::in_place);
158     std::expected<void, ThrowOnMove> e2(std::unexpect, e2Destroyed);
159     try {
160       swap(e1, e2);
161       assert(false);
162     } catch (Except) {
163       assert(e1.has_value());
164       assert(!e2.has_value());
165       assert(!e2Destroyed);
166     }
167   }
168 
169   // TailClobberer
170   {
171     std::expected<void, TailClobbererNonTrivialMove<0, false, true>> x(std::in_place);
172     std::expected<void, TailClobbererNonTrivialMove<0, false, true>> y(std::unexpect);
173     try {
174       swap(x, y);
175       assert(false);
176     } catch (Except) {
177       // This would fail if `TailClobbererNonTrivialMove<0, false, true>`
178       // clobbered the flag before throwing the exception.
179       assert(x.has_value());
180       assert(!y.has_value());
181     }
182   }
183 #endif // TEST_HAS_NO_EXCEPTIONS
184 }
185 
186 int main(int, char**) {
187   test();
188   static_assert(test());
189   testException();
190   return 0;
191 }
192