diff --git a/CMakeLists.txt b/CMakeLists.txt index 06ca9de..685cc9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,9 @@ target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) 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/emplace.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/bases.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/observers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp) add_executable(tests ${TEST_SOURCES}) diff --git a/README.md b/README.md index 529a3e3..306647e 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The interface is the same as `std::expected` as proposed in [p0323r3](http://www * `tl::expected s = exp_string.map(&std::string::size);` - `map_error`: carries out some operation on the unexpected object if there is one. * `my_error_code translate_error (std::error_code);` - * `tl::expected s = exp_int.map(translate_error);` + * `tl::expected s = exp_int.map_error(translate_error);` - `and_then`: like `map`, but for operations which return a `tl::expected`. * `tl::expected parse (const std::string& s);` * `tl::expected exp_ast = exp_string.and_then(parse);` @@ -82,6 +82,10 @@ Requires [Standardese](https://github.com/foonathan/standardese) for generating Requires [Catch](https://github.com/philsquared/Catch) for testing. This is bundled in the test directory. +### Acknowledgements + +Thanks to [Kévin Alexandre Boissonneault](https://github.com/KABoissonneault) for various bug fixes. + ---------- [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)]("http://creativecommons.org/publicdomain/zero/1.0/") diff --git a/expected.hpp b/expected.hpp index d7e875a..348cdb5 100644 --- a/expected.hpp +++ b/expected.hpp @@ -158,8 +158,8 @@ constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { /// *Example:* /// auto e1 = tl::make_unexpected(42); /// unexpected e2 (42); //same semantics -template unexpected make_unexpected(E &&e) { - return unexpected(std::forward(e)); +template unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); } /// \brief A tag type to tell expected to construct the unexpected value @@ -631,7 +631,8 @@ struct expected_copy_base : expected_operations_base { // move constructible #ifndef TL_EXPECTED_GCC49 template ::value> + bool = std::is_trivially_move_constructible::value + && std::is_trivially_move_constructible::value> struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; }; @@ -662,7 +663,10 @@ struct expected_move_base : expected_copy_base { template + IS_TRIVIALLY_DESTRUCTIBLE(T) && + IS_TRIVIALLY_COPY_ASSIGNABLE(E) && + IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E) && + IS_TRIVIALLY_DESTRUCTIBLE(E)> struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; }; @@ -691,8 +695,11 @@ struct expected_copy_assign_base : expected_move_base { #ifndef TL_EXPECTED_GCC49 template ::value - &&std::is_trivially_move_constructible::value - &&std::is_trivially_move_assignable::value> + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value + &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> struct expected_move_assign_base : expected_copy_assign_base { using expected_copy_assign_base::expected_copy_assign_base; }; @@ -709,14 +716,17 @@ struct expected_move_assign_base expected_move_assign_base(const expected_move_assign_base &rhs) = default; expected_move_assign_base(expected_move_assign_base &&rhs) = default; + expected_move_assign_base & operator=(const expected_move_assign_base &rhs) = default; + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( - std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_assignable::value) { - this->assign(std::move(rhs)); - return *this; + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; } }; @@ -929,9 +939,9 @@ public: /// \group and_then /// Carries out some operation which returns an expected on the stored object /// if there is one. \requires `std::invoke(std::forward(f), value())` - /// returns a `std::expected` for some `U`. \returns Let `U` be the result - /// of `std::invoke(std::forward(f), value())`. Returns a - /// `std::expected`. The return value is empty if `*this` is empty, + /// returns an `expected` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns an + /// `expected`. The return value is empty if `*this` is empty, /// otherwise the return value of `std::invoke(std::forward(f), value())` /// is returned. /// \synopsis template \nconstexpr auto and_then(F &&f) &; @@ -983,9 +993,9 @@ public: /// \group and_then /// Carries out some operation which returns an expected on the stored object /// if there is one. \requires `std::invoke(std::forward(f), value())` - /// returns a `std::expected` for some `U`. \returns Let `U` be the result - /// of `std::invoke(std::forward(f), value())`. Returns a - /// `std::expected`. The return value is empty if `*this` is empty, + /// returns an `expected` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns an + /// `expected`. The return value is empty if `*this` is empty, /// otherwise the return value of `std::invoke(std::forward(f), value())` /// is returned. /// \synopsis template \nconstexpr auto and_then(F &&f) &; @@ -1042,7 +1052,8 @@ public: !defined(TL_EXPECTED_GCC54) /// \brief Carries out some operation on the stored object if there is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), - /// value())`. Returns a `std::expected`. If `*this` is unexpected, the + /// value())`. If `U` is `void`, returns an `expected, otherwise + // returns an `expected`. If `*this` is unexpected, the /// result is `*this`, otherwise an `expected` is constructed from the /// return value of `std::invoke(std::forward(f), value())` and is /// returned. @@ -1073,7 +1084,8 @@ public: #else /// \brief Carries out some operation on the stored object if there is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), - /// value())`. Returns a `std::expected`. If `*this` is unexpected, the + /// value())`. If `U` is `void`, returns an `expected, otherwise + // returns an `expected`. If `*this` is unexpected, the /// result is `*this`, otherwise an `expected` is constructed from the /// return value of `std::invoke(std::forward(f), value())` and is /// returned. @@ -1122,7 +1134,8 @@ public: /// \brief Carries out some operation on the stored unexpected object if there /// is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), - /// value())`. Returns a `std::expected`. If `*this` has an expected + /// value())`. If `U` is `void`, returns an `expected`, otherwise + /// returns an `expected`. If `*this` has an expected /// value, the result is `*this`, otherwise an `expected` is constructed /// from `make_unexpected(std::invoke(std::forward(f), value()))` and is /// returned. @@ -1154,7 +1167,7 @@ public: /// \brief Carries out some operation on the stored unexpected object if there /// is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), - /// value())`. Returns a `std::expected`. If `*this` has an expected + /// value())`. Returns an `expected`. If `*this` has an expected /// value, the result is `*this`, otherwise an `expected` is constructed /// from `make_unexpected(std::invoke(std::forward(f), value()))` and is /// returned. @@ -1630,6 +1643,7 @@ public: /// \exclude namespace detail { +template using exp_t = typename detail::decay_t::error_type; template using err_t = typename detail::decay_t::error_type; template using ret_t = expected>; @@ -1639,7 +1653,7 @@ template ())), detail::enable_if_t::value> * = nullptr> constexpr auto map_impl(Exp &&exp, F &&f) { - using result = ret_t; + using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) : result(unexpect, std::forward(exp).error()); @@ -1664,8 +1678,8 @@ template ())), detail::enable_if_t::value> * = nullptr> -constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t { - using result = ret_t; +constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t> { + using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) @@ -1691,26 +1705,57 @@ auto map_impl(Exp &&exp, F &&f) -> expected> { !defined(TL_EXPECTED_GCC54) template (), - *std::declval()))> + *std::declval())), + detail::enable_if_t::value> * = nullptr> constexpr auto map_error_impl(Exp &&exp, F &&f) { - using result = ret_t; + using result = expected, detail::decay_t>; return exp.has_value() ? result(*std::forward(exp)) : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), + std::forward(exp).error()); + return result(unexpect, monostate{}); +} #else template (), - *std::declval()))> -constexpr auto map_error_impl(Exp &&exp, F &&f) -> ret_t { - using result = ret_t; + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { + using result = ret_t>; return exp.has_value() ? result(*std::forward(exp)) : result(unexpect, detail::invoke(std::forward(f), std::forward(exp).error())); } + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), + std::forward(exp).error()); + return result(unexpect, monostate{}); +} #endif template >::value)); } + + + // mapping functions which return references + { + tl::expected e(42); + auto ret = e.map([](int& i) -> int& { return i; }); + REQUIRE(ret); + REQUIRE(ret == 42); + } } TEST_CASE("Map error extensions", "[extensions.map_error]") { @@ -191,6 +200,55 @@ TEST_CASE("Map error extensions", "[extensions.map_error]") { REQUIRE(!ret); REQUIRE(ret.error() == 42); } + + { + tl::expected e = 21; + auto ret = e.map_error(ret_void); + REQUIRE(ret); + } + + { + const tl::expected e = 21; + auto ret = e.map_error(ret_void); + REQUIRE(ret); + } + + { + tl::expected e = 21; + auto ret = std::move(e).map_error(ret_void); + REQUIRE(ret); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).map_error(ret_void); + REQUIRE(ret); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.map_error(ret_void); + REQUIRE(!ret); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.map_error(ret_void); + REQUIRE(!ret); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map_error(ret_void); + REQUIRE(!ret); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map_error(ret_void); + REQUIRE(!ret); + } + } TEST_CASE("And then extensions", "[extensions.and_then]") {