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