diff --git a/README.md b/README.md index 6759c22..67ad343 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Implemented LWG Issues: - [x] [LWG-3886](https://wg21.link/lwg3886) Monad mo' problems - [x] [LWG-4031](https://wg21.link/lwg4031) `bad_expected_access` member functions should be noexcept - [x] [LWG-4222](https://wg21.link/lwg4222) `expected` constructor from a single value missing a constraint +- [x] [LWG-4025](https://wg21.link/lwg4025) Move assignment operator of `std::expected` should not be conditionally deleted Enhancements: diff --git a/tests/test_expected/CMakeLists.txt b/tests/test_expected/CMakeLists.txt index 01cc0d2..2112537 100644 --- a/tests/test_expected/CMakeLists.txt +++ b/tests/test_expected/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES lwg_3886_tests.cpp lwg_4031_tests.cpp lwg_4222_tests.cpp + lwg_4025_tests.cpp ) find_package(Catch2 3 REQUIRED) diff --git a/tests/test_expected/lwg_4025_tests.cpp b/tests/test_expected/lwg_4025_tests.cpp new file mode 100644 index 0000000..2942aec --- /dev/null +++ b/tests/test_expected/lwg_4025_tests.cpp @@ -0,0 +1,259 @@ +// LWG-4025 requires that operator=(expected&&) for expected be +// constrained rather than conditionally deleted. The existing implementation +// already satisfies this via [class.copy.assign]/7 (CWG 1402): the base +// class's =delete causes expected::operator=(expected&&) = default +// to be defined as deleted, and a defaulted move assignment that is defined +// as deleted is ignored by overload resolution — falling back to copy +// assignment when E is copyable but not movable. + +#include + +#include + +using namespace zeus; + +namespace +{ + +struct CopyableNotMovable +{ + CopyableNotMovable() = default; + CopyableNotMovable(const CopyableNotMovable &) = default; + CopyableNotMovable &operator=(const CopyableNotMovable &) = default; + CopyableNotMovable(CopyableNotMovable &&) = delete; + CopyableNotMovable &operator=(CopyableNotMovable &&) = delete; +}; + +struct NotCopyableNotMovable +{ + NotCopyableNotMovable() = default; + NotCopyableNotMovable(const NotCopyableNotMovable &) = delete; + NotCopyableNotMovable &operator=(const NotCopyableNotMovable &) = delete; + NotCopyableNotMovable(NotCopyableNotMovable &&) = delete; + NotCopyableNotMovable &operator=(NotCopyableNotMovable &&) = delete; +}; + +struct MoveOnly +{ + int value; + MoveOnly() : value(0) {} + MoveOnly(int v) : value(v) {} + MoveOnly(const MoveOnly &) = delete; + MoveOnly &operator=(const MoveOnly &) = delete; + MoveOnly(MoveOnly &&) = default; + MoveOnly &operator=(MoveOnly &&) = default; +}; + +struct StatefulCopyableNotMovable +{ + int value; + StatefulCopyableNotMovable() : value(0) {} + StatefulCopyableNotMovable(int v) : value(v) {} + StatefulCopyableNotMovable(const StatefulCopyableNotMovable &) = default; + StatefulCopyableNotMovable &operator=(const StatefulCopyableNotMovable &) = default; + StatefulCopyableNotMovable(StatefulCopyableNotMovable &&) = delete; + StatefulCopyableNotMovable &operator=(StatefulCopyableNotMovable &&) = delete; +}; + +} // namespace + +TEST_CASE("expected is move-assignable when E is movable", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); +} + +TEST_CASE("expected is trivially move-assignable when E is trivial", "[LWG-4025][LWG-4026]") +{ + STATIC_REQUIRE(std::is_trivially_move_assignable_v>); + STATIC_REQUIRE(std::is_nothrow_move_assignable_v>); +} + +TEST_CASE("expected is move-assignable via copy fallback when E is copyable but not movable", + "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); + STATIC_REQUIRE(std::is_trivially_move_assignable_v>); + STATIC_REQUIRE(std::is_nothrow_move_assignable_v>); +} + +TEST_CASE("expected copy fallback: both sides have value", "[LWG-4025]") +{ + expected e1; + expected e2; + e2 = std::move(e1); + CHECK(e2.has_value()); +} + +TEST_CASE("expected copy fallback: this has error, rhs has value", "[LWG-4025]") +{ + expected e1; + expected e2(unexpect); + e2 = std::move(e1); + CHECK(e2.has_value()); +} + +TEST_CASE("expected copy fallback: both sides have error", "[LWG-4025]") +{ + expected e1(unexpect); + expected e2(unexpect); + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); +} + +TEST_CASE("expected is not move-assignable when E is neither copyable nor movable", "[LWG-4025]") +{ + STATIC_REQUIRE_FALSE(std::is_move_assignable_v>); + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected is copy-assignable when E is copyable but not movable", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected is move-assignable but not copy-assignable when E is move-only", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected move assignment works when E is movable", "[LWG-4025]") +{ + expected e1(unexpect, 42); + expected e2; + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error() == 42); +} + +TEST_CASE("expected move assignment works when E is move-only", "[LWG-4025]") +{ + expected e1(unexpect, 42); + expected e2; + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error().value == 42); +} + +TEST_CASE("expected copy fallback: this has value, rhs has error", "[LWG-4025]") +{ + expected e1(unexpect); + expected e2; + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); +} + +TEST_CASE("expected copy fallback propagates error value", "[LWG-4025]") +{ + expected e1(unexpect, 42); + expected e2; + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error().value == 42); +} + +TEST_CASE("expected copy fallback propagates error value (both have error)", "[LWG-4025]") +{ + expected e1(unexpect, 10); + expected e2(unexpect, 20); + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error().value == 10); +} + +TEST_CASE("expected is move-assignable when E is movable", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); +} + +TEST_CASE("expected is trivially move-assignable when E is trivial", "[LWG-4025][LWG-4026]") +{ + STATIC_REQUIRE(std::is_trivially_move_assignable_v>); + STATIC_REQUIRE(std::is_nothrow_move_assignable_v>); +} + +TEST_CASE("expected is move-assignable via copy fallback when E is copyable but not movable", + "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); + STATIC_REQUIRE(std::is_trivially_move_assignable_v>); + STATIC_REQUIRE(std::is_nothrow_move_assignable_v>); +} + +TEST_CASE("expected is not move-assignable when E is neither copyable nor movable", "[LWG-4025]") +{ + STATIC_REQUIRE_FALSE(std::is_move_assignable_v>); + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected is copy-assignable when E is copyable but not movable", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected is move-assignable but not copy-assignable when E is move-only", "[LWG-4025]") +{ + STATIC_REQUIRE(std::is_move_assignable_v>); + STATIC_REQUIRE_FALSE(std::is_copy_assignable_v>); +} + +TEST_CASE("expected move assignment works when E is movable", "[LWG-4025]") +{ + expected e1(unexpect, 42); + expected e2; + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error() == 42); +} + +TEST_CASE("expected copy fallback: both sides have value", "[LWG-4025]") +{ + expected e1(100); + expected e2(200); + e2 = std::move(e1); + CHECK(e2.has_value()); + CHECK(*e2 == 100); +} + +TEST_CASE("expected copy fallback: this has error, rhs has value", "[LWG-4025]") +{ + expected e1(100); + expected e2(unexpect); + e2 = std::move(e1); + CHECK(e2.has_value()); + CHECK(*e2 == 100); +} + +TEST_CASE("expected copy fallback: this has value, rhs has error", "[LWG-4025]") +{ + expected e1(unexpect); + expected e2(200); + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); +} + +TEST_CASE("expected copy fallback: both sides have error", "[LWG-4025]") +{ + expected e1(unexpect); + expected e2(unexpect); + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); +} + +TEST_CASE("expected copy fallback propagates error value", "[LWG-4025]") +{ + expected e1(unexpect, 42); + expected e2(100); + e2 = std::move(e1); + CHECK_FALSE(e2.has_value()); + CHECK(e2.error().value == 42); +} + +TEST_CASE("expected copy fallback propagates value", "[LWG-4025]") +{ + expected e1(999); + expected e2(unexpect, 10); + e2 = std::move(e1); + CHECK(e2.has_value()); + CHECK(*e2 == 999); +}