From c7a5205bfefb1852a6b1f6b37aaed32c03231723 Mon Sep 17 00:00:00 2001 From: "Wolfgang E. Sanyer" Date: Sat, 10 Jul 2021 19:56:19 -0400 Subject: [PATCH 1/2] Add `and_also` extension function. Signed-off-by: Wolfgang E. Sanyer --- include/tl/expected.hpp | 53 ++++++++++++++++++ tests/extensions.cpp | 117 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index 31a5193..f7eafc6 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1263,10 +1263,24 @@ class expected : private detail::expected_move_assign_base, return and_then_impl(*this, std::forward(f)); } + template TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) & { + return and_also_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) && { + return and_also_impl(std::move(*this), std::forward(f)); + } + template constexpr auto and_also(F &&f) const & { + return and_also_impl(*this, std::forward(f)); + } + #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && { return and_then_impl(std::move(*this), std::forward(f)); } + + template constexpr auto and_also(F &&f) const && { + return and_also_impl(std::move(*this), std::forward(f)); + } #endif #else @@ -1286,12 +1300,33 @@ class expected : private detail::expected_move_assign_base, return and_then_impl(*this, std::forward(f)); } + template + TL_EXPECTED_11_CONSTEXPR auto + and_also(F &&f) & -> decltype(and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) && -> decltype( + and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_also(F &&f) const & -> decltype( + and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(*this, std::forward(f)); + } + #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && -> decltype( and_then_impl(std::declval(), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } + template + constexpr auto and_also(F &&f) const && -> decltype( + and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(std::move(*this), std::forward(f)); + } #endif #endif @@ -1964,6 +1999,15 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) { return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); } + +template ()))> +constexpr auto and_also_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} #else template struct TC; template Ret { return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); } + +template ()))> +constexpr auto and_also_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} #endif #ifdef TL_EXPECTED_CXX14 diff --git a/tests/extensions.cpp b/tests/extensions.cpp index 9670f90..5c56e9f 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -368,6 +368,123 @@ TEST_CASE("And then extensions", "[extensions.and_then]") { } } +TEST_CASE("And also extensions", "[extensions.and_also]") { + auto succeed = []() { return tl::expected(21 * 2); }; + auto fail = []() { return tl::expected(tl::unexpect, 17); }; + + { + tl::expected e = 21; + auto ret = e.and_also(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = e.and_also(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e = 21; + auto ret = std::move(e).and_also(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).and_also(succeed); + REQUIRE(ret); + REQUIRE(*ret == 42); + } + + { + tl::expected e = 21; + auto ret = e.and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + const tl::expected e = 21; + auto ret = e.and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + tl::expected e = 21; + auto ret = std::move(e).and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + const tl::expected e = 21; + auto ret = std::move(e).and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.and_also(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.and_also(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_also(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_also(succeed); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = e.and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = e.and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } + + { + const tl::expected e(tl::unexpect, 21); + auto ret = std::move(e).and_also(fail); + REQUIRE(!ret); + REQUIRE(ret.error() == 21); + } +} + TEST_CASE("or_else", "[extensions.or_else]") { using eptr = std::unique_ptr; auto succeed = [](int a) { return tl::expected(21 * 2); }; From 7c1afd3102d2a686e52f3e37255a4852d259cded Mon Sep 17 00:00:00 2001 From: "Wolfgang E. Sanyer" Date: Sun, 11 Jul 2021 14:18:43 -0400 Subject: [PATCH 2/2] Add support for monadic bind operators. This is opt-in, using the EXPECTED_BIND_OPERATORS cmake definition Signed-off-by: Wolfgang E. Sanyer --- CMakeLists.txt | 5 +++ include/tl/expected.hpp | 70 ++++++++++++++++++++++++++++++++++ tests/extensions.cpp | 83 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8df9107..45b0cd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,11 @@ if (NOT DEFINED CMAKE_CXX_STANDARD) endif() option(EXPECTED_BUILD_PACKAGE "Build package files as well" ON) +option(EXPECTED_BIND_OPERATORS "Add haskell-style monadic bind operators" OFF) + +if(${EXPECTED_BIND_OPERATORS}) + add_compile_definitions(TL_EXPECTED_BIND_OPERATORS) +endif() cmake_dependent_option(EXPECTED_BUILD_TESTS "Enable tl::expected tests" ON diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index f7eafc6..b33fe83 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1263,6 +1263,18 @@ class expected : private detail::expected_move_assign_base, return and_then_impl(*this, std::forward(f)); } +#if defined(TL_EXPECTED_BIND_OPERATORS) + template TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + template constexpr auto operator>=(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } +#endif + template TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) & { return and_also_impl(*this, std::forward(f)); } @@ -1273,6 +1285,18 @@ class expected : private detail::expected_move_assign_base, return and_also_impl(*this, std::forward(f)); } +#if defined(TL_EXPECTED_BIND_OPERATORS) + template TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) & { + return and_also_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && { + return and_also_impl(std::move(*this), std::forward(f)); + } + template constexpr auto operator>(F &&f) const & { + return and_also_impl(*this, std::forward(f)); + } +#endif + #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && { return and_then_impl(std::move(*this), std::forward(f)); @@ -1281,6 +1305,16 @@ class expected : private detail::expected_move_assign_base, template constexpr auto and_also(F &&f) const && { return and_also_impl(std::move(*this), std::forward(f)); } + +#if defined(TL_EXPECTED_BIND_OPERATORS) + template constexpr auto operator>=(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto operator>(F &&f) const && { + return and_also_impl(std::move(*this), std::forward(f)); + } +#endif #endif #else @@ -1300,6 +1334,24 @@ class expected : private detail::expected_move_assign_base, return and_then_impl(*this, std::forward(f)); } +#if defined(TL_EXPECTED_BIND_OPERATORS) + template + TL_EXPECTED_11_CONSTEXPR auto + operator>=(F &&f) & -> decltype(and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && -> decltype( + and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto operator>=(F &&f) const & -> decltype( + and_then_impl(std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } +#endif + template TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) & -> decltype(and_also_impl(std::declval(), std::forward(f))) { @@ -1316,6 +1368,24 @@ class expected : private detail::expected_move_assign_base, return and_also_impl(*this, std::forward(f)); } +#if defined(TL_EXPECTED_BIND_OPERATORS) + template + TL_EXPECTED_11_CONSTEXPR auto + operator>(F &&f) & -> decltype(and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && -> decltype( + and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto operator>(F &&f) const & -> decltype( + and_also_impl(std::declval(), std::forward(f))) { + return and_also_impl(*this, std::forward(f)); + } +#endif + #ifndef TL_EXPECTED_NO_CONSTRR template constexpr auto and_then(F &&f) const && -> decltype( diff --git a/tests/extensions.cpp b/tests/extensions.cpp index 5c56e9f..15d998e 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -485,6 +485,89 @@ TEST_CASE("And also extensions", "[extensions.and_also]") { } } +#if defined(TL_EXPECTED_BIND_OPERATORS) +TEST_CASE("Monadic bind operators", "[extensions.bind_operators]") { + auto succeed = [](int a) { return tl::expected(21 * 2); }; + auto fail = [](int a) { return tl::expected(tl::unexpect, 17); }; + auto also_succeed = []() { return tl::expected(21 * 2 + 1); }; + auto also_fail = []() { return tl::expected(tl::unexpect, 18); }; + + SECTION("A succesful op can be chained") { + tl::expected e = 21; + auto ret = e >= succeed > also_succeed; + REQUIRE(ret); + REQUIRE(*ret == 43); + } + + SECTION("A succesful op can be chained, with const") { + const tl::expected e = 21; + auto ret = e >= succeed > also_succeed; + REQUIRE(ret); + REQUIRE(*ret == 43); + } + + SECTION("A succesful op can be chained, with r-value reference") { + tl::expected e = 21; + auto ret = std::move(e) >= succeed > also_succeed; + REQUIRE(ret); + REQUIRE(*ret == 43); + } + + SECTION("A succesful op can be chained, with const r-value reference") { + const tl::expected e = 21; + auto ret = std::move(e) >= succeed > also_succeed; + REQUIRE(ret); + REQUIRE(*ret == 43); + } + + SECTION("A failed op short circuits") { + tl::expected e = 21; + auto ret = e >= fail >= succeed; + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + + ret = e >= succeed > also_fail; + REQUIRE(!ret); + REQUIRE(ret.error() == 18); + } + + SECTION("A failed op short circuits, with const") { + const tl::expected e = 21; + auto ret = e >= fail >= succeed; + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + + ret = e >= succeed > also_fail; + REQUIRE(!ret); + REQUIRE(ret.error() == 18); + } + + SECTION("A failed op short circuits, with r-value reference") { + tl::expected e = 21; + auto ret = std::move(e) >= fail >= succeed; + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + + e = 21; + ret = e >= succeed > also_fail; + REQUIRE(!ret); + REQUIRE(ret.error() == 18); + } + + SECTION("A failed op short circuits, with const r-value reference") { + const tl::expected e = 21; + auto ret = std::move(e) >= fail >= succeed; + REQUIRE(!ret); + REQUIRE(ret.error() == 17); + + const tl::expected e2 = 21; + ret = std::move(e2) >= succeed > also_fail; + REQUIRE(!ret); + REQUIRE(ret.error() == 18); + } +} +#endif + TEST_CASE("or_else", "[extensions.or_else]") { using eptr = std::unique_ptr; auto succeed = [](int a) { return tl::expected(21 * 2); };