Skip to content

Instantly share code, notes, and snippets.

@jharmer95
Last active October 5, 2024 04:22
Show Gist options
  • Save jharmer95/a10396542ea3a6dd8efb9e176a4a0737 to your computer and use it in GitHub Desktop.
Save jharmer95/a10396542ea3a6dd8efb9e176a4a0737 to your computer and use it in GitHub Desktop.
C++ Composables
#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