diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d9ebc1..06ca9de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) # Make test executable set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/extensions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/assignment.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp) add_executable(tests ${TEST_SOURCES}) diff --git a/expected.hpp b/expected.hpp index 41ab1a7..dba758e 100644 --- a/expected.hpp +++ b/expected.hpp @@ -399,6 +399,50 @@ template struct expected_storage_base { // TODO, conditionally delete things template struct expected_ctor_base {}; + template ::value && std::is_copy_assignable::value && + std::is_copy_constructible::value && std::is_copy_constructible::value && + std::is_nothrow_move_constructible::value), + bool = (std::is_move_constructible::value && std::is_move_assignable::value && + std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value)> + struct expected_assign_base { + expected_assign_base() = default; + ~expected_assign_base() = default; + expected_assign_base(const expected_assign_base&) = default; + expected_assign_base(expected_assign_base&&) noexcept = default; + expected_assign_base& operator=(const expected_assign_base&) = default; + expected_assign_base& operator=(expected_assign_base&&) noexcept = default; + }; + + template + struct expected_assign_base { + expected_assign_base() = default; + ~expected_assign_base() = default; + expected_assign_base(const expected_assign_base&) = default; + expected_assign_base(expected_assign_base&&) noexcept = default; + expected_assign_base& operator=(const expected_assign_base&) = default; + expected_assign_base& operator=(expected_assign_base&&) noexcept = delete; + }; + + template + struct expected_assign_base { + expected_assign_base() = default; + ~expected_assign_base() = default; + expected_assign_base(const expected_assign_base&) = default; + expected_assign_base(expected_assign_base&&) noexcept = default; + expected_assign_base& operator=(const expected_assign_base&) = delete; + expected_assign_base& operator=(expected_assign_base&&) noexcept = default; + }; + + template + struct expected_assign_base { + expected_assign_base() = default; + ~expected_assign_base() = default; + expected_assign_base(const expected_assign_base&) = default; + expected_assign_base(expected_assign_base&&) noexcept = default; + expected_assign_base& operator=(const expected_assign_base&) = delete; + expected_assign_base& operator=(expected_assign_base&&) noexcept = delete; + }; } // namespace detail template class bad_expected_access : public std::exception { @@ -419,7 +463,9 @@ private: }; template -class expected : private detail::expected_storage_base { +class expected : private detail::expected_storage_base, + private detail::expected_assign_base +{ static_assert(!std::is_reference::value, "T must not be a reference"); static_assert(!std::is_same>::value, "T must not be in_place_t"); @@ -814,13 +860,15 @@ public: expected& operator=(expected&& rhs) { return assign(std::move(rhs)); } + template , detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && std::is_assignable::value && - std::is_nothrow_move_constructible::value)>* = nullptr, detail::enable_if_t::value>* = nullptr> + std::is_nothrow_move_constructible::value)>* = nullptr, + detail::enable_if_t::value>* = nullptr> expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); @@ -864,7 +912,7 @@ public: template ::value && std::is_assignable::value>* = nullptr> + detail::enable_if_t::value && std::is_assignable::value>* = nullptr> expected &operator=(const unexpected &rhs) { if (!has_value()) { err() = rhs; @@ -879,7 +927,7 @@ public: } template ::value && std::is_move_assignable::value>* = nullptr> + detail::enable_if_t::value && std::is_move_assignable::value>* = nullptr> expected &operator=(unexpected && rhs) noexcept { if (!has_value()) { err() = std::move(rhs); diff --git a/tests/assignment.cpp b/tests/assignment.cpp new file mode 100644 index 0000000..ac0514f --- /dev/null +++ b/tests/assignment.cpp @@ -0,0 +1,76 @@ +#include "catch.hpp" +#include "expected.hpp" + +TEST_CASE("Simple assignment", "[assignment.simple]") { + tl::expected e1 = 42; + tl::expected e2 = 17; + tl::expected e3 = 21; + tl::expected e4 = tl::make_unexpected(42); + tl::expected e5 = tl::make_unexpected(17); + tl::expected e6 = tl::make_unexpected(21); + + e1 = e2; + REQUIRE(e1); + REQUIRE(*e1 == 17); + REQUIRE(e2); + REQUIRE(*e2 == 17); + + e1 = std::move(e2); + REQUIRE(e1); + REQUIRE(*e1 == 17); + REQUIRE(e2); + REQUIRE(*e2 == 17); + + e1 = 42; + REQUIRE(e1); + REQUIRE(*e1 == 42); + + auto unex = tl::make_unexpected(12); + e1 = unex; + REQUIRE(!e1); + REQUIRE(e1.error() == 12); + + e1 = tl::make_unexpected(42); + REQUIRE(!e1); + REQUIRE(e1.error() == 42); + + e1 = e3; + REQUIRE(e1); + REQUIRE(*e1 == 21); + + e4 = e5; + REQUIRE(!e4); + REQUIRE(e4.error() == 17); + + e4 = std::move(e6); + REQUIRE(!e4); + REQUIRE(e4.error() == 21); + + e4 = e1; + REQUIRE(e4); + REQUIRE(*e4 == 21); + +} + +TEST_CASE("Assignment deletion", "[assignment.deletion]") { + struct has_all { + has_all() = default; + has_all(const has_all&) = default; + has_all(has_all&&) noexcept = default; + has_all& operator=(const has_all&) = default; + }; + + tl::expected e1 = {}; + tl::expected e2 = {}; + e1 = e2; + + struct except_move { + except_move() = default; + except_move(const except_move&) = default; + except_move(except_move&&) noexcept(false) {}; + except_move& operator=(const except_move&) = default; + }; + tl::expected e3 = {}; + tl::expected e4 = {}; + e3 = e4; +} diff --git a/tests/emplace.cpp b/tests/emplace.cpp new file mode 100644 index 0000000..6169541 --- /dev/null +++ b/tests/emplace.cpp @@ -0,0 +1,4 @@ +#include "catch.hpp" +#include "expected.hpp" + +TEST_CASE("Emplace", "[emplace]") {