Last active
October 5, 2024 04:22
-
-
Save jharmer95/a10396542ea3a6dd8efb9e176a4a0737 to your computer and use it in GitHub Desktop.
C++ Composables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <future> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
template <typename F> | |
class Composable; | |
template <typename R, bool NoExcept> | |
class AsyncComposable; | |
template <typename F1, typename F2> | |
class SplitComposable; | |
template <typename R1, typename R2, bool NoExcept> | |
class AsyncSplitComposable; | |
template <typename F> | |
[[nodiscard]] constexpr auto compose(F &&f) noexcept { | |
return Composable{ | |
[f = std::forward<decltype(f)>(f)]<typename... Args>( | |
Args &&...args) noexcept(std::is_nothrow_invocable_v<F, Args...>) { | |
return f(std::forward<Args>(args)...); | |
}}; | |
} | |
template <typename F, typename G> | |
[[nodiscard]] constexpr auto compose(F &&f, G &&g) noexcept { | |
return Composable{ | |
[f = std::forward<F>(f), g = std::forward<G>(g)]<typename... Args>( | |
Args &&...args) noexcept(std::is_nothrow_invocable_v<F, Args...> | |
and std::is_nothrow_invocable_v< | |
G, decltype(f(args...))>) { | |
return g(f(std::forward<Args>(args)...)); | |
}}; | |
} | |
template <typename F1, typename... Fs> | |
[[nodiscard]] constexpr auto compose(F1 &&f1, Fs &&...fs) noexcept { | |
return compose(std::forward<F1>(f1), compose(std::forward<Fs>(fs)...)); | |
} | |
template <typename F> | |
class [[nodiscard]] Composable { | |
public: | |
constexpr Composable(const F &func) noexcept : m_func(func) {} | |
constexpr Composable(F &&func) noexcept : m_func(std::move(func)) {} | |
template <typename G> | |
constexpr Composable(const Composable<G> &other) noexcept | |
: m_func(other.func) {} | |
template <typename G> | |
constexpr Composable(Composable<G> &&other) noexcept | |
: m_func(std::move(other).func) {} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto operator()(Args &&...args) const | |
noexcept(std::is_nothrow_invocable_v<F, Args...>) { | |
return execute(std::forward<Args>(args)...); | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] constexpr auto then(this Self &&self, G &&g) noexcept { | |
return compose(std::forward<Self>(self).m_func, std::forward<G>(g)); | |
} | |
template <typename Ex, typename Self, typename G> | |
[[nodiscard]] constexpr auto catch_ex(this Self &&self, G &&g) noexcept { | |
return compose([f = std::forward<Self>(self).m_func, | |
g = std::forward<G>(g)]<typename... Args>( | |
Args &&...args) { | |
static_assert(!std::is_nothrow_invocable_v<decltype(f), Args...>, | |
"No need to catch noexcept"); | |
try { | |
return f(std::forward<Args>(args)...); | |
} catch (const Ex &ex) { | |
return g(ex); | |
} | |
}); | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] constexpr auto catch_all(this Self &&self, G &&g) noexcept { | |
return compose( | |
[f = std::forward<Self>(self).m_func, | |
g = std::forward<G>(g)]<typename... Args>( | |
Args &&...args) noexcept(std::is_nothrow_invocable_v<G>) { | |
static_assert( | |
!std::is_nothrow_invocable_v<decltype(f), Args...>, | |
"No need to catch noexcept"); | |
try { | |
return f(std::forward<Args>(args)...); | |
} catch (...) { | |
return g(); | |
} | |
}); | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] constexpr auto split(this Self &&self, G &&g) noexcept { | |
return SplitComposable(std::forward<Self>(self).m_func, | |
std::forward<G>(g)); | |
} | |
template <typename Self, typename G, typename... Args1, typename... Args2> | |
[[nodiscard]] auto split_async(this Self &&self, | |
std::tuple<Args1...> &&args1, G &&g, | |
std::tuple<Args2...> &&args2) { | |
return AsyncSplitComposable( | |
std::launch::async, std::forward<Self>(self).m_func, | |
std::move(args1), std::forward<G>(g), std::move(args2)); | |
} | |
template <typename Self, typename G, typename... Args1, typename... Args2> | |
[[nodiscard]] auto split_async_defer(this Self &&self, | |
std::tuple<Args1...> &&args1, G &&g, | |
std::tuple<Args2...> &&args2) { | |
return AsyncSplitComposable( | |
std::launch::deferred, std::forward<Self>(self).m_func, | |
std::move(args1), std::forward<G>(g), std::move(args2)); | |
} | |
template <typename Self, typename... Args> | |
[[nodiscard]] auto async(this Self &&self, Args &&...args) { | |
return AsyncComposable(std::launch::async, | |
std::forward<Self>(self).m_func, | |
std::forward<Args>(args)...); | |
} | |
template <typename Self, typename... Args> | |
[[nodiscard]] auto async_defer(this Self &&self, Args &&...args) { | |
return AsyncComposable(std::launch::deferred, | |
std::forward<Self>(self).m_func, | |
std::forward<Args>(args)...); | |
} | |
template <typename... Args> | |
[[nodiscard]] constexpr auto execute(Args &&...args) const | |
noexcept(std::is_nothrow_invocable_v<F, Args...>) { | |
return m_func(std::forward<Args>(args)...); | |
} | |
private: | |
F m_func; | |
}; | |
template <typename F> | |
Composable(F) -> Composable<F>; | |
template <typename R, bool NoExcept> | |
class AsyncComposable { | |
public: | |
template <typename F, typename... Args> | |
AsyncComposable(std::launch policy, F &&f, Args &&...args) | |
: m_fut(std::async(policy, std::forward<F>(f), | |
std::forward<Args>(args)...)) {} | |
template <typename Self, typename G> | |
[[nodiscard]] auto then(this Self &&self, G &&g) { | |
return from_future<NoExcept>(std::launch::async, | |
std::forward<Self>(self).m_fut, | |
std::forward<G>(g)); | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] auto then_defer(this Self &&self, G &&g) { | |
return from_future<NoExcept>(std::launch::deferred, | |
std::forward<Self>(self).m_fut, | |
std::forward<G>(g)); | |
} | |
template <typename Ex, typename Self, typename G> | |
[[nodiscard]] auto catch_ex(this Self &&self, G &&g) { | |
static_assert(!NoExcept, "No need to catch noexcept"); | |
return AsyncComposable{ | |
[f = std::forward<Self>(self).m_fut, g = std::forward<G>(g)]() { | |
try { | |
return f.get(); | |
} catch (const Ex &ex) { | |
return g(ex); | |
} | |
}}; | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] auto catch_all(this Self &&self, G &&g) { | |
static_assert(!NoExcept, "No need to catch noexcept"); | |
return AsyncComposable{ | |
[f = std::forward<Self>(self).m_fut, | |
g = std::forward<G>( | |
g)]() noexcept(std::is_nothrow_invocable_v<G>) { | |
try { | |
return f.get(); | |
} catch (...) { | |
return g(); | |
} | |
}}; | |
} | |
template <typename Self, typename G, typename... Args> | |
[[nodiscard]] auto split(this Self &&self, G &&g, Args &&...args) { | |
return AsyncSplitComposable<R, std::invoke_result_t<G, Args...>, | |
(NoExcept and | |
std::is_nothrow_invocable_v<G, Args...>)>:: | |
from_future(std::launch::async, std::forward<Self>(self).m_fut, | |
std::forward<G>(g), std::forward<Args>(args)...); | |
} | |
template <typename Self, typename G, typename... Args> | |
[[nodiscard]] auto split_defer(this Self &&self, G &&g, Args &&...args) { | |
return AsyncSplitComposable<R, std::invoke_result_t<G, Args...>, | |
(NoExcept and | |
std::is_nothrow_invocable_v<G, Args...>)>:: | |
from_future(std::launch::deferred, std::forward<Self>(self).m_fut, | |
std::forward<G>(g), std::forward<Args>(args)...); | |
} | |
[[nodiscard]] R await() noexcept(NoExcept) { return m_fut.get(); } | |
private: | |
template <bool NoExcept2, typename R2, typename G> | |
[[nodiscard]] static AsyncComposable from_future( | |
std::launch policy, const std::shared_future<R2> &fut, G &&g) { | |
return AsyncComposable{ | |
policy, | |
[]<typename GG>(const std::shared_future<R2> &f, GG &&gg) noexcept( | |
NoExcept2 and std::is_nothrow_invocable_v<GG, R2>) { | |
return std::forward<GG>(gg)(f.get()); | |
}, | |
std::cref(fut), std::forward<G>(g)}; | |
} | |
template <bool NoExcept2, typename R2, typename G> | |
[[nodiscard]] static AsyncComposable from_future( | |
std::launch policy, std::shared_future<R2> &&fut, G &&g) { | |
return AsyncComposable{ | |
policy, | |
[]<typename GG>(std::shared_future<R2> &&f, GG &&gg) noexcept( | |
NoExcept2 and std::is_nothrow_invocable_v<GG, R2>) { | |
return std::forward<GG>(gg)(std::move(f).get()); | |
}, | |
std::move(fut), std::forward<G>(g)}; | |
} | |
std::shared_future<R> m_fut; | |
}; | |
template <typename F, typename... Args> | |
AsyncComposable(std::launch, F, Args...) | |
-> AsyncComposable<std::invoke_result_t<F, Args...>, | |
std::is_nothrow_invocable_v<F, Args...>>; | |
template <typename F1, typename F2> | |
class [[nodiscard]] SplitComposable { | |
public: | |
constexpr SplitComposable(const F1 &f1, const F2 &f2) noexcept | |
: m_func1(f1), m_func2(f2) {} | |
constexpr SplitComposable(F1 &&f1, F2 &&f2) noexcept | |
: m_func1(std::move(f1)), m_func2(std::move(f2)) {} | |
template <typename G, typename Arg1, typename Arg2> | |
[[nodiscard]] constexpr auto join(Arg1 &&arg1, Arg2 &&arg2, | |
G &&g) noexcept { | |
return compose( | |
[f1 = this->m_func1, f2 = this->m_func2, | |
arg1 = std::forward<Arg1>(arg1), arg2 = std::forward<Arg2>(arg2), | |
g = std::forward<G>( | |
g)]() noexcept(std::is_nothrow_invocable_v<F1, Arg1> | |
and std::is_nothrow_invocable_v<F2, Arg2> | |
and std::is_nothrow_invocable_v< | |
G, std::invoke_result_t<F1, Arg1>, | |
std::invoke_result_t<F2, Arg2>>) { | |
return g(f1(arg1), f2(arg2)); | |
}); | |
} | |
template <typename G, typename... Args1, typename... Args2> | |
[[nodiscard]] constexpr auto join(std::tuple<Args1...> &&args1, | |
std::tuple<Args2...> &&args2, | |
G &&g) noexcept { | |
return compose( | |
[f1 = this->m_func1, f2 = this->m_func2, args1 = std::move(args1), | |
args2 = std::move(args2), | |
g = std::forward<G>( | |
g)]() noexcept(std::is_nothrow_invocable_v<F1, Args1...> and | |
std::is_nothrow_invocable_v<F2, Args2...> | |
and std::is_nothrow_invocable_v< | |
G, | |
std::invoke_result_t<F1, Args1...>, | |
std::invoke_result_t<F2, | |
Args2...>>) { | |
return g(std::apply(f1, args1), std::apply(f2, args2)); | |
}); | |
} | |
private: | |
F1 m_func1; | |
F2 m_func2; | |
}; | |
template <typename F1, typename F2> | |
SplitComposable(F1, F2) -> SplitComposable<F1, F2>; | |
template <typename R1, typename R2, bool NoExcept> | |
class [[nodiscard]] AsyncSplitComposable { | |
public: | |
friend class AsyncComposable<R1, NoExcept>; | |
friend class AsyncComposable<R2, NoExcept>; | |
template <typename F1, typename... Args1, typename F2, typename... Args2> | |
AsyncSplitComposable(std::launch policy, F1 &&f1, | |
std::tuple<Args1...> &&args1, F2 &&f2, | |
std::tuple<Args2...> &&args2) | |
: m_fut1(std::async( | |
policy, | |
[f1 = std::forward<F1>(f1), args = std::move(args1)]() noexcept( | |
std::is_nothrow_invocable_v<F1, Args1...>) { | |
return std::apply(f1, args); | |
})), | |
m_fut2(std::async( | |
policy, | |
[f2 = std::forward<F2>(f2), args = std::move(args2)]() noexcept( | |
std::is_nothrow_invocable_v<F2, Args2...>) { | |
return std::apply(f2, args); | |
})) {} | |
template <typename Self, typename G> | |
[[nodiscard]] auto join(this Self &&self, G &&g) { | |
return AsyncComposable( | |
std::launch::async, | |
[f1 = std::forward<Self>(self).m_fut1, | |
f2 = std::forward<Self>(self).m_fut2, | |
g = std::forward<G>( | |
g)]() noexcept(NoExcept and | |
std::is_nothrow_invocable_v<G, R1, R2>) { | |
return g(f1.get(), f2.get()); | |
}); | |
} | |
template <typename Self, typename G> | |
[[nodiscard]] auto join_defer(this Self &&self, G &&g) { | |
return AsyncComposable( | |
std::launch::deferred, | |
[f1 = std::forward<Self>(self).m_fut1, | |
f2 = std::forward<Self>(self).m_fut2, | |
g = std::forward<G>( | |
g)]() noexcept(NoExcept and | |
std::is_nothrow_invocable_v<G, R1, R2>) { | |
return g(f1.get(), f2.get()); | |
}); | |
} | |
private: | |
AsyncSplitComposable(const std::shared_future<R1> &fut1, | |
std::shared_future<R2> &&fut2) noexcept | |
: m_fut1(fut1), m_fut2(std::move(fut2)) {} | |
AsyncSplitComposable(std::shared_future<R1> &&fut1, | |
std::shared_future<R2> &&fut2) noexcept | |
: m_fut1(std::move(fut1)), m_fut2(std::move(fut2)) {} | |
template <typename RR1, typename G, typename... Args> | |
[[nodiscard]] static AsyncSplitComposable from_future( | |
std::launch policy, const std::shared_future<RR1> &fut, G &&g, | |
Args &&...args) { | |
return AsyncSplitComposable{fut, | |
std::async(policy, std::forward<G>(g), | |
std::forward<Args>(args)...)}; | |
} | |
template <typename RR1, typename G, typename... Args> | |
[[nodiscard]] static AsyncSplitComposable from_future( | |
std::launch policy, std::shared_future<RR1> &&fut, G &&g, | |
Args &&...args) { | |
return AsyncSplitComposable{std::move(fut), | |
std::async(policy, std::forward<G>(g), | |
std::forward<Args>(args)...)}; | |
} | |
std::shared_future<R1> m_fut1; | |
std::shared_future<R2> m_fut2; | |
}; | |
template <typename F1, typename... Args1, typename F2, typename... Args2> | |
AsyncSplitComposable(std::launch, F1, std::tuple<Args1...> &&, F2, | |
std::tuple<Args2...> &&) | |
-> AsyncSplitComposable<std::invoke_result_t<F1, Args1...>, | |
std::invoke_result_t<F2, Args2...>, | |
std::is_nothrow_invocable_v<F1, Args1...> and | |
std::is_nothrow_invocable_v<F2, Args2...>>; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment