Skip to content

Commit c8f0a48

Browse files
committed
Add support for monadic bind operators.
This is opt-in, using the EXPECTED_BIND_OPERATORS cmake definition Signed-off-by: Wolfgang E. Sanyer <WolfgangESanyer@gmail.com>
1 parent c7a5205 commit c8f0a48

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ if (NOT DEFINED CMAKE_CXX_STANDARD)
1616
endif()
1717

1818
option(EXPECTED_BUILD_PACKAGE "Build package files as well" ON)
19+
option(EXPECTED_BIND_OPERATORS "Add haskell-style monadic bind operators" OFF)
20+
21+
if(${EXPECTED_BIND_OPERATORS})
22+
add_compile_definitions(TL_EXPECTED_BIND_OPERATORS)
23+
endif()
1924

2025
cmake_dependent_option(EXPECTED_BUILD_TESTS
2126
"Enable tl::expected tests" ON

include/tl/expected.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,18 @@ class expected : private detail::expected_move_assign_base<T, E>,
12631263
return and_then_impl(*this, std::forward<F>(f));
12641264
}
12651265

1266+
#if defined(TL_EXPECTED_BIND_OPERATORS)
1267+
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) & {
1268+
return and_then_impl(*this, std::forward<F>(f));
1269+
}
1270+
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && {
1271+
return and_then_impl(std::move(*this), std::forward<F>(f));
1272+
}
1273+
template <class F> constexpr auto operator>=(F &&f) const & {
1274+
return and_then_impl(*this, std::forward<F>(f));
1275+
}
1276+
#endif
1277+
12661278
template <class F> TL_EXPECTED_11_CONSTEXPR auto and_also(F &&f) & {
12671279
return and_also_impl(*this, std::forward<F>(f));
12681280
}
@@ -1273,6 +1285,18 @@ class expected : private detail::expected_move_assign_base<T, E>,
12731285
return and_also_impl(*this, std::forward<F>(f));
12741286
}
12751287

1288+
#if defined(TL_EXPECTED_BIND_OPERATORS)
1289+
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) & {
1290+
return and_also_impl(*this, std::forward<F>(f));
1291+
}
1292+
template <class F> TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && {
1293+
return and_also_impl(std::move(*this), std::forward<F>(f));
1294+
}
1295+
template <class F> constexpr auto operator>(F &&f) const & {
1296+
return and_also_impl(*this, std::forward<F>(f));
1297+
}
1298+
#endif
1299+
12761300
#ifndef TL_EXPECTED_NO_CONSTRR
12771301
template <class F> constexpr auto and_then(F &&f) const && {
12781302
return and_then_impl(std::move(*this), std::forward<F>(f));
@@ -1281,6 +1305,16 @@ class expected : private detail::expected_move_assign_base<T, E>,
12811305
template <class F> constexpr auto and_also(F &&f) const && {
12821306
return and_also_impl(std::move(*this), std::forward<F>(f));
12831307
}
1308+
1309+
#if defined(TL_EXPECTED_BIND_OPERATORS)
1310+
template <class F> constexpr auto operator>=(F &&f) const && {
1311+
return and_then_impl(std::move(*this), std::forward<F>(f));
1312+
}
1313+
1314+
template <class F> constexpr auto operator>(F &&f) const && {
1315+
return and_also_impl(std::move(*this), std::forward<F>(f));
1316+
}
1317+
#endif
12841318
#endif
12851319

12861320
#else
@@ -1300,6 +1334,24 @@ class expected : private detail::expected_move_assign_base<T, E>,
13001334
return and_then_impl(*this, std::forward<F>(f));
13011335
}
13021336

1337+
#if defined(TL_EXPECTED_BIND_OPERATORS)
1338+
template <class F>
1339+
TL_EXPECTED_11_CONSTEXPR auto
1340+
operator>=(F &&f) & -> decltype(and_then_impl(std::declval<expected&>(), std::forward<F>(f))) {
1341+
return and_then_impl(*this, std::forward<F>(f));
1342+
}
1343+
template <class F>
1344+
TL_EXPECTED_11_CONSTEXPR auto operator>=(F &&f) && -> decltype(
1345+
and_then_impl(std::declval<expected&&>(), std::forward<F>(f))) {
1346+
return and_then_impl(std::move(*this), std::forward<F>(f));
1347+
}
1348+
template <class F>
1349+
constexpr auto operator>=(F &&f) const & -> decltype(
1350+
and_then_impl(std::declval<expected const&>(), std::forward<F>(f))) {
1351+
return and_then_impl(*this, std::forward<F>(f));
1352+
}
1353+
#endif
1354+
13031355
template <class F>
13041356
TL_EXPECTED_11_CONSTEXPR auto
13051357
and_also(F &&f) & -> decltype(and_also_impl(std::declval<expected&>(), std::forward<F>(f))) {
@@ -1316,6 +1368,24 @@ class expected : private detail::expected_move_assign_base<T, E>,
13161368
return and_also_impl(*this, std::forward<F>(f));
13171369
}
13181370

