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(x.swap(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 Constraints: |
22 | struct NotSwappable { |
23 | NotSwappable operator=(const NotSwappable&) = delete; |
24 | }; |
25 | void swap(NotSwappable&, NotSwappable&) = delete; |
26 | |
27 | static_assert(std::is_swappable_v<std::expected<int, int>>); |
28 | |
29 | // !is_swappable_v<T> |
30 | static_assert(!std::is_swappable_v<std::expected<NotSwappable, int>>); |
31 | |
32 | // !is_swappable_v<E> |
33 | static_assert(!std::is_swappable_v<std::expected<int, NotSwappable>>); |
34 | |
35 | struct NotMoveConstructible { |
36 | NotMoveConstructible(NotMoveConstructible&&) = delete; |
37 | friend void swap(NotMoveConstructible&, NotMoveConstructible&) {} |
38 | }; |
39 | |
40 | // !is_move_constructible_v<T> |
41 | static_assert(!std::is_swappable_v<std::expected<NotMoveConstructible, int>>); |
42 | |
43 | // !is_move_constructible_v<E> |
44 | static_assert(!std::is_swappable_v<std::expected<int, NotMoveConstructible>>); |
45 | |
46 | struct MoveMayThrow { |
47 | MoveMayThrow(MoveMayThrow&&) noexcept(false); |
48 | friend void swap(MoveMayThrow&, MoveMayThrow&) noexcept {} |
49 | }; |
50 | |
51 | // !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E> |
52 | static_assert(std::is_swappable_v<std::expected<MoveMayThrow, int>>); |
53 | |
54 | // is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
55 | static_assert(std::is_swappable_v<std::expected<int, MoveMayThrow>>); |
56 | |
57 | // !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
58 | static_assert(!std::is_swappable_v<std::expected<MoveMayThrow, MoveMayThrow>>); |
59 | |
60 | // Test noexcept |
61 | static_assert(std::is_nothrow_swappable_v<std::expected<int, int>>); |
62 | |
63 | // !is_nothrow_move_constructible_v<T> |
64 | static_assert(!std::is_nothrow_swappable_v<std::expected<MoveMayThrow, int>>); |
65 | |
66 | // !is_nothrow_move_constructible_v<E> |
67 | static_assert(!std::is_nothrow_swappable_v<std::expected<int, MoveMayThrow>>); |
68 | |
69 | struct SwapMayThrow { |
70 | friend void swap(SwapMayThrow&, SwapMayThrow&) noexcept(false) {} |
71 | }; |
72 | |
73 | // !is_nothrow_swappable_v<T> |
74 | static_assert(!std::is_nothrow_swappable_v<std::expected<SwapMayThrow, int>>); |
75 | |
76 | // !is_nothrow_swappable_v<E> |
77 | static_assert(!std::is_nothrow_swappable_v<std::expected<int, SwapMayThrow>>); |
78 | |
79 | constexpr bool test() { |
80 | // this->has_value() && rhs.has_value() |
81 | { |
82 | std::expected<ADLSwap, int> x(std::in_place, 5); |
83 | std::expected<ADLSwap, int> y(std::in_place, 10); |
84 | swap(x, y); |
85 | |
86 | assert(x.has_value()); |
87 | assert(x->i == 10); |
88 | assert(x->adlSwapCalled); |
89 | assert(y.has_value()); |
90 | assert(y->i == 5); |
91 | assert(y->adlSwapCalled); |
92 | } |
93 | |
94 | // !this->has_value() && !rhs.has_value() |
95 | { |
96 | std::expected<int, ADLSwap> x(std::unexpect, 5); |
97 | std::expected<int, ADLSwap> y(std::unexpect, 10); |
98 | swap(x, y); |
99 | |
100 | assert(!x.has_value()); |
101 | assert(x.error().i == 10); |
102 | assert(x.error().adlSwapCalled); |
103 | assert(!y.has_value()); |
104 | assert(y.error().i == 5); |
105 | assert(y.error().adlSwapCalled); |
106 | } |
107 | |
108 | // this->has_value() && !rhs.has_value() |
109 | // && is_nothrow_move_constructible_v<E> |
110 | { |
111 | std::expected<TrackedMove<true>, TrackedMove<true>> e1(std::in_place, 5); |
112 | std::expected<TrackedMove<true>, TrackedMove<true>> e2(std::unexpect, 10); |
113 | |
114 | swap(e1, e2); |
115 | |
116 | assert(!e1.has_value()); |
117 | assert(e1.error().i == 10); |
118 | assert(e2.has_value()); |
119 | assert(e2->i == 5); |
120 | |
121 | assert(e1.error().numberOfMoves == 2); |
122 | assert(!e1.error().swapCalled); |
123 | assert(e2->numberOfMoves == 1); |
124 | assert(!e2->swapCalled); |
125 | } |
126 | |
127 | // this->has_value() && !rhs.has_value() |
128 | // && !is_nothrow_move_constructible_v<E> |
129 | { |
130 | std::expected<TrackedMove<true>, TrackedMove<false>> e1(std::in_place, 5); |
131 | std::expected<TrackedMove<true>, TrackedMove<false>> e2(std::unexpect, 10); |
132 | |
133 | swap(e1, e2); |
134 | |
135 | assert(!e1.has_value()); |
136 | assert(e1.error().i == 10); |
137 | assert(e2.has_value()); |
138 | assert(e2->i == 5); |
139 | |
140 | assert(e1.error().numberOfMoves == 1); |
141 | assert(!e1.error().swapCalled); |
142 | assert(e2->numberOfMoves == 2); |
143 | assert(!e2->swapCalled); |
144 | } |
145 | |
146 | // !this->has_value() && rhs.has_value() |
147 | // && is_nothrow_move_constructible_v<E> |
148 | { |
149 | std::expected<TrackedMove<true>, TrackedMove<true>> e1(std::unexpect, 10); |
150 | std::expected<TrackedMove<true>, TrackedMove<true>> e2(std::in_place, 5); |
151 | |
152 | swap(e1, e2); |
153 | |
154 | assert(e1.has_value()); |
155 | assert(e1->i == 5); |
156 | assert(!e2.has_value()); |
157 | assert(e2.error().i == 10); |
158 | |
159 | assert(e1->numberOfMoves == 1); |
160 | assert(!e1->swapCalled); |
161 | assert(e2.error().numberOfMoves == 2); |
162 | assert(!e2.error().swapCalled); |
163 | } |
164 | |
165 | // !this->has_value() && rhs.has_value() |
166 | // && !is_nothrow_move_constructible_v<E> |
167 | { |
168 | std::expected<TrackedMove<true>, TrackedMove<false>> e1(std::unexpect, 10); |
169 | std::expected<TrackedMove<true>, TrackedMove<false>> e2(std::in_place, 5); |
170 | |
171 | swap(e1, e2); |
172 | |
173 | assert(e1.has_value()); |
174 | assert(e1->i == 5); |
175 | assert(!e2.has_value()); |
176 | assert(e2.error().i == 10); |
177 | |
178 | assert(e1->numberOfMoves == 2); |
179 | assert(!e1->swapCalled); |
180 | assert(e2.error().numberOfMoves == 1); |
181 | assert(!e2.error().swapCalled); |
182 | } |
183 | |
184 | // TailClobberer |
185 | { |
186 | // is_nothrow_move_constructible_v<E> |
187 | { |
188 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place); |
189 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect); |
190 | |
191 | swap(x, y); |
192 | |
193 | // Both of these would fail if adjusting the "has value" flags happened |
194 | // _before_ constructing the member objects inside the `swap`. |
195 | assert(!x.has_value()); |
196 | assert(y.has_value()); |
197 | } |
198 | |
199 | // !is_nothrow_move_constructible_v<E> |
200 | { |
201 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place); |
202 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect); |
203 | |
204 | swap(x, y); |
205 | |
206 | // Both of these would fail if adjusting the "has value" flags happened |
207 | // _before_ constructing the member objects inside the `swap`. |
208 | assert(!x.has_value()); |
209 | assert(y.has_value()); |
210 | } |
211 | } |
212 | |
213 | return true; |
214 | } |
215 | |
216 | void testException() { |
217 | #ifndef TEST_HAS_NO_EXCEPTIONS |
218 | // !e1.has_value() && e2.has_value() |
219 | { |
220 | std::expected<ThrowOnMoveConstruct, int> e1(std::unexpect, 5); |
221 | std::expected<ThrowOnMoveConstruct, int> e2(std::in_place); |
222 | try { |
223 | swap(e1, e2); |
224 | assert(false); |
225 | } catch (Except) { |
226 | assert(!e1.has_value()); |
227 | assert(e1.error() == 5); |
228 | } |
229 | } |
230 | |
231 | // e1.has_value() && !e2.has_value() |
232 | { |
233 | std::expected<int, ThrowOnMoveConstruct> e1(5); |
234 | std::expected<int, ThrowOnMoveConstruct> e2(std::unexpect); |
235 | try { |
236 | swap(e1, e2); |
237 | assert(false); |
238 | } catch (Except) { |
239 | assert(e1.has_value()); |
240 | assert(*e1 == 5); |
241 | } |
242 | } |
243 | |
244 | // TailClobberer |
245 | { |
246 | // is_nothrow_move_constructible_v<E> |
247 | { |
248 | std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place); |
249 | std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect); |
250 | try { |
251 | swap(x, y); |
252 | assert(false); |
253 | } catch (Except) { |
254 | assert(x.has_value()); |
255 | // This would fail if `TailClobbererNonTrivialMove<1>` clobbered the |
256 | // flag when rolling back the swap. |
257 | assert(!y.has_value()); |
258 | } |
259 | } |
260 | |
261 | // !is_nothrow_move_constructible_v<E> |
262 | { |
263 | std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place); |
264 | std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect); |
265 | try { |
266 | swap(x, y); |
267 | assert(false); |
268 | } catch (Except) { |
269 | // This would fail if `TailClobbererNonTrivialMove<0>` clobbered the |
270 | // flag when rolling back the swap. |
271 | assert(x.has_value()); |
272 | assert(!y.has_value()); |
273 | } |
274 | } |
275 | } |
276 | #endif // TEST_HAS_NO_EXCEPTIONS |
277 | } |
278 | |
279 | int main(int, char**) { |
280 | test(); |
281 | static_assert(test()); |
282 | testException(); |
283 | return 0; |
284 | } |
285 | |