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