diff --git a/CMakeLists.txt b/CMakeLists.txt index 61e4731..4d9ebc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,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/constructors.cpp) add_executable(tests ${TEST_SOURCES}) diff --git a/expected.hpp b/expected.hpp index 82d28e1..a59c69c 100644 --- a/expected.hpp +++ b/expected.hpp @@ -50,11 +50,19 @@ #endif namespace tl { +template class expected; + namespace detail { template using enable_if_t = typename std::enable_if::type; template using decay_t = typename std::decay::type; +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + // std::invoke from C++17 // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround template unexpected_type; + /// \group and_then + /// Carries out some operation which returns an optional on the stored object + /// if there is one. \requires `std::invoke(std::forward(f), value())` + /// returns a `std::optional` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns a + /// `std::optional`. The return value is empty if `*this` is empty, + /// otherwise the return value of `std::invoke(std::forward(f), value())` + /// is returned. \group and_then \synopsis template \nconstexpr auto + /// and_then(F &&f) &; + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_expected::value, + "F must return an expected"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(unexpect, this->error()); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) &&; + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_expected::value, + "F must return an expected"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(unexpect, std::move(this->error())); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &; + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_expected::value, + "F must return an expected"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(unexpect, this->error()); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_expected::value, + "F must return an expected"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(unexpect, std:: + : move(this->error())); + } +#endif + #ifdef TL_EXPECTED_CXX14 /// \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), @@ -691,28 +753,33 @@ public: } private: -#ifdef TL_EXPECTED_CX14 + template using err_t = typename detail::decay_t::error_type; + template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 template (), *std::declval())), detail::enable_if_t::value> * = nullptr> - constexpr auto map_impl(Exp &&exp, F &&f) { - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : unexpected(std::forward(exp).error()); + static constexpr auto map_impl(Exp &&exp, F &&f) { + using result = ret_t; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); } template (), *std::declval())), detail::enable_if_t::value> * = nullptr> - auto map_impl(Exp &&exp, F &&f) { + static auto map_impl(Exp &&exp, F &&f) { + using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); - return monostate{}; + return result(monostate{}); } - return unexpected(std::forward(exp).error()); + return result(unexpect, std::forward(exp).error()); } #else template ())), detail::enable_if_t::value> * = nullptr> - constexpr auto map_impl(Exp &&exp, F &&f) - -> expected { - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : unexpected(std::forward(exp).error()); + static 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))) + : result(unexpect, std::forward(exp).error()); } template ())), detail::enable_if_t::value> * = nullptr> - auto map_impl(Exp &&exp, F &&f) - -> expected { + static auto map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); - return std::forward(exp).error(); + return tl::monostate{}; } - return unexpected(std::forward(exp).error()); + return unexpected>(std::forward(exp).error()); } #endif }; diff --git a/tests/extensions.cpp b/tests/extensions.cpp new file mode 100644 index 0000000..bc619a9 --- /dev/null +++ b/tests/extensions.cpp @@ -0,0 +1,250 @@ +#include "catch.hpp" +#include "expected.hpp" + +#define TOKENPASTE(x, y) x##y +#define TOKENPASTE2(x, y) TOKENPASTE(x, y) +#define STATIC_REQUIRE(e) \ + constexpr bool TOKENPASTE2(rqure, __LINE__) = e; \ + REQUIRE(e); + +TEST_CASE("Map extensions", "[extensions.map]") { + auto mul2 = [](auto a) { return a * 2; }; + auto ret_void = [](auto a) {}; + + { + tl::expected e = 21; + auto ret = e.map(mul2); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = e.map(mul2); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e = 21; + auto ret = std::move(e).map(mul2); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).map(mul2); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.map(mul2); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.map(mul2); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map(mul2); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map(mul2); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e = 21; + auto ret = e.map(ret_void); + REQUIRE(ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + const tl::expected e = 21; + auto ret = e.map(ret_void); + REQUIRE(ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + tl::expected e = 21; + auto ret = std::move(e).map(ret_void); + REQUIRE(ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).map(ret_void); + REQUIRE(ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.map(ret_void); + REQUIRE(!ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.map(ret_void); + REQUIRE(!ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map(ret_void); + REQUIRE(!ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).map(ret_void); + REQUIRE(!ret); + STATIC_REQUIRE( + (std::is_same>::value)); + } +} + +TEST_CASE("And then extensions", "[extensions.and_then]") { + auto succeed = [](auto a) { return tl::expected(21 * 2); }; + auto fail = [](auto a) { return tl::expected(tl::unexpect, 17); }; + + { + tl::expected e = 21; + auto ret = e.and_then(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = e.and_then(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e = 21; + auto ret = std::move(e).and_then(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).and_then(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e = 21; + auto ret = e.and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + const tl::expected e = 21; + auto ret = e.and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + tl::expected e = 21; + auto ret = std::move(e).and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.and_then(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.and_then(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_then(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_then(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_then(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } +}