1 | //===----------------------------------------------------------------------===// |
2 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
3 | // See https://llvm.org/LICENSE.txt for license information. |
4 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
5 | // |
6 | //===----------------------------------------------------------------------===// |
7 | |
8 | // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 |
9 | |
10 | // constexpr void swap(expected& rhs) noexcept(see below); |
11 | // |
12 | // Constraints: |
13 | // is_swappable_v<T> is true and |
14 | // is_swappable_v<E> is true and |
15 | // is_move_constructible_v<T> && is_move_constructible_v<E> is true, and |
16 | // is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E> is true. |
17 | // |
18 | // Throws: Any exception thrown by the expressions in the Effects. |
19 | // Remarks: The exception specification is equivalent to: |
20 | // is_nothrow_move_constructible_v<T> && is_nothrow_swappable_v<T> && |
21 | // is_nothrow_move_constructible_v<E> && is_nothrow_swappable_v<E> |
22 | |
23 | #include <cassert> |
24 | #include <expected> |
25 | #include <type_traits> |
26 | #include <utility> |
27 | |
28 | #include "../../types.h" |
29 | #include "test_macros.h" |
30 | |
31 | // Test Constraints: |
32 | template <class T, class E> |
33 | concept HasMemberSwap = requires(std::expected<T, E> x, std::expected<T, E> y) { x.swap(y); }; |
34 | |
35 | static_assert(HasMemberSwap<int, int>); |
36 | |
37 | struct NotSwappable {}; |
38 | void swap(NotSwappable&, NotSwappable&) = delete; |
39 | |
40 | // !is_swappable_v<T> |
41 | static_assert(!HasMemberSwap<NotSwappable, int>); |
42 | |
43 | // !is_swappable_v<E> |
44 | static_assert(!HasMemberSwap<int, NotSwappable>); |
45 | |
46 | struct NotMoveConstructible { |
47 | NotMoveConstructible(NotMoveConstructible&&) = delete; |
48 | friend void swap(NotMoveConstructible&, NotMoveConstructible&) {} |
49 | }; |
50 | |
51 | // !is_move_constructible_v<T> |
52 | static_assert(!HasMemberSwap<NotMoveConstructible, int>); |
53 | |
54 | // !is_move_constructible_v<E> |
55 | static_assert(!HasMemberSwap<int, NotMoveConstructible>); |
56 | |
57 | struct MoveMayThrow { |
58 | MoveMayThrow(MoveMayThrow&&) noexcept(false); |
59 | friend void swap(MoveMayThrow&, MoveMayThrow&) noexcept {} |
60 | }; |
61 | |
62 | // !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E> |
63 | static_assert(HasMemberSwap<MoveMayThrow, int>); |
64 | |
65 | // is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
66 | static_assert(HasMemberSwap<int, MoveMayThrow>); |
67 | |
68 | // !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> |
69 | static_assert(!HasMemberSwap<MoveMayThrow, MoveMayThrow>); |
70 | |
71 | // Test noexcept |
72 | template <class T, class E> |
73 | concept MemberSwapNoexcept = // |
74 | requires(std::expected<T, E> x, std::expected<T, E> y) { |
75 | { x.swap(y) } noexcept; |
76 | }; |
77 | |
78 | static_assert(MemberSwapNoexcept<int, int>); |
79 | |
80 | // !is_nothrow_move_constructible_v<T> |
81 | static_assert(!MemberSwapNoexcept<MoveMayThrow, int>); |
82 | |
83 | // !is_nothrow_move_constructible_v<E> |
84 | static_assert(!MemberSwapNoexcept<int, MoveMayThrow>); |
85 | |
86 | struct SwapMayThrow { |
87 | friend void swap(SwapMayThrow&, SwapMayThrow&) noexcept(false) {} |
88 | }; |
89 | |
90 | // !is_nothrow_swappable_v<T> |
91 | static_assert(!MemberSwapNoexcept<SwapMayThrow, int>); |
92 | |
93 | // !is_nothrow_swappable_v<E> |
94 | static_assert(!MemberSwapNoexcept<int, SwapMayThrow>); |
95 | |
96 | constexpr bool test() { |
97 | // this->has_value() && rhs.has_value() |
98 | { |
99 | std::expected<ADLSwap, int> x(std::in_place, 5); |
100 | std::expected<ADLSwap, int> y(std::in_place, 10); |
101 | x.swap(y); |
102 | |
103 | assert(x.has_value()); |
104 | assert(x->i == 10); |
105 | assert(x->adlSwapCalled); |
106 | assert(y.has_value()); |
107 | assert(y->i == 5); |
108 | assert(y->adlSwapCalled); |
109 | } |
110 | |
111 | // !this->has_value() && !rhs.has_value() |
112 | { |
113 | std::expected<int, ADLSwap> x(std::unexpect, 5); |
114 | std::expected<int, ADLSwap> y(std::unexpect, 10); |
115 | x.swap(y); |
116 | |
117 | assert(!x.has_value()); |
118 | assert(x.error().i == 10); |
119 | assert(x.error().adlSwapCalled); |
120 | assert(!y.has_value()); |
121 | assert(y.error().i == 5); |
122 | assert(y.error().adlSwapCalled); |
123 | } |
124 | |
125 | // this->has_value() && !rhs.has_value() |
126 | // && is_nothrow_move_constructible_v<E> |
127 | { |
128 | std::expected<TrackedMove<true>, TrackedMove<true>> e1(std::in_place, 5); |
129 | std::expected<TrackedMove<true>, TrackedMove<true>> e2(std::unexpect, 10); |
130 | |
131 | e1.swap(e2); |
132 | |
133 | assert(!e1.has_value()); |
134 | assert(e1.error().i == 10); |
135 | assert(e2.has_value()); |
136 | assert(e2->i == 5); |
137 | |
138 | assert(e1.error().numberOfMoves == 2); |
139 | assert(!e1.error().swapCalled); |
140 | assert(e2->numberOfMoves == 1); |
141 | assert(!e2->swapCalled); |
142 | } |
143 | |
144 | // this->has_value() && !rhs.has_value() |
145 | // && !is_nothrow_move_constructible_v<E> |
146 | { |
147 | std::expected<TrackedMove<true>, TrackedMove<false>> e1(std::in_place, 5); |
148 | std::expected<TrackedMove<true>, TrackedMove<false>> e2(std::unexpect, 10); |
149 | |
150 | e1.swap(e2); |
151 | |
152 | assert(!e1.has_value()); |
153 | assert(e1.error().i == 10); |
154 | assert(e2.has_value()); |
155 | assert(e2->i == 5); |
156 | |
157 | assert(e1.error().numberOfMoves == 1); |
158 | assert(!e1.error().swapCalled); |
159 | assert(e2->numberOfMoves == 2); |
160 | assert(!e2->swapCalled); |
161 | } |
162 | |
163 | // !this->has_value() && rhs.has_value() |
164 | // && is_nothrow_move_constructible_v<E> |
165 | { |
166 | std::expected<TrackedMove<true>, TrackedMove<true>> e1(std::unexpect, 10); |
167 | std::expected<TrackedMove<true>, TrackedMove<true>> e2(std::in_place, 5); |
168 | |
169 | e1.swap(e2); |
170 | |
171 | assert(e1.has_value()); |
172 | assert(e1->i == 5); |
173 | assert(!e2.has_value()); |
174 | assert(e2.error().i == 10); |
175 | |
176 | assert(e1->numberOfMoves == 1); |
177 | assert(!e1->swapCalled); |
178 | assert(e2.error().numberOfMoves == 2); |
179 | assert(!e2.error().swapCalled); |
180 | } |
181 | |
182 | // !this->has_value() && rhs.has_value() |
183 | // && !is_nothrow_move_constructible_v<E> |
184 | { |
185 | std::expected<TrackedMove<true>, TrackedMove<false>> e1(std::unexpect, 10); |
186 | std::expected<TrackedMove<true>, TrackedMove<false>> e2(std::in_place, 5); |
187 | |
188 | e1.swap(e2); |
189 | |
190 | assert(e1.has_value()); |
191 | assert(e1->i == 5); |
192 | assert(!e2.has_value()); |
193 | assert(e2.error().i == 10); |
194 | |
195 | assert(e1->numberOfMoves == 2); |
196 | assert(!e1->swapCalled); |
197 | assert(e2.error().numberOfMoves == 1); |
198 | assert(!e2.error().swapCalled); |
199 | } |
200 | |
201 | // TailClobberer |
202 | { |
203 | // is_nothrow_move_constructible_v<E> |
204 | { |
205 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place); |
206 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect); |
207 | |
208 | x.swap(y); |
209 | |
210 | // Both of these would fail if adjusting the "has value" flags happened |
211 | // _before_ constructing the member objects inside the `swap`. |
212 | assert(!x.has_value()); |
213 | assert(y.has_value()); |
214 | } |
215 | |
216 | // !is_nothrow_move_constructible_v<E> |
217 | { |
218 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place); |
219 | std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect); |
220 | |
221 | x.swap(y); |
222 | |
223 | // Both of these would fail if adjusting the "has value" flags happened |
224 | // _before_ constructing the member objects inside the `swap`. |
225 | assert(!x.has_value()); |
226 | assert(y.has_value()); |
227 | } |
228 | } |
229 | |
230 | // CheckForInvalidWrites |
231 | { |
232 | { |
233 | CheckForInvalidWrites<true> x(std::unexpect); |
234 | CheckForInvalidWrites<true> y; |
235 | |
236 | x.swap(y); |
237 | |
238 | assert(x.check()); |
239 | assert(y.check()); |
240 | } |
241 | { |
242 | CheckForInvalidWrites<false> x(std::unexpect); |
243 | CheckForInvalidWrites<false> y; |
244 | |
245 | x.swap(y); |
246 | |
247 | assert(x.check()); |
248 | assert(y.check()); |
249 | } |
250 | } |
251 | |
252 | return true; |
253 | } |
254 | |
255 | void testException() { |
256 | #ifndef TEST_HAS_NO_EXCEPTIONS |
257 | // !e1.has_value() && e2.has_value() |
258 | { |
259 | std::expected<ThrowOnMoveConstruct, int> e1(std::unexpect, 5); |
260 | std::expected<ThrowOnMoveConstruct, int> e2(std::in_place); |
261 | try { |
262 | e1.swap(e2); |
263 | assert(false); |
264 | } catch (Except) { |
265 | assert(!e1.has_value()); |
266 | assert(e1.error() == 5); |
267 | } |
268 | } |
269 | |
270 | // e1.has_value() && !e2.has_value() |
271 | { |
272 | std::expected<int, ThrowOnMoveConstruct> e1(5); |
273 | std::expected<int, ThrowOnMoveConstruct> e2(std::unexpect); |
274 | try { |
275 | e1.swap(e2); |
276 | assert(false); |
277 | } catch (Except) { |
278 | assert(e1.has_value()); |
279 | assert(*e1 == 5); |
280 | } |
281 | } |
282 | |
283 | // TailClobberer |
284 | { |
285 | // is_nothrow_move_constructible_v<E> |
286 | { |
287 | std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place); |
288 | std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect); |
289 | try { |
290 | x.swap(y); |
291 | assert(false); |
292 | } catch (Except) { |
293 | assert(x.has_value()); |
294 | // This would fail if `TailClobbererNonTrivialMove<1>` clobbered the |
295 | // flag when rolling back the swap. |
296 | assert(!y.has_value()); |
297 | } |
298 | } |
299 | |
300 | // !is_nothrow_move_constructible_v<E> |
301 | { |
302 | std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place); |
303 | std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect); |
304 | try { |
305 | x.swap(y); |
306 | assert(false); |
307 | } catch (Except) { |
308 | // This would fail if `TailClobbererNonTrivialMove<0>` clobbered the |
309 | // flag when rolling back the swap. |
310 | assert(x.has_value()); |
311 | assert(!y.has_value()); |
312 | } |
313 | } |
314 | } |
315 | #endif // TEST_HAS_NO_EXCEPTIONS |
316 | } |
317 | |
318 | int main(int, char**) { |
319 | test(); |
320 | static_assert(test()); |
321 | testException(); |
322 | return 0; |
323 | } |
324 | |