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