diff --git a/CMakeLists.txt b/CMakeLists.txt index 684039d..f98d579 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,10 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/make_optional.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/in_place.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/relops.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/tests/observers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/observers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/monadic.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/nullopt.cpp) add_executable(tests ${TEST_SOURCES}) target_link_libraries(tests Catch) -set_property(TARGET tests PROPERTY CXX_STANDARD 14) +set_property(TARGET tests PROPERTY CXX_STANDARD 17) diff --git a/optional.hpp b/optional.hpp index 8a125f5..c4d3de3 100644 --- a/optional.hpp +++ b/optional.hpp @@ -18,6 +18,8 @@ #include namespace tl { +template class optional; +class monostate {}; namespace detail { template using remove_cv_t = typename std::remove_cv::type; template using remove_const_t = typename std::remove_const::type; @@ -63,6 +65,67 @@ struct conjunction template struct voider { using type = void; }; template using void_t = typename voider::type; + +template struct is_optional_impl : std::false_type {}; + +template struct is_optional_impl> : std::true_type {}; + +template using is_optional = is_optional_impl>; + +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template >{}, int> = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template < + typename Fn, typename... Args, + std::enable_if_t>{}, int> = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +template +using fixup_void = conditional_t, monostate, U>; + +// TODO check if we need std::invoke_result here +template struct get_invoke_optional_ret { + using type = result_of_t< + conditional_t, + typename std::remove_reference_t::value_type &, + typename std::remove_reference_t::value_type &&>(U)>; +}; + +template +using get_invoke_ret = + typename conditional_t::value, get_invoke_optional_ret, + invoke_result>::type; + +template +using get_map_return = optional>>; + +template +using returns_void = std::is_void>; } struct in_place_t { @@ -680,5 +743,228 @@ public: this->m_has_value = false; } } + + template constexpr auto bind(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + return detail::invoke(std::forward(f), value()); + } + + template constexpr auto bind(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + return detail::invoke(std::forward(f), std::move(value())); + } + + template constexpr auto bind(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + return detail::invoke(std::forward(f), value()); + } + + template constexpr auto bind(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + return detail::invoke(std::forward(f), std::move(value())); + } + + template constexpr auto bind(F &&f, E &&e) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + auto ret = detail::invoke(std::forward(f), value()); + if (!ret) + detail::invoke(e); + return ret; + } + + template constexpr auto bind(F &&f, E &&e) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + auto ret = detail::invoke(std::forward(f), std::move(value())); + if (!ret) + detail::invoke(e); + return ret; + } + + template constexpr auto bind(F &&f, E &&e) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + auto ret = detail::invoke(std::forward(f), value()); + if (!ret) + detail::invoke(e); + return ret; + } + + template constexpr auto bind(F &&f, E &&e) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + if (!has_value()) + return result(nullopt); + + auto ret = detail::invoke(std::forward(f), std::move(value())); + if (!ret) + detail::invoke(e); + return ret; + } + + template constexpr detail::get_map_return map(F &&f) & { + if + constexpr(detail::is_optional::value) { + if (!f.has_value() || !has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f).value(), value()); + return nullopt; + } + else { + return detail::invoke(std::forward(f).value(), value()); + } + } + else { + if (!has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f), value()); + return {}; + } + else { + return detail::invoke(std::forward(f), value()); + } + } + } + + template constexpr detail::get_map_return map(F &&f) && { + if + constexpr(detail::is_optional::value) { + if (!f.has_value() || !has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f).value(), std::move(value())); + return {}; + } + else { + return detail::invoke(std::forward(f).value(), std::move(value())); + } + } + else { + if (!has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f), std::move(value())); + return {}; + } + else { + return detail::invoke(std::forward(f), std::move(value())); + } + } + } + + template + constexpr detail::get_map_return map(F &&f) const & { + if + constexpr(detail::is_optional::value) { + if (!f.has_value() || !has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f).value(), value()); + return {}; + } + else { + return detail::invoke(std::forward(f).value(), value()); + } + } + else { + if (!has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f), value()); + return {}; + } + else { + return detail::invoke(std::forward(f), value()); + } + } + } + + template + constexpr detail::get_map_return map(F &&f) const && { + if + constexpr(detail::is_optional::value) { + if (!f.has_value() || !has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f).value(), std::move(value())); + return {}; + } + else { + return detail::invoke(std::forward(f).value(), std::move(value())); + } + } + else { + if (!has_value()) + return nullopt; + + if + constexpr(detail::returns_void::value) { + detail::invoke(std::forward(f), std::move(value())); + return {}; + } + else { + return detail::invoke(std::forward(f), std::move(value())); + } + } + } }; + + template optional(T) -> optional; } diff --git a/tests/monadic.cpp b/tests/monadic.cpp new file mode 100644 index 0000000..3813df2 --- /dev/null +++ b/tests/monadic.cpp @@ -0,0 +1,105 @@ +#include "catch.hpp" +#include "optional.hpp" + +// What is Clang Format up to?! +TEST_CASE("Monadic operations", + "[monadic]"){SECTION("map"){// lhs is empty + tl::optional o1; +auto o1r = o1.map([](int i) { return i + 2; }); +static_assert(std::is_same_v>); +REQUIRE(!o1r); + +// lhs has value +tl::optional o2 = 40; +auto o2r = o2.map([](int i) { return i + 2; }); +static_assert(std::is_same_v>); +REQUIRE(o2r.value() == 42); + +struct rval_call_map { + auto operator()(int) && { return 42.0; }; +}; + +// ensure that function object is forwarded +tl::optional o3 = 42; +auto o3r = o3.map(rval_call_map{}); +static_assert(std::is_same_v>); +REQUIRE(o3r.value() == 42); + +// ensure that lhs is forwarded +tl::optional o4 = 40; +auto o4r = std::move(o4).map([](int &&i) { return i + 2; }); +static_assert(std::is_same_v>); +REQUIRE(o4r.value() == 42); + +// ensure that lhs is const-propagated +const tl::optional o5 = 40; +auto o5r = o5.map([](const int &i) { return i + 2; }); +static_assert(std::is_same_v>); +REQUIRE(o5r.value() == 42); + +// test applicative functor +tl::optional o6 = 40; +auto f6 = tl::optional{[](const int &i) { return i + 2; }}; +auto o6r = o6.map(f6); +static_assert(std::is_same_v>); +REQUIRE(o6r.value() == 42); + +// test void return +tl::optional o7 = 40; +auto f7 = tl::optional{[](const int &i) { return; }}; +auto o7r = o7.map(f7); +static_assert(std::is_same_v>); +REQUIRE(o6r.has_value()); +} + +SECTION("bind") { + + // lhs is empty + tl::optional o1; + auto o1r = o1.bind([](int i) { return tl::optional{42}; }); + static_assert(std::is_same_v>); + REQUIRE(!o1r); + + // lhs has value + tl::optional o2 = 12; + auto o2r = o2.bind([](int i) { return tl::optional{42}; }); + static_assert(std::is_same_v>); + REQUIRE(o2r.value() == 42.f); + + // lhs is empty, rhs returns empty + tl::optional o3; + auto o3r = o3.bind([](int i) { return tl::optional{}; }); + static_assert(std::is_same_v>); + REQUIRE(!o3r); + + // rhs returns empty + tl::optional o4 = 12; + auto o4r = o4.bind([](int i) { return tl::optional{}; }); + static_assert(std::is_same_v>); + REQUIRE(!o4r); + + struct rval_call_bind { + auto operator()(int) && { return tl::optional(42.0); }; + }; + + // ensure that function object is forwarded + tl::optional o5 = 42; + auto o5r = o5.bind(rval_call_bind{}); + static_assert(std::is_same_v>); + REQUIRE(o5r.value() == 42); + + // ensure that lhs is forwarded + tl::optional o6 = 42; + auto o6r = + std::move(o6).bind([](int &&i) { return tl::optional(i); }); + static_assert(std::is_same_v>); + REQUIRE(o6r.value() == 42); + + // ensure that function object is const-propagated + const tl::optional o7 = 42; + auto o7r = o7.bind([](const int &i) { return tl::optional(i); }); + static_assert(std::is_same_v>); + REQUIRE(o7r.value() == 42); +} +} +;