Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add monadic bind operators, >= and > #87

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions include/tl/expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1263,10 +1263,58 @@ class expected : private detail::expected_move_assign_base<T, E>,
return and_then_impl(*this, std::forward<F>(f));
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) & {
return and_then_impl(*this, std::forward<F>(f));
}
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && {
return and_then_impl(std::move(*this), std::forward<F>(f));
}
template <class F> constexpr auto operator>=(F &&f) const & {
return and_then_impl(*this, std::forward<F>(f));
}
#endif

template <class F> TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) & {
return and_also_impl(*this, std::forward<F>(f));
}
template <class F> TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) && {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
template <class F> constexpr auto and_also(F &&f) const & {
return and_also_impl(*this, std::forward<F>(f));
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) & {
return and_also_impl(*this, std::forward<F>(f));
}
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
template <class F> constexpr auto operator>(F &&f) const & {
return and_also_impl(*this, std::forward<F>(f));
}
#endif

#ifndef TL_EXPECTED_NO_CONSTRR
template <class F> constexpr auto and_then(F &&f) const && {
return and_then_impl(std::move(*this), std::forward<F>(f));
}

template <class F> constexpr auto and_also(F &&f) const && {
return and_also_impl(std::move(*this), std::forward<F>(f));
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
template <class F> constexpr auto operator>=(F &&f) const && {
return and_then_impl(std::move(*this), std::forward<F>(f));
}

template <class F> constexpr auto operator>(F &&f) const && {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
#endif
#endif

#else
Expand All @@ -1286,12 +1334,69 @@ class expected : private detail::expected_move_assign_base<T, E>,
return and_then_impl(*this, std::forward<F>(f));
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
template <class F>
TL_EXPECTED_11_CONSTEXPR auto
operator>=(F &&f) & -> decltype(and_then_impl(std::declval<expected&>(), std::forward<F>(f))) {
return and_then_impl(*this, std::forward<F>(f));
}
template <class F>
TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && -> decltype(
and_then_impl(std::declval<expected&&>(), std::forward<F>(f))) {
return and_then_impl(std::move(*this), std::forward<F>(f));
}
template <class F>
constexpr auto operator>=(F &&f) const & -> decltype(
and_then_impl(std::declval<expected const&>(), std::forward<F>(f))) {
return and_then_impl(*this, std::forward<F>(f));
}
#endif

template <class F>
TL_EXPECTED_11_CONSTEXPR auto
and_also(F &&f) & -> decltype(and_also_impl(std::declval<expected&>(), std::forward<F>(f))) {
return and_also_impl(*this, std::forward<F>(f));
}
template <class F>
TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) && -> decltype(
and_also_impl(std::declval<expected&&>(), std::forward<F>(f))) {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
template <class F>
constexpr auto and_also(F &&f) const & -> decltype(
and_also_impl(std::declval<expected const&>(), std::forward<F>(f))) {
return and_also_impl(*this, std::forward<F>(f));
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
template <class F>
TL_EXPECTED_11_CONSTEXPR auto
operator>(F &&f) & -> decltype(and_also_impl(std::declval<expected&>(), std::forward<F>(f))) {
return and_also_impl(*this, std::forward<F>(f));
}
template <class F>
TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && -> decltype(
and_also_impl(std::declval<expected&&>(), std::forward<F>(f))) {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
template <class F>
constexpr auto operator>(F &&f) const & -> decltype(
and_also_impl(std::declval<expected const&>(), std::forward<F>(f))) {
return and_also_impl(*this, std::forward<F>(f));
}
#endif

#ifndef TL_EXPECTED_NO_CONSTRR
template <class F>
constexpr auto and_then(F &&f) const && -> decltype(
and_then_impl(std::declval<expected const&&>(), std::forward<F>(f))) {
return and_then_impl(std::move(*this), std::forward<F>(f));
}
template <class F>
constexpr auto and_also(F &&f) const && -> decltype(
and_also_impl(std::declval<expected const&&>(), std::forward<F>(f))) {
return and_also_impl(std::move(*this), std::forward<F>(f));
}
#endif
#endif

Expand Down Expand Up @@ -1964,6 +2069,15 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) {
return exp.has_value() ? detail::invoke(std::forward<F>(f))
: Ret(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
class Ret = decltype(detail::invoke(std::declval<F>()))>
constexpr auto and_also_impl(Exp &&exp, F &&f) {
static_assert(detail::is_expected<Ret>::value, "F must return an expected");

return exp.has_value() ? detail::invoke(std::forward<F>(f))
: Ret(unexpect, std::forward<Exp>(exp).error());
}
#else
template <class> struct TC;
template <class Exp, class F,
Expand All @@ -1987,6 +2101,15 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret {
return exp.has_value() ? detail::invoke(std::forward<F>(f))
: Ret(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
class Ret = decltype(detail::invoke(std::declval<F>()))>
constexpr auto and_also_impl(Exp &&exp, F &&f) -> Ret {
static_assert(detail::is_expected<Ret>::value, "F must return an expected");

return exp.has_value() ? detail::invoke(std::forward<F>(f))
: Ret(unexpect, std::forward<Exp>(exp).error());
}
#endif

#ifdef TL_EXPECTED_CXX14
Expand Down
200 changes: 200 additions & 0 deletions tests/extensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,206 @@ TEST_CASE("And then extensions", "[extensions.and_then]") {
}
}

TEST_CASE("And also extensions", "[extensions.and_also]") {
auto succeed = []() { return tl::expected<int, int>(21 * 2); };
auto fail = []() { return tl::expected<int, int>(tl::unexpect, 17); };

{
tl::expected<int, int> e = 21;
auto ret = e.and_also(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}

{
const tl::expected<int, int> e = 21;
auto ret = e.and_also(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}

{
tl::expected<int, int> e = 21;
auto ret = std::move(e).and_also(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}

{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).and_also(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}

{
tl::expected<int, int> e = 21;
auto ret = e.and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}

{
const tl::expected<int, int> e = 21;
auto ret = e.and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}

{
tl::expected<int, int> e = 21;
auto ret = std::move(e).and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}

{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}

{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_also(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_also(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_also(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_also(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}

{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_also(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
}

#if defined(TL_EXPECTED_BIND_OPERATORS)
TEST_CASE("Monadic bind operators", "[extensions.bind_operators]") {
auto succeed = [](int a) { return tl::expected<int, int>(21 * 2); };
auto fail = [](int a) { return tl::expected<int, int>(tl::unexpect, 17); };
auto also_succeed = []() { return tl::expected<int, int>(21 * 2 + 1); };
auto also_fail = []() { return tl::expected<int, int>(tl::unexpect, 18); };

SECTION("A succesful op can be chained") {
tl::expected<int, int> 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<int, int> 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<int, int> 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<int, int> e = 21;
auto ret = std::move(e) >= succeed > also_succeed;
REQUIRE(ret);
REQUIRE(*ret == 43);
}

SECTION("A failed op short circuits") {
tl::expected<int, int> 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<int, int> 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<int, int> 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<int, int> e = 21;
auto ret = std::move(e) >= fail >= succeed;
REQUIRE(!ret);
REQUIRE(ret.error() == 17);

const tl::expected<int, int> 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<int>;
auto succeed = [](int a) { return tl::expected<int, int>(21 * 2); };
Expand Down