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 // constexpr expected& operator=(expected&& rhs) noexcept(see below); 12 // 13 // Constraints: 14 // - is_move_constructible_v<T> is true and 15 // - is_move_assignable_v<T> is true and 16 // - is_move_constructible_v<E> is true and 17 // - is_move_assignable_v<E> is true and 18 // - is_nothrow_move_constructible_v<T> || is_nothrow_move_constructible_v<E> is true. 19 // 20 // Effects: 21 // - If this->has_value() && rhs.has_value() is true, equivalent to val = std::move(*rhs). 22 // - Otherwise, if this->has_value() is true, equivalent to: 23 // reinit-expected(unex, val, std::move(rhs.error())) 24 // - Otherwise, if rhs.has_value() is true, equivalent to: 25 // reinit-expected(val, unex, std::move(*rhs)) 26 // - Otherwise, equivalent to unex = std::move(rhs.error()). 27 // - Then, if no exception was thrown, equivalent to: has_val = rhs.has_value(); return *this; 28 // 29 // Returns: *this. 30 // 31 // Remarks: The exception specification is equivalent to: 32 // is_nothrow_move_assignable_v<T> && is_nothrow_move_constructible_v<T> && 33 // is_nothrow_move_assignable_v<E> && is_nothrow_move_constructible_v<E> 34 35 #include <cassert> 36 #include <concepts> 37 #include <expected> 38 #include <type_traits> 39 #include <utility> 40 41 #include "../../types.h" 42 #include "test_macros.h" 43 44 struct NotMoveConstructible { 45 NotMoveConstructible(NotMoveConstructible&&) = delete; 46 NotMoveConstructible& operator=(NotMoveConstructible&&) = default; 47 }; 48 49 struct NotMoveAssignable { 50 NotMoveAssignable(NotMoveAssignable&&) = default; 51 NotMoveAssignable& operator=(NotMoveAssignable&&) = delete; 52 }; 53 54 struct MoveCtorMayThrow { 55 MoveCtorMayThrow(MoveCtorMayThrow&&) noexcept(false) {} 56 MoveCtorMayThrow& operator=(MoveCtorMayThrow&&) noexcept = default; 57 }; 58 59 // Test constraints 60 static_assert(std::is_move_assignable_v<std::expected<int, int>>); 61 62 // !is_move_assignable_v<T> 63 static_assert(!std::is_move_assignable_v<std::expected<NotMoveAssignable, int>>); 64 65 // !is_move_constructible_v<T> 66 static_assert(!std::is_move_assignable_v<std::expected<NotMoveConstructible, int>>); 67 68 // !is_move_assignable_v<E> 69 static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveAssignable>>); 70 71 // !is_move_constructible_v<E> 72 static_assert(!std::is_move_assignable_v<std::expected<int, NotMoveConstructible>>); 73 74 // !is_nothrow_move_constructible_v<T> && is_nothrow_move_constructible_v<E> 75 static_assert(std::is_move_assignable_v<std::expected<MoveCtorMayThrow, int>>); 76 77 // is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> 78 static_assert(std::is_move_assignable_v<std::expected<int, MoveCtorMayThrow>>); 79 80 // !is_nothrow_move_constructible_v<T> && !is_nothrow_move_constructible_v<E> 81 static_assert(!std::is_move_assignable_v<std::expected<MoveCtorMayThrow, MoveCtorMayThrow>>); 82 83 struct MoveAssignMayThrow { 84 MoveAssignMayThrow(MoveAssignMayThrow&&) noexcept = default; 85 MoveAssignMayThrow& operator=(MoveAssignMayThrow&&) noexcept(false) { return *this; } 86 }; 87 88 // Test noexcept 89 static_assert(std::is_nothrow_move_assignable_v<std::expected<int, int>>); 90 91 // !is_nothrow_move_assignable_v<T> 92 static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveAssignMayThrow, int>>); 93 94 // !is_nothrow_move_constructible_v<T> 95 static_assert(!std::is_nothrow_move_assignable_v<std::expected<MoveCtorMayThrow, int>>); 96 97 // !is_nothrow_move_assignable_v<E> 98 static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveAssignMayThrow>>); 99 100 // !is_nothrow_move_constructible_v<E> 101 static_assert(!std::is_nothrow_move_assignable_v<std::expected<int, MoveCtorMayThrow>>); 102 103 constexpr bool test() { 104 // If this->has_value() && rhs.has_value() is true, equivalent to val = std::move(*rhs). 105 { 106 Traced::state oldState{}; 107 Traced::state newState{}; 108 std::expected<Traced, int> e1(std::in_place, oldState, 5); 109 std::expected<Traced, int> e2(std::in_place, newState, 10); 110 decltype(auto) x = (e1 = std::move(e2)); 111 static_assert(std::same_as<decltype(x), std::expected<Traced, int>&>); 112 assert(&x == &e1); 113 114 assert(e1.has_value()); 115 assert(e1.value().data_ == 10); 116 assert(oldState.moveAssignCalled); 117 } 118 119 // - Otherwise, if this->has_value() is true, equivalent to: 120 // reinit-expected(unex, val, rhs.error()) 121 // E move is not noexcept 122 // In this case, it should call the branch 123 // 124 // U tmp(std::move(oldval)); 125 // destroy_at(addressof(oldval)); 126 // try { 127 // construct_at(addressof(newval), std::forward<Args>(args)...); 128 // } catch (...) { 129 // construct_at(addressof(oldval), std::move(tmp)); 130 // throw; 131 // } 132 // 133 { 134 TracedNoexcept::state oldState{}; 135 Traced::state newState{}; 136 std::expected<TracedNoexcept, Traced> e1(std::in_place, oldState, 5); 137 std::expected<TracedNoexcept, Traced> e2(std::unexpect, newState, 10); 138 139 decltype(auto) x = (e1 = std::move(e2)); 140 static_assert(std::same_as<decltype(x), std::expected<TracedNoexcept, Traced>&>); 141 assert(&x == &e1); 142 143 assert(!e1.has_value()); 144 assert(e1.error().data_ == 10); 145 146 assert(!oldState.moveAssignCalled); 147 assert(oldState.moveCtorCalled); 148 assert(oldState.dtorCalled); 149 assert(!oldState.copyCtorCalled); 150 assert(!newState.copyCtorCalled); 151 assert(newState.moveCtorCalled); 152 assert(!newState.dtorCalled); 153 } 154 155 // - Otherwise, if this->has_value() is true, equivalent to: 156 // reinit-expected(unex, val, rhs.error()) 157 // E move is noexcept 158 // In this case, it should call the branch 159 // 160 // destroy_at(addressof(oldval)); 161 // construct_at(addressof(newval), std::forward<Args>(args)...); 162 // 163 { 164 Traced::state oldState{}; 165 TracedNoexcept::state newState{}; 166 std::expected<Traced, TracedNoexcept> e1(std::in_place, oldState, 5); 167 std::expected<Traced, TracedNoexcept> e2(std::unexpect, newState, 10); 168 169 decltype(auto) x = (e1 = std::move(e2)); 170 static_assert(std::same_as<decltype(x), std::expected<Traced, TracedNoexcept>&>); 171 assert(&x == &e1); 172 173 assert(!e1.has_value()); 174 assert(e1.error().data_ == 10); 175 176 assert(!oldState.moveCtorCalled); 177 assert(oldState.dtorCalled); 178 assert(!oldState.copyCtorCalled); 179 assert(!newState.copyCtorCalled); 180 assert(newState.moveCtorCalled); 181 assert(!newState.dtorCalled); 182 } 183 184 // - Otherwise, if rhs.has_value() is true, equivalent to: 185 // reinit-expected(val, unex, *rhs) 186 // T move is not noexcept 187 // In this case, it should call the branch 188 // 189 // U tmp(std::move(oldval)); 190 // destroy_at(addressof(oldval)); 191 // try { 192 // construct_at(addressof(newval), std::forward<Args>(args)...); 193 // } catch (...) { 194 // construct_at(addressof(oldval), std::move(tmp)); 195 // throw; 196 // } 197 // 198 { 199 TracedNoexcept::state oldState{}; 200 Traced::state newState{}; 201 std::expected<Traced, TracedNoexcept> e1(std::unexpect, oldState, 5); 202 std::expected<Traced, TracedNoexcept> e2(std::in_place, newState, 10); 203 204 decltype(auto) x = (e1 = std::move(e2)); 205 static_assert(std::same_as<decltype(x), std::expected<Traced, TracedNoexcept>&>); 206 assert(&x == &e1); 207 208 assert(e1.has_value()); 209 assert(e1.value().data_ == 10); 210 211 assert(oldState.moveCtorCalled); 212 assert(oldState.dtorCalled); 213 assert(!oldState.copyCtorCalled); 214 assert(!newState.copyCtorCalled); 215 assert(newState.moveCtorCalled); 216 assert(!newState.dtorCalled); 217 } 218 219 // - Otherwise, if rhs.has_value() is true, equivalent to: 220 // reinit-expected(val, unex, *rhs) 221 // T move is noexcept 222 // In this case, it should call the branch 223 // 224 // destroy_at(addressof(oldval)); 225 // construct_at(addressof(newval), std::forward<Args>(args)...); 226 // 227 { 228 Traced::state oldState{}; 229 TracedNoexcept::state newState{}; 230 std::expected<TracedNoexcept, Traced> e1(std::unexpect, oldState, 5); 231 std::expected<TracedNoexcept, Traced> e2(std::in_place, newState, 10); 232 233 decltype(auto) x = (e1 = std::move(e2)); 234 static_assert(std::same_as<decltype(x), std::expected<TracedNoexcept, Traced>&>); 235 assert(&x == &e1); 236 237 assert(e1.has_value()); 238 assert(e1.value().data_ == 10); 239 240 assert(!oldState.moveCtorCalled); 241 assert(oldState.dtorCalled); 242 assert(!oldState.copyCtorCalled); 243 assert(!newState.copyCtorCalled); 244 assert(newState.moveCtorCalled); 245 assert(!newState.dtorCalled); 246 } 247 248 // Otherwise, equivalent to unex = rhs.error(). 249 { 250 Traced::state oldState{}; 251 Traced::state newState{}; 252 std::expected<int, Traced> e1(std::unexpect, oldState, 5); 253 std::expected<int, Traced> e2(std::unexpect, newState, 10); 254 decltype(auto) x = (e1 = std::move(e2)); 255 static_assert(std::same_as<decltype(x), std::expected<int, Traced>&>); 256 assert(&x == &e1); 257 258 assert(!e1.has_value()); 259 assert(e1.error().data_ == 10); 260 assert(oldState.moveAssignCalled); 261 } 262 263 // CheckForInvalidWrites 264 { 265 { 266 CheckForInvalidWrites<true> e1(std::unexpect); 267 CheckForInvalidWrites<true> e2; 268 269 e1 = std::move(e2); 270 271 assert(e1.check()); 272 assert(e2.check()); 273 } 274 { 275 CheckForInvalidWrites<false> e1(std::unexpect); 276 CheckForInvalidWrites<false> e2; 277 278 e1 = std::move(e2); 279 280 assert(e1.check()); 281 assert(e2.check()); 282 } 283 } 284 285 return true; 286 } 287 288 void testException() { 289 #ifndef TEST_HAS_NO_EXCEPTIONS 290 // assign value throw on move 291 { 292 std::expected<ThrowOnMoveConstruct, int> e1(std::unexpect, 5); 293 std::expected<ThrowOnMoveConstruct, int> e2(std::in_place); 294 try { 295 e1 = std::move(e2); 296 assert(false); 297 } catch (Except) { 298 assert(!e1.has_value()); 299 assert(e1.error() == 5); 300 } 301 } 302 303 // assign error throw on move 304 { 305 std::expected<int, ThrowOnMoveConstruct> e1(5); 306 std::expected<int, ThrowOnMoveConstruct> e2(std::unexpect); 307 try { 308 e1 = std::move(e2); 309 assert(false); 310 } catch (Except) { 311 assert(e1.has_value()); 312 assert(*e1 == 5); 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