1371+
#if defined(TL_EXPECTED_BIND_OPERATORS)
1372+
template <class F>
1373+
TL_EXPECTED_11_CONSTEXPR auto
1374+
operator>(F &&f) & -> decltype(and_also_impl(std::declval<expected&>(), std::forward<F>(f))) {
1375+
return and_also_impl(*this, std::forward<F>(f));
1376+
}
1377+
template <class F>
1378+
TL_EXPECTED_11_CONSTEXPR auto operator>(F &&f) && -> decltype(
1379+
and_also_impl(std::declval<expected&&>(), std::forward<F>(f))) {
1380+
return and_also_impl(std::move(*this), std::forward<F>(f));
1381+
}
1382+
template <class F>
1383+
constexpr auto operator>(F &&f) const & -> decltype(
1384+
and_also_impl(std::declval<expected const&>(), std::forward<F>(f))) {
1385+
return and_also_impl(*this, std::forward<F>(f));
1386+
}
1387+
#endif
1388+
13191389
#ifndef TL_EXPECTED_NO_CONSTRR
13201390
template <class F>
13211391
constexpr auto and_then(F &&f) const && -> decltype(

tests/extensions.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,87 @@ TEST_CASE("And also extensions", "[extensions.and_also]") {
485485
}
486486
}
487487

488+
TEST_CASE("Monadic bind operators", "[extensions.bind_operators]") {
489+
auto succeed = [](int a) { return tl::expected<int, int>(21 * 2); };
490+
auto fail = [](int a) { return tl::expected<int, int>(tl::unexpect, 17); };
491+
auto also_succeed = []() { return tl::expected<int, int>(21 * 2 + 1); };
492+
auto also_fail = []() { return tl::expected<int, int>(tl::unexpect, 18); };
493+
494+
SECTION("A succesful op can be chained") {
495+
tl::expected<int, int> e = 21;
496+
auto ret = e >= succeed > also_succeed;
497+
REQUIRE(ret);
498+
REQUIRE(*ret == 43);
499+
}
500+
501+
SECTION("A succesful op can be chained, with const") {
502+
const tl::expected<int, int> e = 21;
503+
auto ret = e >= succeed > also_succeed;
504+
REQUIRE(ret);
505+
REQUIRE(*ret == 43);
506+
}
507+
508+
SECTION("A succesful op can be chained, with r-value reference") {
509+
tl::expected<int, int> e = 21;
510+
auto ret = std::move(e) >= succeed > also_succeed;
511+
REQUIRE(ret);
512+
REQUIRE(*ret == 43);
513+
}
514+
515+
SECTION("A succesful op can be chained, with const r-value reference") {
516+
const tl::expected<int, int> e = 21;
517+
auto ret = std::move(e) >= succeed > also_succeed;
518+
REQUIRE(ret);
519+
REQUIRE(*ret == 43);
520+
}
521+
522+
SECTION("A failed op short circuits") {
523+
tl::expected<int, int> e = 21;
524+
auto ret = e >= fail >= succeed;
525+
REQUIRE(!ret);
526+
REQUIRE(ret.error() == 17);
527+
528+
ret = e >= succeed > also_fail;
529+
REQUIRE(!ret);
530+
REQUIRE(ret.error() == 18);
531+
}
532+
533+
SECTION("A failed op short circuits, with const") {
534+
const tl::expected<int, int> e = 21;
535+
auto ret = e >= fail >= succeed;
536+
REQUIRE(!ret);
537+
REQUIRE(ret.error() == 17);
538+
539+
ret = e >= succeed > also_fail;
540+
REQUIRE(!ret);
541+
REQUIRE(ret.error() == 18);
542+
}
543+
544+
SECTION("A failed op short circuits, with r-value reference") {
545+
tl::expected<int, int> e = 21;
546+
auto ret = std::move(e) >= fail >= succeed;
547+
REQUIRE(!ret);
548+
REQUIRE(ret.error() == 17);
549+
550+
e = 21;
551+
ret = e >= succeed > also_fail;
552+
REQUIRE(!ret);
553+
REQUIRE(ret.error() == 18);
554+
}
555+
556+
SECTION("A failed op short circuits, with const r-value reference") {
557+
const tl::expected<int, int> e = 21;
558+
auto ret = std::move(e) >= fail >= succeed;
559+
REQUIRE(!ret);
560+
REQUIRE(ret.error() == 17);
561+
562+
const tl::expected<int, int> e2 = 21;
563+
ret = std::move(e2) >= succeed > also_fail;
564+
REQUIRE(!ret);
565+
REQUIRE(ret.error() == 18);
566+
}
567+
}
568+
488569
TEST_CASE("or_else", "[extensions.or_else]") {
489570
using eptr = std::unique_ptr<int>;
490571
auto succeed = [](int a) { return tl::expected<int, int>(21 * 2); };

0 commit comments

Comments
 (0)