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