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