mirror of
https://github.com/TartanLlama/optional.git
synced 2025-07-29 17:37:13 +02:00
Monadic operations
This commit is contained in:
@ -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)
|
||||
|
286
optional.hpp
286
optional.hpp
@ -18,6 +18,8 @@
|
||||
#include <utility>
|
||||
|
||||
namespace tl {
|
||||
template <class T> class optional;
|
||||
class monostate {};
|
||||
namespace detail {
|
||||
template <class T> using remove_cv_t = typename std::remove_cv<T>::type;
|
||||
template <class T> using remove_const_t = typename std::remove_const<T>::type;
|
||||
@ -63,6 +65,67 @@ struct conjunction<B, Bs...>
|
||||
|
||||
template <class...> struct voider { using type = void; };
|
||||
template <class... Ts> using void_t = typename voider<Ts...>::type;
|
||||
|
||||
template <class T> struct is_optional_impl : std::false_type {};
|
||||
|
||||
template <class T> struct is_optional_impl<optional<T>> : std::true_type {};
|
||||
|
||||
template <class T> using is_optional = is_optional_impl<decay_t<T>>;
|
||||
|
||||
// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround
|
||||
template <typename Fn, typename... Args,
|
||||
enable_if_t<std::is_member_pointer<decay_t<Fn>>{}, int> = 0>
|
||||
constexpr auto invoke(Fn &&f, Args &&... args) noexcept(
|
||||
noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
|
||||
-> decltype(std::mem_fn(f)(std::forward<Args>(args)...)) {
|
||||
return std::mem_fn(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Fn, typename... Args,
|
||||
std::enable_if_t<!std::is_member_pointer<std::decay_t<Fn>>{}, int> = 0>
|
||||
constexpr auto invoke(Fn &&f, Args &&... args) noexcept(
|
||||
noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
|
||||
-> decltype(std::forward<Fn>(f)(std::forward<Args>(args)...)) {
|
||||
return std::forward<Fn>(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class F, class, class... Us> struct invoke_result_impl;
|
||||
|
||||
template <class F, class... Us>
|
||||
struct invoke_result_impl<
|
||||
F, decltype(invoke(std::declval<F>(), std::declval<Us>()...), void()),
|
||||
Us...> {
|
||||
using type = decltype(invoke(std::declval<F>(), std::declval<Us>()...));
|
||||
};
|
||||
|
||||
template <class F, class... Us>
|
||||
using invoke_result = invoke_result_impl<F, void, Us...>;
|
||||
|
||||
template <class F, class... Us>
|
||||
using invoke_result_t = typename invoke_result<F, Us...>::type;
|
||||
|
||||
template <class U>
|
||||
using fixup_void = conditional_t<std::is_void_v<U>, monostate, U>;
|
||||
|
||||
// TODO check if we need std::invoke_result here
|
||||
template <class F, class U> struct get_invoke_optional_ret {
|
||||
using type = result_of_t<
|
||||
conditional_t<std::is_lvalue_reference_v<F>,
|
||||
typename std::remove_reference_t<F>::value_type &,
|
||||
typename std::remove_reference_t<F>::value_type &&>(U)>;
|
||||
};
|
||||
|
||||
template <class F, class U>
|
||||
using get_invoke_ret =
|
||||
typename conditional_t<is_optional<F>::value, get_invoke_optional_ret<F, U>,
|
||||
invoke_result<F, U>>::type;
|
||||
|
||||
template <class F, class U>
|
||||
using get_map_return = optional<fixup_void<get_invoke_ret<F, U>>>;
|
||||
|
||||
template <class F, class U>
|
||||
using returns_void = std::is_void<get_invoke_ret<F, U>>;
|
||||
}
|
||||
|
||||
struct in_place_t {
|
||||
@ -680,5 +743,228 @@ public:
|
||||
this->m_has_value = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class F> constexpr auto bind(F &&f) & {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
return detail::invoke(std::forward<F>(f), value());
|
||||
}
|
||||
|
||||
template <class F> constexpr auto bind(F &&f) && {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
return detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
}
|
||||
|
||||
template <class F> constexpr auto bind(F &&f) const & {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
return detail::invoke(std::forward<F>(f), value());
|
||||
}
|
||||
|
||||
template <class F> constexpr auto bind(F &&f) const && {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
return detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
}
|
||||
|
||||
template <class F, class E> constexpr auto bind(F &&f, E &&e) & {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
auto ret = detail::invoke(std::forward<F>(f), value());
|
||||
if (!ret)
|
||||
detail::invoke(e);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class F, class E> constexpr auto bind(F &&f, E &&e) && {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
auto ret = detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
if (!ret)
|
||||
detail::invoke(e);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class F, class E> constexpr auto bind(F &&f, E &&e) const & {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
auto ret = detail::invoke(std::forward<F>(f), value());
|
||||
if (!ret)
|
||||
detail::invoke(e);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class F, class E> constexpr auto bind(F &&f, E &&e) const && {
|
||||
using result = detail::invoke_result_t<F, T>;
|
||||
static_assert(detail::is_optional<result>::value,
|
||||
"F must return an optional");
|
||||
|
||||
if (!has_value())
|
||||
return result(nullopt);
|
||||
|
||||
auto ret = detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
if (!ret)
|
||||
detail::invoke(e);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class F> constexpr detail::get_map_return<F, T &> map(F &&f) & {
|
||||
if
|
||||
constexpr(detail::is_optional<F>::value) {
|
||||
if (!f.has_value() || !has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &>::value) {
|
||||
detail::invoke(std::forward<F>(f).value(), value());
|
||||
return nullopt;
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f).value(), value());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &>::value) {
|
||||
detail::invoke(std::forward<F>(f), value());
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f), value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class F> constexpr detail::get_map_return<F, T &&> map(F &&f) && {
|
||||
if
|
||||
constexpr(detail::is_optional<F>::value) {
|
||||
if (!f.has_value() || !has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &&>::value) {
|
||||
detail::invoke(std::forward<F>(f).value(), std::move(value()));
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f).value(), std::move(value()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &&>::value) {
|
||||
detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr detail::get_map_return<F, T &> map(F &&f) const & {
|
||||
if
|
||||
constexpr(detail::is_optional<F>::value) {
|
||||
if (!f.has_value() || !has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &>::value) {
|
||||
detail::invoke(std::forward<F>(f).value(), value());
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f).value(), value());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &>::value) {
|
||||
detail::invoke(std::forward<F>(f), value());
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f), value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
constexpr detail::get_map_return<F, T &&> map(F &&f) const && {
|
||||
if
|
||||
constexpr(detail::is_optional<F>::value) {
|
||||
if (!f.has_value() || !has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &&>::value) {
|
||||
detail::invoke(std::forward<F>(f).value(), std::move(value()));
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f).value(), std::move(value()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!has_value())
|
||||
return nullopt;
|
||||
|
||||
if
|
||||
constexpr(detail::returns_void<F, T &&>::value) {
|
||||
detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
return detail::invoke(std::forward<F>(f), std::move(value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> optional(T) -> optional<T>;
|
||||
}
|
||||
|
105
tests/monadic.cpp
Normal file
105
tests/monadic.cpp
Normal file
@ -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<int> o1;
|
||||
auto o1r = o1.map([](int i) { return i + 2; });
|
||||
static_assert(std::is_same_v<decltype(o1r), tl::optional<int>>);
|
||||
REQUIRE(!o1r);
|
||||
|
||||
// lhs has value
|
||||
tl::optional<int> o2 = 40;
|
||||
auto o2r = o2.map([](int i) { return i + 2; });
|
||||
static_assert(std::is_same_v<decltype(o2r), tl::optional<int>>);
|
||||
REQUIRE(o2r.value() == 42);
|
||||
|
||||
struct rval_call_map {
|
||||
auto operator()(int) && { return 42.0; };
|
||||
};
|
||||
|
||||
// ensure that function object is forwarded
|
||||
tl::optional<int> o3 = 42;
|
||||
auto o3r = o3.map(rval_call_map{});
|
||||
static_assert(std::is_same_v<decltype(o3r), tl::optional<double>>);
|
||||
REQUIRE(o3r.value() == 42);
|
||||
|
||||
// ensure that lhs is forwarded
|
||||
tl::optional<int> o4 = 40;
|
||||
auto o4r = std::move(o4).map([](int &&i) { return i + 2; });
|
||||
static_assert(std::is_same_v<decltype(o4r), tl::optional<int>>);
|
||||
REQUIRE(o4r.value() == 42);
|
||||
|
||||
// ensure that lhs is const-propagated
|
||||
const tl::optional<int> o5 = 40;
|
||||
auto o5r = o5.map([](const int &i) { return i + 2; });
|
||||
static_assert(std::is_same_v<decltype(o5r), tl::optional<int>>);
|
||||
REQUIRE(o5r.value() == 42);
|
||||
|
||||
// test applicative functor
|
||||
tl::optional<int> o6 = 40;
|
||||
auto f6 = tl::optional{[](const int &i) { return i + 2; }};
|
||||
auto o6r = o6.map(f6);
|
||||
static_assert(std::is_same_v<decltype(o6r), tl::optional<int>>);
|
||||
REQUIRE(o6r.value() == 42);
|
||||
|
||||
// test void return
|
||||
tl::optional<int> o7 = 40;
|
||||
auto f7 = tl::optional{[](const int &i) { return; }};
|
||||
auto o7r = o7.map(f7);
|
||||
static_assert(std::is_same_v<decltype(o7r), tl::optional<tl::monostate>>);
|
||||
REQUIRE(o6r.has_value());
|
||||
}
|
||||
|
||||
SECTION("bind") {
|
||||
|
||||
// lhs is empty
|
||||
tl::optional<int> o1;
|
||||
auto o1r = o1.bind([](int i) { return tl::optional<float>{42}; });
|
||||
static_assert(std::is_same_v<decltype(o1r), tl::optional<float>>);
|
||||
REQUIRE(!o1r);
|
||||
|
||||
// lhs has value
|
||||
tl::optional<int> o2 = 12;
|
||||
auto o2r = o2.bind([](int i) { return tl::optional<float>{42}; });
|
||||
static_assert(std::is_same_v<decltype(o2r), tl::optional<float>>);
|
||||
REQUIRE(o2r.value() == 42.f);
|
||||
|
||||
// lhs is empty, rhs returns empty
|
||||
tl::optional<int> o3;
|
||||
auto o3r = o3.bind([](int i) { return tl::optional<float>{}; });
|
||||
static_assert(std::is_same_v<decltype(o3r), tl::optional<float>>);
|
||||
REQUIRE(!o3r);
|
||||
|
||||
// rhs returns empty
|
||||
tl::optional<int> o4 = 12;
|
||||
auto o4r = o4.bind([](int i) { return tl::optional<float>{}; });
|
||||
static_assert(std::is_same_v<decltype(o4r), tl::optional<float>>);
|
||||
REQUIRE(!o4r);
|
||||
|
||||
struct rval_call_bind {
|
||||
auto operator()(int) && { return tl::optional<double>(42.0); };
|
||||
};
|
||||
|
||||
// ensure that function object is forwarded
|
||||
tl::optional<int> o5 = 42;
|
||||
auto o5r = o5.bind(rval_call_bind{});
|
||||
static_assert(std::is_same_v<decltype(o5r), tl::optional<double>>);
|
||||
REQUIRE(o5r.value() == 42);
|
||||
|
||||
// ensure that lhs is forwarded
|
||||
tl::optional<int> o6 = 42;
|
||||
auto o6r =
|
||||
std::move(o6).bind([](int &&i) { return tl::optional<double>(i); });
|
||||
static_assert(std::is_same_v<decltype(o6r), tl::optional<double>>);
|
||||
REQUIRE(o6r.value() == 42);
|
||||
|
||||
// ensure that function object is const-propagated
|
||||
const tl::optional<int> o7 = 42;
|
||||
auto o7r = o7.bind([](const int &i) { return tl::optional<double>(i); });
|
||||
static_assert(std::is_same_v<decltype(o7r), tl::optional<double>>);
|
||||
REQUIRE(o7r.value() == 42);
|
||||
}
|
||||
}
|
||||
;
|
Reference in New Issue
Block a user