mirror of
https://github.com/Naios/continuable.git
synced 2025-12-08 01:36:46 +08:00
Split more functionality into seperate header
This commit is contained in:
parent
bd68d14b34
commit
bc2d46ff40
@ -31,852 +31,20 @@
|
||||
#ifndef CONTINUABLE_BASE_HPP_INCLUDED__
|
||||
#define CONTINUABLE_BASE_HPP_INCLUDED__
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "continuable/detail/api.hpp"
|
||||
#include "continuable/detail/base.hpp"
|
||||
#include "continuable/detail/composition.hpp"
|
||||
#include "continuable/detail/traits.hpp"
|
||||
#include "continuable/detail/transforms.hpp"
|
||||
#include "continuable/detail/util.hpp"
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
/// Represents a present signature hint
|
||||
template <typename... Args>
|
||||
using signature_hint_tag = util::identity<Args...>;
|
||||
/// Represents an absent signature hint
|
||||
struct absent_signature_hint_tag {};
|
||||
|
||||
template <typename>
|
||||
struct is_absent_hint : std::false_type {};
|
||||
template <>
|
||||
struct is_absent_hint<absent_signature_hint_tag> : std::true_type {};
|
||||
|
||||
struct this_thread_executor_tag {};
|
||||
|
||||
/// The namespace `base` provides the low level API for working
|
||||
/// with continuable types.
|
||||
///
|
||||
/// Important methods are:
|
||||
/// - Creating a continuation from a callback taking functional
|
||||
/// base::attorney::create(auto&& callback)
|
||||
/// -> base::continuation<auto>
|
||||
/// - Chaining a continuation together with a callback
|
||||
/// base::chain_continuation(base::continuation<auto> continuation,
|
||||
/// auto&& callback)
|
||||
/// -> base::continuation<auto>
|
||||
/// - Finally invoking the continuation chain
|
||||
/// base::finalize_continuation(base::continuation<auto> continuation)
|
||||
/// -> void
|
||||
namespace base {
|
||||
/// Returns the signature hint of the given continuable
|
||||
template <typename T>
|
||||
constexpr auto hint_of(util::identity<T>) {
|
||||
static_assert(util::fail<T>::value,
|
||||
"Expected a continuation with an existing signature hint!");
|
||||
return util::identity_of<void>();
|
||||
}
|
||||
/// Returns the signature hint of the given continuable
|
||||
template <typename Data, typename... Args>
|
||||
constexpr auto
|
||||
hint_of(util::identity<continuable_base<Data, signature_hint_tag<Args...>>>) {
|
||||
return signature_hint_tag<Args...>{};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct is_continuation : std::false_type {};
|
||||
template <typename Data, typename Annotation>
|
||||
struct is_continuation<continuable_base<Data, Annotation>> : std::true_type {};
|
||||
|
||||
/// Helper class to access private methods and members of
|
||||
/// the continuable_base class.
|
||||
struct attorney {
|
||||
/// Makes a continuation wrapper from the given argument
|
||||
template <typename T, typename A = absent_signature_hint_tag>
|
||||
static auto create(T&& continuation, A /*hint*/, util::ownership ownership_) {
|
||||
return continuable_base<std::decay_t<T>, std::decay_t<A>>(
|
||||
std::forward<T>(continuation), ownership_);
|
||||
}
|
||||
|
||||
/// Invokes a continuation object in a reference correct way
|
||||
template <typename Data, typename Annotation, typename Callback>
|
||||
static auto
|
||||
invoke_continuation(continuable_base<Data, Annotation>&& continuation,
|
||||
Callback&& callback) {
|
||||
auto materialized = std::move(continuation).materialize();
|
||||
materialized.release();
|
||||
return materialized.data_(std::forward<Callback>(callback));
|
||||
}
|
||||
|
||||
template <typename Data, typename Annotation>
|
||||
static auto materialize(continuable_base<Data, Annotation>&& continuation) {
|
||||
return std::move(continuation).materialize();
|
||||
}
|
||||
|
||||
template <typename Data, typename Annotation>
|
||||
static Data&&
|
||||
consume_data(continuable_base<Data, Annotation>&& continuation) {
|
||||
return std::move(continuation).consume_data();
|
||||
}
|
||||
|
||||
template <typename Continuable>
|
||||
static util::ownership ownership_of(Continuable&& continuation) {
|
||||
return continuation.ownership_;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the invoker of a callback, the next callback
|
||||
// and the arguments of the previous continuation.
|
||||
//
|
||||
// The return type of the invokerOf function matches a functional of:
|
||||
// void(auto&& callback, auto&& nextCallback, auto&&... args)
|
||||
//
|
||||
// The invoker decorates the result type in the following way
|
||||
// - void -> nextCallback()
|
||||
// - ? -> nextCallback(?)
|
||||
// - std::pair<?, ?> -> nextCallback(?, ?)
|
||||
// - std::tuple<?...> -> nextCallback(?...)
|
||||
//
|
||||
// When the result is a continuation itself pass the callback to it
|
||||
// - continuation<?...> -> result(nextCallback);
|
||||
namespace decoration {
|
||||
/// Helper class wrapping the underlaying unwrapping lambda
|
||||
/// in order to extend it with a hint method.
|
||||
template <typename T, typename Hint>
|
||||
class invoker : public T {
|
||||
public:
|
||||
explicit invoker(T invoke) : T(std::move(invoke)) {
|
||||
}
|
||||
|
||||
using T::operator();
|
||||
|
||||
/// Returns the underlaying signature hint
|
||||
static constexpr Hint hint() noexcept {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Args>
|
||||
constexpr auto make_invoker(T&& invoke, signature_hint_tag<Args...>) {
|
||||
return invoker<std::decay_t<T>, signature_hint_tag<Args...>>(
|
||||
std::forward<T>(invoke));
|
||||
}
|
||||
|
||||
/// - continuable<?...> -> result(nextCallback);
|
||||
template <typename Data, typename Annotation>
|
||||
constexpr auto invoker_of(util::identity<continuable_base<Data, Annotation>>) {
|
||||
/// Get the hint of the unwrapped returned continuable
|
||||
using Type = decltype(attorney::materialize(
|
||||
std::declval<continuable_base<Data, Annotation>>()));
|
||||
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto continuation_ = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
attorney::invoke_continuation(
|
||||
std::move(continuation_),
|
||||
std::forward<decltype(nextCallback)>(nextCallback));
|
||||
},
|
||||
hint_of(util::identity_of<Type>()));
|
||||
}
|
||||
|
||||
/// - ? -> nextCallback(?)
|
||||
template <typename T>
|
||||
auto invoker_of(util::identity<T>) {
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto result = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)(std::move(result));
|
||||
},
|
||||
util::identity_of<T>());
|
||||
}
|
||||
|
||||
/// - void -> nextCallback()
|
||||
inline auto invoker_of(util::identity<void>) {
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)();
|
||||
},
|
||||
util::identity<>{});
|
||||
}
|
||||
|
||||
/// Returns a sequenced invoker which is able to invoke
|
||||
/// objects where std::get is applicable.
|
||||
inline auto sequenced_unpack_invoker() {
|
||||
return [](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto result = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
util::unpack(std::move(result), [&](auto&&... types) {
|
||||
/// TODO Add inplace resolution here
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)(
|
||||
std::forward<decltype(types)>(types)...);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// - std::pair<?, ?> -> nextCallback(?, ?)
|
||||
template <typename First, typename Second>
|
||||
constexpr auto invoker_of(util::identity<std::pair<First, Second>>) {
|
||||
return make_invoker(sequenced_unpack_invoker(),
|
||||
util::identity<First, Second>{});
|
||||
}
|
||||
|
||||
// - std::tuple<?...> -> nextCallback(?...)
|
||||
template <typename... Args>
|
||||
constexpr auto invoker_of(util::identity<std::tuple<Args...>>) {
|
||||
return make_invoker(sequenced_unpack_invoker(), util::identity<Args...>{});
|
||||
}
|
||||
} // end namespace decoration
|
||||
|
||||
/// Invoke the callback immediately
|
||||
template <typename Invoker, typename Callback, typename NextCallback,
|
||||
typename... Args>
|
||||
void packed_dispatch(this_thread_executor_tag, Invoker&& invoker,
|
||||
Callback&& callback, NextCallback&& nextCallback,
|
||||
Args&&... args) {
|
||||
|
||||
// Invoke the callback with the decorated invoker immediately
|
||||
std::forward<Invoker>(invoker)(std::forward<Callback>(callback),
|
||||
std::forward<NextCallback>(nextCallback),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Invoke the callback through the given executor
|
||||
template <typename Executor, typename Invoker, typename Callback,
|
||||
typename NextCallback, typename... Args>
|
||||
void packed_dispatch(Executor&& executor, Invoker&& invoker,
|
||||
Callback&& callback, NextCallback&& nextCallback,
|
||||
Args&&... args) {
|
||||
|
||||
// Create a worker object which when invoked calls the callback with the
|
||||
// the returned arguments.
|
||||
auto work = [
|
||||
invoker = std::forward<Invoker>(invoker),
|
||||
callback = std::forward<Callback>(callback),
|
||||
nextCallback = std::forward<NextCallback>(nextCallback),
|
||||
args = std::make_tuple(std::forward<Args>(args)...)
|
||||
]() mutable {
|
||||
util::unpack(std::move(args), [&](auto&&... captured_args) {
|
||||
// Just use the packed dispatch method which dispatches the work on
|
||||
// the current thread.
|
||||
packed_dispatch(this_thread_executor_tag{}, std::move(invoker),
|
||||
std::move(callback), std::move(nextCallback),
|
||||
std::forward<decltype(captured_args)>(captured_args)...);
|
||||
});
|
||||
};
|
||||
|
||||
// Pass the work functional object to the executor
|
||||
std::forward<Executor>(executor)(std::move(work));
|
||||
}
|
||||
|
||||
/// Invokes a continuation with a given callback.
|
||||
/// Passes the next callback to the resulting continuable or
|
||||
/// invokes the next callback directly if possible.
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
/// - Callback: [](std::string) { }
|
||||
/// - NextCallback: []() { }
|
||||
///
|
||||
template <typename... Args, typename Continuation, typename Callback,
|
||||
typename Executor, typename NextCallback>
|
||||
void invoke_proxy(signature_hint_tag<Args...>, Continuation&& continuation,
|
||||
Callback&& callback, Executor&& executor,
|
||||
NextCallback&& nextCallback) {
|
||||
|
||||
// Invoke the continuation with a proxy callback.
|
||||
// The proxy callback is responsible for passing the
|
||||
// the result to the callback as well as decorating it.
|
||||
attorney::invoke_continuation(std::forward<Continuation>(continuation), [
|
||||
callback = std::forward<Callback>(callback),
|
||||
executor = std::forward<Executor>(executor),
|
||||
nextCallback = std::forward<NextCallback>(nextCallback)
|
||||
](Args... args) mutable {
|
||||
|
||||
// In order to retrieve the correct decorator we must know what the
|
||||
// result type is.
|
||||
auto result =
|
||||
util::identity_of<decltype(std::move(callback)(std::move(args)...))>();
|
||||
|
||||
// Pick the correct invoker that handles decorating of the result
|
||||
auto invoker = decoration::invoker_of(result);
|
||||
|
||||
// Invoke the callback
|
||||
packed_dispatch(std::move(executor), std::move(invoker),
|
||||
std::move(callback), std::move(nextCallback),
|
||||
std::move(args)...);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the next hint when the callback is invoked with the given hint
|
||||
template <typename T, typename... Args>
|
||||
constexpr auto next_hint_of(util::identity<T> /*callback*/,
|
||||
signature_hint_tag<Args...> /*current*/) {
|
||||
return decoration::invoker_of(util::identity_of<decltype(std::declval<T>()(
|
||||
std::declval<Args>()...))>())
|
||||
.hint();
|
||||
}
|
||||
|
||||
/// Chains a callback together with a continuation and returns a continuation:
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
/// - Callback: [](std::string) { }
|
||||
///
|
||||
/// This function returns a function accepting the next callback in the chain:
|
||||
/// - Result: continuation<[](auto&& callback) { /*...*/ }>
|
||||
///
|
||||
template <typename Continuation, typename Callback, typename Executor>
|
||||
auto chain_continuation(Continuation&& continuation, Callback&& callback,
|
||||
Executor&& executor) {
|
||||
static_assert(is_continuation<std::decay_t<Continuation>>{},
|
||||
"Expected a continuation!");
|
||||
|
||||
// Wrap the callback into a partial callable callback
|
||||
auto partial_callable = [callback = std::forward<Callback>(callback)](
|
||||
auto&&... args) mutable {
|
||||
return util::partial_invoke(std::move(callback),
|
||||
std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
|
||||
auto hint = hint_of(util::identity_of(continuation));
|
||||
auto next_hint = next_hint_of(util::identity_of(partial_callable), hint);
|
||||
|
||||
auto ownership_ = attorney::ownership_of(continuation);
|
||||
continuation.freeze();
|
||||
|
||||
return attorney::create(
|
||||
[
|
||||
// TODO consume only the data here
|
||||
continuation = std::forward<Continuation>(continuation),
|
||||
partial_callable = std::move(partial_callable),
|
||||
executor = std::forward<Executor>(executor)
|
||||
](auto&& nextCallback) mutable {
|
||||
invoke_proxy(hint_of(util::identity_of(continuation)),
|
||||
std::move(continuation), std::move(partial_callable),
|
||||
std::move(executor),
|
||||
std::forward<decltype(nextCallback)>(nextCallback));
|
||||
},
|
||||
next_hint, ownership_);
|
||||
}
|
||||
|
||||
/// Workaround for GCC bug:
|
||||
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
|
||||
struct empty_callback {
|
||||
template <typename... Args>
|
||||
void operator()(Args...) const {
|
||||
}
|
||||
};
|
||||
|
||||
/// Final invokes the given continuation chain:
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
template <typename Continuation>
|
||||
void finalize_continuation(Continuation&& continuation) {
|
||||
attorney::invoke_continuation(std::forward<Continuation>(continuation),
|
||||
empty_callback{});
|
||||
}
|
||||
|
||||
/// Workaround for GCC bug:
|
||||
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
|
||||
template <typename T>
|
||||
class supplier_callback {
|
||||
T data_;
|
||||
|
||||
public:
|
||||
explicit supplier_callback(T data) : data_(std::move(data)) {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args...) {
|
||||
return std::move(data_);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a continuable into a functional object returning the continuable
|
||||
template <typename Continuation>
|
||||
auto wrap_continuation(Continuation&& continuation) {
|
||||
continuation.freeze();
|
||||
return supplier_callback<std::decay_t<Continuation>>(
|
||||
std::forward<Continuation>(continuation));
|
||||
}
|
||||
} // end namespace base
|
||||
|
||||
/// The namespace `compose` offers methods to chain continuations together
|
||||
/// with `all`, `any` or `seq` logic.
|
||||
namespace compose {
|
||||
struct strategy_all_tag {};
|
||||
struct strategy_any_tag {};
|
||||
|
||||
template <typename T>
|
||||
struct is_strategy : std::false_type {};
|
||||
template <>
|
||||
struct is_strategy<strategy_all_tag> : std::true_type {};
|
||||
template <>
|
||||
struct is_strategy<strategy_any_tag> : std::true_type {};
|
||||
|
||||
/// Provides support for extracting the signature hint out
|
||||
/// of given types and parameters.
|
||||
namespace annotating {
|
||||
namespace detail {
|
||||
/// Void hints are equal to an empty signature
|
||||
constexpr auto make_hint_of(util::identity<void>) noexcept {
|
||||
return signature_hint_tag<>{};
|
||||
}
|
||||
/// All other hints are the obvious hints...
|
||||
template <typename... HintArgs>
|
||||
constexpr auto make_hint_of(util::identity<HintArgs...> args) noexcept {
|
||||
return args; // Identity is equal to signature_hint_tag
|
||||
}
|
||||
} // end namespace detail
|
||||
|
||||
/// Extracts the signature hint of a given continuation and it's optional
|
||||
/// present hint arguments.
|
||||
///
|
||||
/// There are 3 cases:
|
||||
/// - Any argument is given:
|
||||
/// -> The hint is of the argument type where void is equal to no args
|
||||
/// - An unwrappable type is given which first arguments signature is known
|
||||
/// -> The hint is of the mentioned signature
|
||||
/// - An object which signature isn't known
|
||||
/// -> The hint is unknown
|
||||
///
|
||||
/// In any cases the return type is a:
|
||||
/// - signature_hint_tag<?...> or a
|
||||
/// - absent_signature_hint_tag
|
||||
///
|
||||
template <typename T, typename... HintArgs>
|
||||
constexpr auto extract(util::identity<T> /*type*/,
|
||||
util::identity<HintArgs...> hint) {
|
||||
return util::static_if(hint, util::is_empty(),
|
||||
[=](auto /*hint*/) {
|
||||
/// When the arguments are the hint is absent
|
||||
return absent_signature_hint_tag{};
|
||||
},
|
||||
[](auto hint) {
|
||||
// When hint arguments are given just take it as hint
|
||||
return detail::make_hint_of(hint);
|
||||
});
|
||||
}
|
||||
} // end namespace annotating
|
||||
|
||||
namespace detail {
|
||||
template <std::size_t Pos, typename T>
|
||||
constexpr void assign(util::size_constant<Pos> /*pos*/, T& /*storage*/) {
|
||||
// ...
|
||||
}
|
||||
template <std::size_t Pos, typename T, typename Current, typename... Args>
|
||||
void assign(util::size_constant<Pos> pos, T& storage, Current&& current,
|
||||
Args&&... args) {
|
||||
std::get<Pos>(storage) = std::forward<Current>(current);
|
||||
assign(pos + util::size_constant_of<1>(), storage,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Caches the partial results and invokes the callback when all results
|
||||
/// are arrived. This class is thread safe.
|
||||
template <typename T, std::size_t Submissions, typename... Args>
|
||||
class all_result_submitter : public std::enable_shared_from_this<
|
||||
all_result_submitter<T, Submissions, Args...>>,
|
||||
public util::non_movable {
|
||||
|
||||
T callback_;
|
||||
std::atomic<std::size_t> left_;
|
||||
std::tuple<Args...> result_;
|
||||
|
||||
public:
|
||||
explicit all_result_submitter(T callback)
|
||||
: callback_(std::move(callback)), left_(Submissions) {
|
||||
}
|
||||
|
||||
/// Creates a submitter which submits it's result into the tuple
|
||||
template <std::size_t From, std::size_t To>
|
||||
auto create_callback(util::size_constant<From> from,
|
||||
util::size_constant<To> to) {
|
||||
|
||||
return [ me = this->shared_from_this(), from, to ](auto&&... args) {
|
||||
static_assert(sizeof...(args) == (To - From),
|
||||
"Submission called with the wrong amount of arguments!");
|
||||
|
||||
// Assign the values from the result to it's correct positions of the
|
||||
// tuple. Maybe think about the thread safety again...:
|
||||
// http://stackoverflow.com/questions/40845699
|
||||
assign(from, me->result_, std::forward<decltype(args)>(args)...);
|
||||
|
||||
// Complete the current result
|
||||
me->complete_one();
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// Invokes the callback with the cached result
|
||||
void invoke() {
|
||||
assert((left_ == 0U) && "Expected that the submitter is finished!");
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
util::unpack(std::move(result_), [&](auto&&... args) {
|
||||
std::move(callback_)(std::forward<decltype(args)>(args)...);
|
||||
});
|
||||
}
|
||||
// Completes one result
|
||||
void complete_one() {
|
||||
assert((left_ > 0U) && "Expected that the submitter isn't finished!");
|
||||
|
||||
auto current = --left_;
|
||||
if (!current) {
|
||||
invoke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Invokes the callback with the first arriving result
|
||||
template <typename T>
|
||||
class any_result_submitter
|
||||
: public std::enable_shared_from_this<any_result_submitter<T>>,
|
||||
public util::non_movable {
|
||||
|
||||
T callback_;
|
||||
std::once_flag flag_;
|
||||
|
||||
public:
|
||||
explicit any_result_submitter(T callback) : callback_(std::move(callback)) {
|
||||
}
|
||||
|
||||
/// Creates a submitter which submits it's result to the callback
|
||||
auto create_callback() {
|
||||
return [me = this->shared_from_this()](auto&&... args) {
|
||||
me->invoke(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// Invokes the callback with the given arguments
|
||||
template <typename... Args>
|
||||
void invoke(Args&&... args) {
|
||||
std::call_once(flag_, std::move(callback_), std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // end namespace detail
|
||||
|
||||
/// Adds the given continuation tuple to the left composition
|
||||
template <typename... LeftArgs, typename... RightArgs>
|
||||
auto chain_composition(std::tuple<LeftArgs...> leftPack,
|
||||
std::tuple<RightArgs...> rightPack) {
|
||||
|
||||
return util::merge(std::move(leftPack), std::move(rightPack));
|
||||
}
|
||||
|
||||
/// Normalizes a continuation to a tuple holding an arbitrary count of
|
||||
/// continuations matching the given strategy.
|
||||
///
|
||||
/// Basically we can encounter 3 cases:
|
||||
/// - The continuable isn't in any strategy:
|
||||
/// -> make a tuple containing the continuable as only element
|
||||
template <typename Strategy, typename Data, typename Annotation,
|
||||
std::enable_if_t<!is_strategy<Annotation>::value>* = nullptr>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Annotation>&& continuation) {
|
||||
|
||||
// If the continuation isn't a strategy initialize the strategy
|
||||
return std::make_tuple(std::move(continuation));
|
||||
}
|
||||
/// - The continuable is in a different strategy then the current one:
|
||||
/// -> materialize it
|
||||
template <typename Strategy, typename Data, typename Annotation,
|
||||
std::enable_if_t<is_strategy<Annotation>::value>* = nullptr>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Annotation>&& continuation) {
|
||||
|
||||
// If the right continuation is a different strategy materialize it
|
||||
// in order to keep the precedence in cases where: `c1 && (c2 || c3)`.
|
||||
return std::make_tuple(base::attorney::materialize(std::move(continuation)));
|
||||
}
|
||||
/// - The continuable is inside the current strategy state:
|
||||
/// -> return the data of the tuple
|
||||
template <typename Strategy, typename Data>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Strategy>&& continuation) {
|
||||
|
||||
// If we are in the given strategy we can just use the data of the continuable
|
||||
return base::attorney::consume_data(std::move(continuation));
|
||||
}
|
||||
|
||||
/// Entry function for connecting two continuables with a given strategy.
|
||||
template <typename Strategy, typename LData, typename LAnnotation,
|
||||
typename RData, typename RAnnotation>
|
||||
auto connect(Strategy strategy, continuable_base<LData, LAnnotation>&& left,
|
||||
continuable_base<RData, RAnnotation>&& right) {
|
||||
|
||||
auto ownership_ =
|
||||
base::attorney::ownership_of(left) | base::attorney::ownership_of(right);
|
||||
|
||||
left.freeze();
|
||||
right.freeze();
|
||||
|
||||
// Make the new data which consists of a tuple containing
|
||||
// all connected continuables.
|
||||
auto data = chain_composition(normalize(strategy, std::move(left)),
|
||||
normalize(strategy, std::move(right)));
|
||||
|
||||
// Return a new continuable containing the tuple and holding
|
||||
// the current strategy as annotation.
|
||||
return base::attorney::create(std::move(data), strategy, ownership_);
|
||||
}
|
||||
|
||||
/// Creates a submitter which caches the intermediate results of `all` chains
|
||||
template <typename Callback, std::size_t Submissions, typename... Args>
|
||||
auto make_all_result_submitter(Callback&& callback,
|
||||
util::size_constant<Submissions>,
|
||||
util::identity<Args...>) {
|
||||
return std::make_shared<detail::all_result_submitter<
|
||||
std::decay_t<decltype(callback)>, Submissions, Args...>>(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
}
|
||||
|
||||
/// Finalizes the all logic of a given composition
|
||||
template <typename Data>
|
||||
auto finalize_composition(
|
||||
continuable_base<Data, strategy_all_tag>&& continuation) {
|
||||
|
||||
auto ownership_ = base::attorney::ownership_of(continuation);
|
||||
|
||||
auto composition = base::attorney::consume_data(std::move(continuation));
|
||||
|
||||
// Merge all signature hints together
|
||||
auto signature = util::unpack(composition, [](auto&... entries) {
|
||||
return util::merge(base::hint_of(util::identity_of(entries))...);
|
||||
});
|
||||
|
||||
return base::attorney::create(
|
||||
[ signature,
|
||||
composition = std::move(composition) ](auto&& callback) mutable {
|
||||
// We mark the current 2-dimensional position through a pair:
|
||||
// std::pair<size_constant<?>, size_constant<?>>
|
||||
// ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
|
||||
// Continuation pos Result pos
|
||||
auto begin = std::make_pair(util::size_constant_of<0>(),
|
||||
util::size_constant_of<0>());
|
||||
auto pack = util::identity_of(composition);
|
||||
auto end = util::pack_size_of(pack);
|
||||
auto condition = [=](auto pos) { return pos.first < end; };
|
||||
|
||||
// Create the result submitter which caches all results and invokes
|
||||
// the final callback upon completion.
|
||||
auto submitter = make_all_result_submitter(
|
||||
std::forward<decltype(callback)>(callback), end, signature);
|
||||
|
||||
// Invoke every continuation with it's callback of the submitter
|
||||
util::static_while(begin, condition, [&](auto current) mutable {
|
||||
auto entry =
|
||||
std::move(std::get<decltype(current.first)::value>(composition));
|
||||
|
||||
// This is the length of the arguments of the current continuable
|
||||
auto arg_size =
|
||||
util::pack_size_of(base::hint_of(util::identity_of(entry)));
|
||||
|
||||
// The next position in the result tuple
|
||||
auto next = current.second + arg_size;
|
||||
|
||||
// Invoke the continuation with the associated submission callback
|
||||
base::attorney::invoke_continuation(
|
||||
std::move(entry),
|
||||
submitter->create_callback(current.second, next));
|
||||
|
||||
return std::make_pair(current.first + util::size_constant_of<1>(),
|
||||
next);
|
||||
});
|
||||
},
|
||||
signature, std::move(ownership_));
|
||||
}
|
||||
|
||||
/// Creates a submitter that continues `any` chains
|
||||
template <typename Callback>
|
||||
auto make_any_result_submitter(Callback&& callback) {
|
||||
return std::make_shared<
|
||||
detail::any_result_submitter<std::decay_t<decltype(callback)>>>(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
constexpr T first_of(util::identity<T, Args...>) noexcept;
|
||||
|
||||
template <typename Signature, typename... Args>
|
||||
constexpr auto common_result_of(Signature signature, signature_hint_tag<>,
|
||||
Args... /*args*/) {
|
||||
/// Assert that the other signatures are empty too which means all signatures
|
||||
/// had the same size.
|
||||
util::static_for_each_in(util::identity<Args...>{}, [&](auto rest) {
|
||||
auto is_empty = (util::pack_size_of(rest) == util::size_constant_of<0>());
|
||||
static_assert(is_empty.value, "Expected all continuations to have the same"
|
||||
"count of arguments!");
|
||||
});
|
||||
return signature;
|
||||
}
|
||||
|
||||
/// Determine the common result between all continuation which are chained
|
||||
/// with an `any` strategy, consider two continuations:
|
||||
/// c1 with `void(int)` and c2 with `void(float)`, the common result shared
|
||||
/// between both continuations is `void(int)`.
|
||||
template <typename Signature, typename First, typename... Args>
|
||||
constexpr auto common_result_of(Signature signature, First first,
|
||||
Args... args) {
|
||||
using Common =
|
||||
util::identity<std::common_type_t<decltype(first_of(first)),
|
||||
decltype(first_of(args))...>>;
|
||||
|
||||
return common_result_of(util::push(signature, Common{}),
|
||||
util::pop_first(first), util::pop_first(args)...);
|
||||
}
|
||||
|
||||
/// Finalizes the any logic of a given composition
|
||||
template <typename Data>
|
||||
auto finalize_composition(
|
||||
continuable_base<Data, strategy_any_tag>&& continuation) {
|
||||
|
||||
auto ownership_ = base::attorney::ownership_of(continuation);
|
||||
|
||||
auto composition = base::attorney::consume_data(std::move(continuation));
|
||||
|
||||
// Determine the shared result between all continuations
|
||||
auto signature = util::unpack(composition, [](auto const&... args) {
|
||||
return common_result_of(signature_hint_tag<>{},
|
||||
base::hint_of(util::identity_of(args))...);
|
||||
});
|
||||
|
||||
return base::attorney::create(
|
||||
[composition = std::move(composition)](auto&& callback) mutable {
|
||||
|
||||
// Create the submitter which calls the given callback once at the first
|
||||
// callback invocation.
|
||||
auto submitter = make_any_result_submitter(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
|
||||
util::static_for_each_in(std::move(composition),
|
||||
[&](auto&& entry) mutable {
|
||||
// Invoke the continuation with a submission
|
||||
// callback
|
||||
base::attorney::invoke_continuation(
|
||||
std::forward<decltype(entry)>(entry),
|
||||
submitter->create_callback());
|
||||
});
|
||||
},
|
||||
signature, std::move(ownership_));
|
||||
}
|
||||
|
||||
/// Connects the left and the right continuable to a sequence
|
||||
///
|
||||
/// \note This is implemented in an eager way because we would not gain
|
||||
/// any profit from chaining sequences lazily.
|
||||
template <typename Left, typename Right>
|
||||
auto sequential_connect(Left&& left, Right&& right) {
|
||||
left.freeze(right.is_frozen());
|
||||
right.freeze();
|
||||
|
||||
return std::forward<Left>(left).then([right = std::forward<Right>(right)](
|
||||
auto&&... args) mutable {
|
||||
return std::move(right).then([previous = std::make_tuple(
|
||||
std::forward<decltype(args)>(args)...)](
|
||||
auto&&... args) mutable {
|
||||
return util::merge(
|
||||
std::move(previous),
|
||||
std::make_tuple(std::forward<decltype(args)>(args)...));
|
||||
});
|
||||
});
|
||||
}
|
||||
} // end namespace compose
|
||||
|
||||
/// Provides helper functions to transform continuations to other types
|
||||
namespace transforms {
|
||||
/// Provides helper functions and typedefs for converting callback arguments
|
||||
/// to their types a promise can accept.
|
||||
template <typename... Args>
|
||||
struct future_trait {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<std::tuple<Args...>>;
|
||||
/// Boxes the argument pack into a tuple
|
||||
static void resolve(promise_t& promise, Args... args) {
|
||||
promise.set_value(std::make_tuple(std::move(args)...));
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct future_trait<> {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<void>;
|
||||
/// Boxes the argument pack into void
|
||||
static void resolve(promise_t& promise) {
|
||||
promise.set_value();
|
||||
}
|
||||
};
|
||||
template <typename First>
|
||||
struct future_trait<First> {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<First>;
|
||||
/// Boxes the argument pack into nothing
|
||||
static void resolve(promise_t& promise, First first) {
|
||||
promise.set_value(std::move(first));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Hint>
|
||||
class promise_callback;
|
||||
|
||||
template <typename... Args>
|
||||
class promise_callback<signature_hint_tag<Args...>>
|
||||
: public future_trait<Args...> {
|
||||
|
||||
typename future_trait<Args...>::promise_t promise_;
|
||||
|
||||
public:
|
||||
constexpr promise_callback() = default;
|
||||
promise_callback(promise_callback const&) = delete;
|
||||
constexpr promise_callback(promise_callback&&) = default;
|
||||
promise_callback& operator=(promise_callback const&) = delete;
|
||||
promise_callback& operator=(promise_callback&&) = delete;
|
||||
|
||||
/// Resolves the promise
|
||||
void operator()(Args... args) {
|
||||
this->resolve(promise_, std::move(args)...);
|
||||
}
|
||||
/// Returns the future from the promise
|
||||
auto get_future() {
|
||||
return promise_.get_future();
|
||||
}
|
||||
};
|
||||
|
||||
/// Transforms the continuation to a future
|
||||
template <typename Data, typename Annotation>
|
||||
auto as_future(continuable_base<Data, Annotation>&& continuable) {
|
||||
// Create the promise which is able to supply the current arguments
|
||||
auto hint = base::hint_of(util::identity_of(continuable));
|
||||
|
||||
promise_callback<std::decay_t<decltype(hint)>> callback;
|
||||
(void)hint;
|
||||
|
||||
// Receive the future
|
||||
auto future = callback.get_future();
|
||||
|
||||
// Dispatch the continuation with the promise resolving callback
|
||||
std::move(continuable).then(std::move(callback)).done();
|
||||
|
||||
return future;
|
||||
}
|
||||
} // end namespace transforms
|
||||
} // end namespace detail
|
||||
|
||||
template <typename Data, typename Annotation>
|
||||
class continuable_base {
|
||||
/// \cond false
|
||||
@ -1024,9 +192,9 @@ public:
|
||||
/// ```
|
||||
///
|
||||
/// \since version 1.0.0
|
||||
template <typename T, typename E = detail::this_thread_executor_tag>
|
||||
template <typename T, typename E = detail::base::this_thread_executor_tag>
|
||||
auto then(T&& callback,
|
||||
E&& executor = detail::this_thread_executor_tag{}) && {
|
||||
E&& executor = detail::base::this_thread_executor_tag{}) && {
|
||||
return detail::base::chain_continuation(std::move(*this).materialize(),
|
||||
std::forward<T>(callback),
|
||||
std::forward<E>(executor));
|
||||
@ -1093,8 +261,8 @@ public:
|
||||
/// \since version 1.0.0
|
||||
template <typename OData, typename OAnnotation>
|
||||
auto operator&&(continuable_base<OData, OAnnotation>&& right) && {
|
||||
return detail::compose::connect(detail::compose::strategy_all_tag{},
|
||||
std::move(*this), std::move(right));
|
||||
return detail::composition::connect(detail::composition::strategy_all_tag{},
|
||||
std::move(*this), std::move(right));
|
||||
}
|
||||
|
||||
/// Invokes both continuable_base objects parallel and calls the
|
||||
@ -1135,8 +303,8 @@ public:
|
||||
/// \since version 1.0.0
|
||||
template <typename OData, typename OAnnotation>
|
||||
auto operator||(continuable_base<OData, OAnnotation>&& right) && {
|
||||
return detail::compose::connect(detail::compose::strategy_any_tag{},
|
||||
std::move(*this), std::move(right));
|
||||
return detail::composition::connect(detail::composition::strategy_any_tag{},
|
||||
std::move(*this), std::move(right));
|
||||
}
|
||||
|
||||
/// Invokes both continuable_base objects sequential and calls the
|
||||
@ -1163,8 +331,8 @@ public:
|
||||
/// \since version 1.0.0
|
||||
template <typename OData, typename OAnnotation>
|
||||
auto operator>>(continuable_base<OData, OAnnotation>&& right) && {
|
||||
return detail::compose::sequential_connect(std::move(*this),
|
||||
std::move(right));
|
||||
return detail::composition::sequential_connect(std::move(*this),
|
||||
std::move(right));
|
||||
}
|
||||
|
||||
/// Starts the continuation chain and returns the asynchronous
|
||||
@ -1251,19 +419,21 @@ private:
|
||||
return materializeImpl(std::move(*this));
|
||||
}
|
||||
|
||||
template <typename OData, typename OAnnotation,
|
||||
std::enable_if_t<
|
||||
!detail::compose::is_strategy<OAnnotation>::value>* = nullptr>
|
||||
template <
|
||||
typename OData, typename OAnnotation,
|
||||
std::enable_if_t<!detail::composition::is_strategy<OAnnotation>::value>* =
|
||||
nullptr>
|
||||
static auto
|
||||
materializeImpl(continuable_base<OData, OAnnotation>&& continuable) {
|
||||
return std::move(continuable);
|
||||
}
|
||||
template <typename OData, typename OAnnotation,
|
||||
std::enable_if_t<
|
||||
detail::compose::is_strategy<OAnnotation>::value>* = nullptr>
|
||||
template <
|
||||
typename OData, typename OAnnotation,
|
||||
std::enable_if_t<detail::composition::is_strategy<OAnnotation>::value>* =
|
||||
nullptr>
|
||||
static auto
|
||||
materializeImpl(continuable_base<OData, OAnnotation>&& continuable) {
|
||||
return detail::compose::finalize_composition(std::move(continuable));
|
||||
return detail::composition::finalize_composition(std::move(continuable));
|
||||
}
|
||||
|
||||
Data&& consume_data() && {
|
||||
@ -1356,7 +526,7 @@ private:
|
||||
/// \since version 1.0.0
|
||||
template <typename... Args, typename Continuation>
|
||||
auto make_continuable(Continuation&& continuation) {
|
||||
auto hint = detail::compose::annotating::extract(
|
||||
auto hint = detail::composition::annotating::extract(
|
||||
detail::util::identity_of(continuation),
|
||||
detail::util::identity<Args...>{});
|
||||
|
||||
|
||||
@ -31,11 +31,377 @@
|
||||
#ifndef CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__
|
||||
#define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "continuable/detail/api.hpp"
|
||||
#include "continuable/detail/hints.hpp"
|
||||
#include "continuable/detail/traits.hpp"
|
||||
#include "continuable/detail/util.hpp"
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
/// The namespace `base` provides the low level API for working
|
||||
/// with continuable types.
|
||||
///
|
||||
/// Important methods are:
|
||||
/// - Creating a continuation from a callback taking functional
|
||||
/// base::attorney::create(auto&& callback)
|
||||
/// -> base::continuation<auto>
|
||||
/// - Chaining a continuation together with a callback
|
||||
/// base::chain_continuation(base::continuation<auto> continuation,
|
||||
/// auto&& callback)
|
||||
/// -> base::continuation<auto>
|
||||
/// - Finally invoking the continuation chain
|
||||
/// base::finalize_continuation(base::continuation<auto> continuation)
|
||||
/// -> void
|
||||
namespace base {
|
||||
struct this_thread_executor_tag {};
|
||||
|
||||
/// Returns the signature hint of the given continuable
|
||||
template <typename T>
|
||||
constexpr auto hint_of(util::identity<T>) {
|
||||
static_assert(util::fail<T>::value,
|
||||
"Expected a continuation with an existing signature hint!");
|
||||
return util::identity_of<void>();
|
||||
}
|
||||
/// Returns the signature hint of the given continuable
|
||||
template <typename Data, typename... Args>
|
||||
constexpr auto
|
||||
hint_of(util::identity<
|
||||
continuable_base<Data, hints::signature_hint_tag<Args...>>>) {
|
||||
return hints::signature_hint_tag<Args...>{};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct is_continuation : std::false_type {};
|
||||
template <typename Data, typename Annotation>
|
||||
struct is_continuation<continuable_base<Data, Annotation>> : std::true_type {};
|
||||
|
||||
/// Helper class to access private methods and members of
|
||||
/// the continuable_base class.
|
||||
struct attorney {
|
||||
/// Makes a continuation wrapper from the given argument
|
||||
template <typename T, typename A = hints::absent_signature_hint_tag>
|
||||
static auto create(T&& continuation, A /*hint*/, util::ownership ownership_) {
|
||||
return continuable_base<std::decay_t<T>, std::decay_t<A>>(
|
||||
std::forward<T>(continuation), ownership_);
|
||||
}
|
||||
|
||||
/// Invokes a continuation object in a reference correct way
|
||||
template <typename Data, typename Annotation, typename Callback>
|
||||
static auto
|
||||
invoke_continuation(continuable_base<Data, Annotation>&& continuation,
|
||||
Callback&& callback) {
|
||||
auto materialized = std::move(continuation).materialize();
|
||||
materialized.release();
|
||||
return materialized.data_(std::forward<Callback>(callback));
|
||||
}
|
||||
|
||||
template <typename Data, typename Annotation>
|
||||
static auto materialize(continuable_base<Data, Annotation>&& continuation) {
|
||||
return std::move(continuation).materialize();
|
||||
}
|
||||
|
||||
template <typename Data, typename Annotation>
|
||||
static Data&&
|
||||
consume_data(continuable_base<Data, Annotation>&& continuation) {
|
||||
return std::move(continuation).consume_data();
|
||||
}
|
||||
|
||||
template <typename Continuable>
|
||||
static util::ownership ownership_of(Continuable&& continuation) {
|
||||
return continuation.ownership_;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the invoker of a callback, the next callback
|
||||
// and the arguments of the previous continuation.
|
||||
//
|
||||
// The return type of the invokerOf function matches a functional of:
|
||||
// void(auto&& callback, auto&& nextCallback, auto&&... args)
|
||||
//
|
||||
// The invoker decorates the result type in the following way
|
||||
// - void -> nextCallback()
|
||||
// - ? -> nextCallback(?)
|
||||
// - std::pair<?, ?> -> nextCallback(?, ?)
|
||||
// - std::tuple<?...> -> nextCallback(?...)
|
||||
//
|
||||
// When the result is a continuation itself pass the callback to it
|
||||
// - continuation<?...> -> result(nextCallback);
|
||||
namespace decoration {
|
||||
/// Helper class wrapping the underlaying unwrapping lambda
|
||||
/// in order to extend it with a hint method.
|
||||
template <typename T, typename Hint>
|
||||
class invoker : public T {
|
||||
public:
|
||||
explicit invoker(T invoke) : T(std::move(invoke)) {
|
||||
}
|
||||
|
||||
using T::operator();
|
||||
|
||||
/// Returns the underlaying signature hint
|
||||
static constexpr Hint hint() noexcept {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Args>
|
||||
constexpr auto make_invoker(T&& invoke, hints::signature_hint_tag<Args...>) {
|
||||
return invoker<std::decay_t<T>, hints::signature_hint_tag<Args...>>(
|
||||
std::forward<T>(invoke));
|
||||
}
|
||||
|
||||
/// - continuable<?...> -> result(nextCallback);
|
||||
template <typename Data, typename Annotation>
|
||||
constexpr auto invoker_of(util::identity<continuable_base<Data, Annotation>>) {
|
||||
/// Get the hint of the unwrapped returned continuable
|
||||
using Type = decltype(attorney::materialize(
|
||||
std::declval<continuable_base<Data, Annotation>>()));
|
||||
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto continuation_ = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
attorney::invoke_continuation(
|
||||
std::move(continuation_),
|
||||
std::forward<decltype(nextCallback)>(nextCallback));
|
||||
},
|
||||
hint_of(util::identity_of<Type>()));
|
||||
}
|
||||
|
||||
/// - ? -> nextCallback(?)
|
||||
template <typename T>
|
||||
auto invoker_of(util::identity<T>) {
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto result = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)(std::move(result));
|
||||
},
|
||||
util::identity_of<T>());
|
||||
}
|
||||
|
||||
/// - void -> nextCallback()
|
||||
inline auto invoker_of(util::identity<void>) {
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)();
|
||||
},
|
||||
util::identity<>{});
|
||||
}
|
||||
|
||||
/// Returns a sequenced invoker which is able to invoke
|
||||
/// objects where std::get is applicable.
|
||||
inline auto sequenced_unpack_invoker() {
|
||||
return [](auto&& callback, auto&& nextCallback, auto&&... args) {
|
||||
auto result = std::forward<decltype(callback)>(callback)(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
util::unpack(std::move(result), [&](auto&&... types) {
|
||||
/// TODO Add inplace resolution here
|
||||
|
||||
std::forward<decltype(nextCallback)>(nextCallback)(
|
||||
std::forward<decltype(types)>(types)...);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// - std::pair<?, ?> -> nextCallback(?, ?)
|
||||
template <typename First, typename Second>
|
||||
constexpr auto invoker_of(util::identity<std::pair<First, Second>>) {
|
||||
return make_invoker(sequenced_unpack_invoker(),
|
||||
util::identity<First, Second>{});
|
||||
}
|
||||
|
||||
// - std::tuple<?...> -> nextCallback(?...)
|
||||
template <typename... Args>
|
||||
constexpr auto invoker_of(util::identity<std::tuple<Args...>>) {
|
||||
return make_invoker(sequenced_unpack_invoker(), util::identity<Args...>{});
|
||||
}
|
||||
} // end namespace decoration
|
||||
|
||||
/// Invoke the callback immediately
|
||||
template <typename Invoker, typename Callback, typename NextCallback,
|
||||
typename... Args>
|
||||
void packed_dispatch(this_thread_executor_tag, Invoker&& invoker,
|
||||
Callback&& callback, NextCallback&& nextCallback,
|
||||
Args&&... args) {
|
||||
|
||||
// Invoke the callback with the decorated invoker immediately
|
||||
std::forward<Invoker>(invoker)(std::forward<Callback>(callback),
|
||||
std::forward<NextCallback>(nextCallback),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Invoke the callback through the given executor
|
||||
template <typename Executor, typename Invoker, typename Callback,
|
||||
typename NextCallback, typename... Args>
|
||||
void packed_dispatch(Executor&& executor, Invoker&& invoker,
|
||||
Callback&& callback, NextCallback&& nextCallback,
|
||||
Args&&... args) {
|
||||
|
||||
// Create a worker object which when invoked calls the callback with the
|
||||
// the returned arguments.
|
||||
auto work = [
|
||||
invoker = std::forward<Invoker>(invoker),
|
||||
callback = std::forward<Callback>(callback),
|
||||
nextCallback = std::forward<NextCallback>(nextCallback),
|
||||
args = std::make_tuple(std::forward<Args>(args)...)
|
||||
]() mutable {
|
||||
util::unpack(std::move(args), [&](auto&&... captured_args) {
|
||||
// Just use the packed dispatch method which dispatches the work on
|
||||
// the current thread.
|
||||
packed_dispatch(this_thread_executor_tag{}, std::move(invoker),
|
||||
std::move(callback), std::move(nextCallback),
|
||||
std::forward<decltype(captured_args)>(captured_args)...);
|
||||
});
|
||||
};
|
||||
|
||||
// Pass the work functional object to the executor
|
||||
std::forward<Executor>(executor)(std::move(work));
|
||||
}
|
||||
|
||||
/// Invokes a continuation with a given callback.
|
||||
/// Passes the next callback to the resulting continuable or
|
||||
/// invokes the next callback directly if possible.
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
/// - Callback: [](std::string) { }
|
||||
/// - NextCallback: []() { }
|
||||
///
|
||||
template <typename... Args, typename Continuation, typename Callback,
|
||||
typename Executor, typename NextCallback>
|
||||
void invoke_proxy(hints::signature_hint_tag<Args...>,
|
||||
Continuation&& continuation, Callback&& callback,
|
||||
Executor&& executor, NextCallback&& nextCallback) {
|
||||
|
||||
// Invoke the continuation with a proxy callback.
|
||||
// The proxy callback is responsible for passing the
|
||||
// the result to the callback as well as decorating it.
|
||||
attorney::invoke_continuation(std::forward<Continuation>(continuation), [
|
||||
callback = std::forward<Callback>(callback),
|
||||
executor = std::forward<Executor>(executor),
|
||||
nextCallback = std::forward<NextCallback>(nextCallback)
|
||||
](Args... args) mutable {
|
||||
|
||||
// In order to retrieve the correct decorator we must know what the
|
||||
// result type is.
|
||||
auto result =
|
||||
util::identity_of<decltype(std::move(callback)(std::move(args)...))>();
|
||||
|
||||
// Pick the correct invoker that handles decorating of the result
|
||||
auto invoker = decoration::invoker_of(result);
|
||||
|
||||
// Invoke the callback
|
||||
packed_dispatch(std::move(executor), std::move(invoker),
|
||||
std::move(callback), std::move(nextCallback),
|
||||
std::move(args)...);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the next hint when the callback is invoked with the given hint
|
||||
template <typename T, typename... Args>
|
||||
constexpr auto next_hint_of(util::identity<T> /*callback*/,
|
||||
hints::signature_hint_tag<Args...> /*current*/) {
|
||||
return decoration::invoker_of(util::identity_of<decltype(std::declval<T>()(
|
||||
std::declval<Args>()...))>())
|
||||
.hint();
|
||||
}
|
||||
|
||||
/// Chains a callback together with a continuation and returns a continuation:
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
/// - Callback: [](std::string) { }
|
||||
///
|
||||
/// This function returns a function accepting the next callback in the chain:
|
||||
/// - Result: continuation<[](auto&& callback) { /*...*/ }>
|
||||
///
|
||||
template <typename Continuation, typename Callback, typename Executor>
|
||||
auto chain_continuation(Continuation&& continuation, Callback&& callback,
|
||||
Executor&& executor) {
|
||||
static_assert(is_continuation<std::decay_t<Continuation>>{},
|
||||
"Expected a continuation!");
|
||||
|
||||
// Wrap the callback into a partial callable callback
|
||||
auto partial_callable = [callback = std::forward<Callback>(callback)](
|
||||
auto&&... args) mutable {
|
||||
return util::partial_invoke(std::move(callback),
|
||||
std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
|
||||
auto hint = hint_of(util::identity_of(continuation));
|
||||
auto next_hint = next_hint_of(util::identity_of(partial_callable), hint);
|
||||
|
||||
auto ownership_ = attorney::ownership_of(continuation);
|
||||
continuation.freeze();
|
||||
|
||||
return attorney::create(
|
||||
[
|
||||
// TODO consume only the data here
|
||||
continuation = std::forward<Continuation>(continuation),
|
||||
partial_callable = std::move(partial_callable),
|
||||
executor = std::forward<Executor>(executor)
|
||||
](auto&& nextCallback) mutable {
|
||||
invoke_proxy(hint_of(util::identity_of(continuation)),
|
||||
std::move(continuation), std::move(partial_callable),
|
||||
std::move(executor),
|
||||
std::forward<decltype(nextCallback)>(nextCallback));
|
||||
},
|
||||
next_hint, ownership_);
|
||||
}
|
||||
|
||||
/// Workaround for GCC bug:
|
||||
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
|
||||
struct empty_callback {
|
||||
template <typename... Args>
|
||||
void operator()(Args...) const {
|
||||
}
|
||||
};
|
||||
|
||||
/// Final invokes the given continuation chain:
|
||||
///
|
||||
/// For example given:
|
||||
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
||||
template <typename Continuation>
|
||||
void finalize_continuation(Continuation&& continuation) {
|
||||
attorney::invoke_continuation(std::forward<Continuation>(continuation),
|
||||
empty_callback{});
|
||||
}
|
||||
|
||||
/// Workaround for GCC bug:
|
||||
/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095
|
||||
template <typename T>
|
||||
class supplier_callback {
|
||||
T data_;
|
||||
|
||||
public:
|
||||
explicit supplier_callback(T data) : data_(std::move(data)) {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args...) {
|
||||
return std::move(data_);
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a continuable into a functional object returning the continuable
|
||||
template <typename Continuation>
|
||||
auto wrap_continuation(Continuation&& continuation) {
|
||||
continuation.freeze();
|
||||
return supplier_callback<std::decay_t<Continuation>>(
|
||||
std::forward<Continuation>(continuation));
|
||||
}
|
||||
} // end namespace base
|
||||
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
|
||||
@ -31,11 +31,400 @@
|
||||
#ifndef CONTINUABLE_DETAIL_COMPOSITION_HPP_INCLUDED__
|
||||
#define CONTINUABLE_DETAIL_COMPOSITION_HPP_INCLUDED__
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "continuable/detail/api.hpp"
|
||||
#include "continuable/detail/base.hpp"
|
||||
#include "continuable/detail/traits.hpp"
|
||||
#include "continuable/detail/util.hpp"
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
//
|
||||
/// The namespace `composition` offers methods to chain continuations together
|
||||
/// with `all`, `any` or `seq` logic.
|
||||
namespace composition {
|
||||
struct strategy_all_tag {};
|
||||
struct strategy_any_tag {};
|
||||
|
||||
template <typename T>
|
||||
struct is_strategy : std::false_type {};
|
||||
template <>
|
||||
struct is_strategy<strategy_all_tag> : std::true_type {};
|
||||
template <>
|
||||
struct is_strategy<strategy_any_tag> : std::true_type {};
|
||||
|
||||
/// Provides support for extracting the signature hint out
|
||||
/// of given types and parameters.
|
||||
namespace annotating {
|
||||
namespace detail {
|
||||
/// Void hints are equal to an empty signature
|
||||
constexpr auto make_hint_of(util::identity<void>) noexcept {
|
||||
return hints::signature_hint_tag<>{};
|
||||
}
|
||||
/// All other hints are the obvious hints...
|
||||
template <typename... HintArgs>
|
||||
constexpr auto make_hint_of(util::identity<HintArgs...> args) noexcept {
|
||||
return args; // Identity is equal to signature_hint_tag
|
||||
}
|
||||
} // end namespace detail
|
||||
|
||||
/// Extracts the signature hint of a given continuation and it's optional
|
||||
/// present hint arguments.
|
||||
///
|
||||
/// There are 3 cases:
|
||||
/// - Any argument is given:
|
||||
/// -> The hint is of the argument type where void is equal to no args
|
||||
/// - An unwrappable type is given which first arguments signature is known
|
||||
/// -> The hint is of the mentioned signature
|
||||
/// - An object which signature isn't known
|
||||
/// -> The hint is unknown
|
||||
///
|
||||
/// In any cases the return type is a:
|
||||
/// - signature_hint_tag<?...> or a
|
||||
/// - absent_signature_hint_tag
|
||||
///
|
||||
template <typename T, typename... HintArgs>
|
||||
constexpr auto extract(util::identity<T> /*type*/,
|
||||
util::identity<HintArgs...> hint) {
|
||||
return util::static_if(hint, util::is_empty(),
|
||||
[=](auto /*hint*/) {
|
||||
/// When the arguments are the hint is absent
|
||||
return hints::absent_signature_hint_tag{};
|
||||
},
|
||||
[](auto hint) {
|
||||
// When hint arguments are given just take it as hint
|
||||
return detail::make_hint_of(hint);
|
||||
});
|
||||
}
|
||||
} // end namespace annotating
|
||||
|
||||
namespace detail {
|
||||
template <std::size_t Pos, typename T>
|
||||
constexpr void assign(util::size_constant<Pos> /*pos*/, T& /*storage*/) {
|
||||
// ...
|
||||
}
|
||||
template <std::size_t Pos, typename T, typename Current, typename... Args>
|
||||
void assign(util::size_constant<Pos> pos, T& storage, Current&& current,
|
||||
Args&&... args) {
|
||||
std::get<Pos>(storage) = std::forward<Current>(current);
|
||||
assign(pos + util::size_constant_of<1>(), storage,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Caches the partial results and invokes the callback when all results
|
||||
/// are arrived. This class is thread safe.
|
||||
template <typename T, std::size_t Submissions, typename... Args>
|
||||
class all_result_submitter : public std::enable_shared_from_this<
|
||||
all_result_submitter<T, Submissions, Args...>>,
|
||||
public util::non_movable {
|
||||
|
||||
T callback_;
|
||||
std::atomic<std::size_t> left_;
|
||||
std::tuple<Args...> result_;
|
||||
|
||||
public:
|
||||
explicit all_result_submitter(T callback)
|
||||
: callback_(std::move(callback)), left_(Submissions) {
|
||||
}
|
||||
|
||||
/// Creates a submitter which submits it's result into the tuple
|
||||
template <std::size_t From, std::size_t To>
|
||||
auto create_callback(util::size_constant<From> from,
|
||||
util::size_constant<To> to) {
|
||||
|
||||
return [ me = this->shared_from_this(), from, to ](auto&&... args) {
|
||||
static_assert(sizeof...(args) == (To - From),
|
||||
"Submission called with the wrong amount of arguments!");
|
||||
|
||||
// Assign the values from the result to it's correct positions of the
|
||||
// tuple. Maybe think about the thread safety again...:
|
||||
// http://stackoverflow.com/questions/40845699
|
||||
assign(from, me->result_, std::forward<decltype(args)>(args)...);
|
||||
|
||||
// Complete the current result
|
||||
me->complete_one();
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// Invokes the callback with the cached result
|
||||
void invoke() {
|
||||
assert((left_ == 0U) && "Expected that the submitter is finished!");
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
util::unpack(std::move(result_), [&](auto&&... args) {
|
||||
std::move(callback_)(std::forward<decltype(args)>(args)...);
|
||||
});
|
||||
}
|
||||
// Completes one result
|
||||
void complete_one() {
|
||||
assert((left_ > 0U) && "Expected that the submitter isn't finished!");
|
||||
|
||||
auto current = --left_;
|
||||
if (!current) {
|
||||
invoke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Invokes the callback with the first arriving result
|
||||
template <typename T>
|
||||
class any_result_submitter
|
||||
: public std::enable_shared_from_this<any_result_submitter<T>>,
|
||||
public util::non_movable {
|
||||
|
||||
T callback_;
|
||||
std::once_flag flag_;
|
||||
|
||||
public:
|
||||
explicit any_result_submitter(T callback) : callback_(std::move(callback)) {
|
||||
}
|
||||
|
||||
/// Creates a submitter which submits it's result to the callback
|
||||
auto create_callback() {
|
||||
return [me = this->shared_from_this()](auto&&... args) {
|
||||
me->invoke(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// Invokes the callback with the given arguments
|
||||
template <typename... Args>
|
||||
void invoke(Args&&... args) {
|
||||
std::call_once(flag_, std::move(callback_), std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // end namespace detail
|
||||
|
||||
/// Adds the given continuation tuple to the left composition
|
||||
template <typename... LeftArgs, typename... RightArgs>
|
||||
auto chain_composition(std::tuple<LeftArgs...> leftPack,
|
||||
std::tuple<RightArgs...> rightPack) {
|
||||
|
||||
return util::merge(std::move(leftPack), std::move(rightPack));
|
||||
}
|
||||
|
||||
/// Normalizes a continuation to a tuple holding an arbitrary count of
|
||||
/// continuations matching the given strategy.
|
||||
///
|
||||
/// Basically we can encounter 3 cases:
|
||||
/// - The continuable isn't in any strategy:
|
||||
/// -> make a tuple containing the continuable as only element
|
||||
template <typename Strategy, typename Data, typename Annotation,
|
||||
std::enable_if_t<!is_strategy<Annotation>::value>* = nullptr>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Annotation>&& continuation) {
|
||||
|
||||
// If the continuation isn't a strategy initialize the strategy
|
||||
return std::make_tuple(std::move(continuation));
|
||||
}
|
||||
/// - The continuable is in a different strategy then the current one:
|
||||
/// -> materialize it
|
||||
template <typename Strategy, typename Data, typename Annotation,
|
||||
std::enable_if_t<is_strategy<Annotation>::value>* = nullptr>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Annotation>&& continuation) {
|
||||
|
||||
// If the right continuation is a different strategy materialize it
|
||||
// in order to keep the precedence in cases where: `c1 && (c2 || c3)`.
|
||||
return std::make_tuple(base::attorney::materialize(std::move(continuation)));
|
||||
}
|
||||
/// - The continuable is inside the current strategy state:
|
||||
/// -> return the data of the tuple
|
||||
template <typename Strategy, typename Data>
|
||||
auto normalize(Strategy /*strategy*/,
|
||||
continuable_base<Data, Strategy>&& continuation) {
|
||||
|
||||
// If we are in the given strategy we can just use the data of the continuable
|
||||
return base::attorney::consume_data(std::move(continuation));
|
||||
}
|
||||
|
||||
/// Entry function for connecting two continuables with a given strategy.
|
||||
template <typename Strategy, typename LData, typename LAnnotation,
|
||||
typename RData, typename RAnnotation>
|
||||
auto connect(Strategy strategy, continuable_base<LData, LAnnotation>&& left,
|
||||
continuable_base<RData, RAnnotation>&& right) {
|
||||
|
||||
auto ownership_ =
|
||||
base::attorney::ownership_of(left) | base::attorney::ownership_of(right);
|
||||
|
||||
left.freeze();
|
||||
right.freeze();
|
||||
|
||||
// Make the new data which consists of a tuple containing
|
||||
// all connected continuables.
|
||||
auto data = chain_composition(normalize(strategy, std::move(left)),
|
||||
normalize(strategy, std::move(right)));
|
||||
|
||||
// Return a new continuable containing the tuple and holding
|
||||
// the current strategy as annotation.
|
||||
return base::attorney::create(std::move(data), strategy, ownership_);
|
||||
}
|
||||
|
||||
/// Creates a submitter which caches the intermediate results of `all` chains
|
||||
template <typename Callback, std::size_t Submissions, typename... Args>
|
||||
auto make_all_result_submitter(Callback&& callback,
|
||||
util::size_constant<Submissions>,
|
||||
util::identity<Args...>) {
|
||||
return std::make_shared<detail::all_result_submitter<
|
||||
std::decay_t<decltype(callback)>, Submissions, Args...>>(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
}
|
||||
|
||||
/// Finalizes the all logic of a given composition
|
||||
template <typename Data>
|
||||
auto finalize_composition(
|
||||
continuable_base<Data, strategy_all_tag>&& continuation) {
|
||||
|
||||
auto ownership_ = base::attorney::ownership_of(continuation);
|
||||
|
||||
auto composition = base::attorney::consume_data(std::move(continuation));
|
||||
|
||||
// Merge all signature hints together
|
||||
auto signature = util::unpack(composition, [](auto&... entries) {
|
||||
return util::merge(base::hint_of(util::identity_of(entries))...);
|
||||
});
|
||||
|
||||
return base::attorney::create(
|
||||
[ signature,
|
||||
composition = std::move(composition) ](auto&& callback) mutable {
|
||||
// We mark the current 2-dimensional position through a pair:
|
||||
// std::pair<size_constant<?>, size_constant<?>>
|
||||
// ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
|
||||
// Continuation pos Result pos
|
||||
auto begin = std::make_pair(util::size_constant_of<0>(),
|
||||
util::size_constant_of<0>());
|
||||
auto pack = util::identity_of(composition);
|
||||
auto end = util::pack_size_of(pack);
|
||||
auto condition = [=](auto pos) { return pos.first < end; };
|
||||
|
||||
// Create the result submitter which caches all results and invokes
|
||||
// the final callback upon completion.
|
||||
auto submitter = make_all_result_submitter(
|
||||
std::forward<decltype(callback)>(callback), end, signature);
|
||||
|
||||
// Invoke every continuation with it's callback of the submitter
|
||||
util::static_while(begin, condition, [&](auto current) mutable {
|
||||
auto entry =
|
||||
std::move(std::get<decltype(current.first)::value>(composition));
|
||||
|
||||
// This is the length of the arguments of the current continuable
|
||||
auto arg_size =
|
||||
util::pack_size_of(base::hint_of(util::identity_of(entry)));
|
||||
|
||||
// The next position in the result tuple
|
||||
auto next = current.second + arg_size;
|
||||
|
||||
// Invoke the continuation with the associated submission callback
|
||||
base::attorney::invoke_continuation(
|
||||
std::move(entry),
|
||||
submitter->create_callback(current.second, next));
|
||||
|
||||
return std::make_pair(current.first + util::size_constant_of<1>(),
|
||||
next);
|
||||
});
|
||||
},
|
||||
signature, std::move(ownership_));
|
||||
}
|
||||
|
||||
/// Creates a submitter that continues `any` chains
|
||||
template <typename Callback>
|
||||
auto make_any_result_submitter(Callback&& callback) {
|
||||
return std::make_shared<
|
||||
detail::any_result_submitter<std::decay_t<decltype(callback)>>>(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
constexpr T first_of(util::identity<T, Args...>) noexcept;
|
||||
|
||||
template <typename Signature, typename... Args>
|
||||
constexpr auto common_result_of(Signature signature,
|
||||
hints::signature_hint_tag<>, Args... /*args*/) {
|
||||
/// Assert that the other signatures are empty too which means all signatures
|
||||
/// had the same size.
|
||||
util::static_for_each_in(util::identity<Args...>{}, [&](auto rest) {
|
||||
auto is_empty = (util::pack_size_of(rest) == util::size_constant_of<0>());
|
||||
static_assert(is_empty.value, "Expected all continuations to have the same"
|
||||
"count of arguments!");
|
||||
});
|
||||
return signature;
|
||||
}
|
||||
|
||||
/// Determine the common result between all continuation which are chained
|
||||
/// with an `any` strategy, consider two continuations:
|
||||
/// c1 with `void(int)` and c2 with `void(float)`, the common result shared
|
||||
/// between both continuations is `void(int)`.
|
||||
template <typename Signature, typename First, typename... Args>
|
||||
constexpr auto common_result_of(Signature signature, First first,
|
||||
Args... args) {
|
||||
using Common =
|
||||
util::identity<std::common_type_t<decltype(first_of(first)),
|
||||
decltype(first_of(args))...>>;
|
||||
|
||||
return common_result_of(util::push(signature, Common{}),
|
||||
util::pop_first(first), util::pop_first(args)...);
|
||||
}
|
||||
|
||||
/// Finalizes the any logic of a given composition
|
||||
template <typename Data>
|
||||
auto finalize_composition(
|
||||
continuable_base<Data, strategy_any_tag>&& continuation) {
|
||||
|
||||
auto ownership_ = base::attorney::ownership_of(continuation);
|
||||
|
||||
auto composition = base::attorney::consume_data(std::move(continuation));
|
||||
|
||||
// Determine the shared result between all continuations
|
||||
auto signature = util::unpack(composition, [](auto const&... args) {
|
||||
return common_result_of(signature_hint_tag<>{},
|
||||
base::hint_of(util::identity_of(args))...);
|
||||
});
|
||||
|
||||
return base::attorney::create(
|
||||
[composition = std::move(composition)](auto&& callback) mutable {
|
||||
|
||||
// Create the submitter which calls the given callback once at the first
|
||||
// callback invocation.
|
||||
auto submitter = make_any_result_submitter(
|
||||
std::forward<decltype(callback)>(callback));
|
||||
|
||||
util::static_for_each_in(std::move(composition),
|
||||
[&](auto&& entry) mutable {
|
||||
// Invoke the continuation with a submission
|
||||
// callback
|
||||
base::attorney::invoke_continuation(
|
||||
std::forward<decltype(entry)>(entry),
|
||||
submitter->create_callback());
|
||||
});
|
||||
},
|
||||
signature, std::move(ownership_));
|
||||
}
|
||||
|
||||
/// Connects the left and the right continuable to a sequence
|
||||
///
|
||||
/// \note This is implemented in an eager way because we would not gain
|
||||
/// any profit from chaining sequences lazily.
|
||||
template <typename Left, typename Right>
|
||||
auto sequential_connect(Left&& left, Right&& right) {
|
||||
left.freeze(right.is_frozen());
|
||||
right.freeze();
|
||||
|
||||
return std::forward<Left>(left).then([right = std::forward<Right>(right)](
|
||||
auto&&... args) mutable {
|
||||
return std::move(right).then([previous = std::make_tuple(
|
||||
std::forward<decltype(args)>(args)...)](
|
||||
auto&&... args) mutable {
|
||||
return util::merge(
|
||||
std::move(previous),
|
||||
std::make_tuple(std::forward<decltype(args)>(args)...));
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace composition
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
|
||||
@ -31,11 +31,25 @@
|
||||
#ifndef CONTINUABLE_DETAIL_HINTS_HPP_INCLUDED__
|
||||
#define CONTINUABLE_DETAIL_HINTS_HPP_INCLUDED__
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "continuable/detail/api.hpp"
|
||||
#include "continuable/detail/util.hpp"
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
//
|
||||
namespace hints {
|
||||
/// Represents a present signature hint
|
||||
template <typename... Args>
|
||||
using signature_hint_tag = util::identity<Args...>;
|
||||
/// Represents an absent signature hint
|
||||
struct absent_signature_hint_tag {};
|
||||
|
||||
template <typename>
|
||||
struct is_absent_hint : std::false_type {};
|
||||
template <>
|
||||
struct is_absent_hint<absent_signature_hint_tag> : std::true_type {};
|
||||
} // namespace hints
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
|
||||
120
include/continuable/detail/transforms.hpp
Normal file
120
include/continuable/detail/transforms.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
|
||||
/**
|
||||
|
||||
/~` _ _ _|_. _ _ |_ | _
|
||||
\_,(_)| | | || ||_|(_||_)|(/_
|
||||
|
||||
https://github.com/Naios/continuable
|
||||
v2.0.0
|
||||
|
||||
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files(the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions :
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
**/
|
||||
|
||||
#ifndef CONTINUABLE_DETAIL_TRANSFORMS_HPP_INCLUDED__
|
||||
#define CONTINUABLE_DETAIL_TRANSFORMS_HPP_INCLUDED__
|
||||
|
||||
#include <future>
|
||||
|
||||
#include "continuable/detail/api.hpp"
|
||||
#include "continuable/detail/hints.hpp"
|
||||
|
||||
namespace cti {
|
||||
namespace detail {
|
||||
/// Provides helper functions to transform continuations to other types
|
||||
namespace transforms {
|
||||
/// Provides helper functions and typedefs for converting callback arguments
|
||||
/// to their types a promise can accept.
|
||||
template <typename... Args>
|
||||
struct future_trait {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<std::tuple<Args...>>;
|
||||
/// Boxes the argument pack into a tuple
|
||||
static void resolve(promise_t& promise, Args... args) {
|
||||
promise.set_value(std::make_tuple(std::move(args)...));
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct future_trait<> {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<void>;
|
||||
/// Boxes the argument pack into void
|
||||
static void resolve(promise_t& promise) {
|
||||
promise.set_value();
|
||||
}
|
||||
};
|
||||
template <typename First>
|
||||
struct future_trait<First> {
|
||||
/// The promise type used to create the future
|
||||
using promise_t = std::promise<First>;
|
||||
/// Boxes the argument pack into nothing
|
||||
static void resolve(promise_t& promise, First first) {
|
||||
promise.set_value(std::move(first));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Hint>
|
||||
class promise_callback;
|
||||
|
||||
template <typename... Args>
|
||||
class promise_callback<hints::signature_hint_tag<Args...>>
|
||||
: public future_trait<Args...> {
|
||||
|
||||
typename future_trait<Args...>::promise_t promise_;
|
||||
|
||||
public:
|
||||
constexpr promise_callback() = default;
|
||||
promise_callback(promise_callback const&) = delete;
|
||||
constexpr promise_callback(promise_callback&&) = default;
|
||||
promise_callback& operator=(promise_callback const&) = delete;
|
||||
promise_callback& operator=(promise_callback&&) = delete;
|
||||
|
||||
/// Resolves the promise
|
||||
void operator()(Args... args) {
|
||||
this->resolve(promise_, std::move(args)...);
|
||||
}
|
||||
/// Returns the future from the promise
|
||||
auto get_future() {
|
||||
return promise_.get_future();
|
||||
}
|
||||
};
|
||||
|
||||
/// Transforms the continuation to a future
|
||||
template <typename Data, typename Annotation>
|
||||
auto as_future(continuable_base<Data, Annotation>&& continuable) {
|
||||
// Create the promise which is able to supply the current arguments
|
||||
auto hint = base::hint_of(util::identity_of(continuable));
|
||||
|
||||
promise_callback<std::decay_t<decltype(hint)>> callback;
|
||||
(void)hint;
|
||||
|
||||
// Receive the future
|
||||
auto future = callback.get_future();
|
||||
|
||||
// Dispatch the continuation with the promise resolving callback
|
||||
std::move(continuable).then(std::move(callback)).done();
|
||||
|
||||
return future;
|
||||
}
|
||||
} // namespace transforms
|
||||
} // namespace detail
|
||||
} // namespace cti
|
||||
|
||||
#endif // CONTINUABLE_DETAIL_TRANSFORMS_HPP_INCLUDED__
|
||||
@ -236,7 +236,7 @@ constexpr auto pack_size_of(identity<Args...>) noexcept {
|
||||
|
||||
/// Returns an index sequence of the given type
|
||||
template <typename T>
|
||||
constexpr auto sequenceOf(T&& /*sequenceable*/) noexcept {
|
||||
constexpr auto sequence_of(T&& /*sequenceable*/) noexcept {
|
||||
return std::make_index_sequence<decltype(
|
||||
pack_size_of(std::declval<T>()))::value>();
|
||||
}
|
||||
@ -244,7 +244,7 @@ constexpr auto sequenceOf(T&& /*sequenceable*/) noexcept {
|
||||
/// Returns a check which returns a true type if the current value
|
||||
/// is below the
|
||||
template <std::size_t End>
|
||||
constexpr auto isLessThen(size_constant<End> end) noexcept {
|
||||
constexpr auto is_less_than(size_constant<End> end) noexcept {
|
||||
return [=](auto current) { return end > current; };
|
||||
}
|
||||
|
||||
@ -256,7 +256,7 @@ constexpr auto is_valid(T&& /*type*/, Check&& /*check*/) noexcept {
|
||||
|
||||
/// Creates a static functional validator object.
|
||||
template <typename Check>
|
||||
constexpr auto validatorOf(Check&& check) noexcept(
|
||||
constexpr auto validator_of(Check&& check) noexcept(
|
||||
std::is_nothrow_move_constructible<std::decay_t<Check>>::value) {
|
||||
return [check = std::forward<Check>(check)](auto&& matchable) {
|
||||
return is_valid(std::forward<decltype(matchable)>(matchable), check);
|
||||
@ -340,7 +340,7 @@ constexpr auto unpack(F&& firstSequenceable, S&& secondSequenceable,
|
||||
template <typename F, typename U>
|
||||
auto unpack(F&& firstSequenceable, U&& unpacker) {
|
||||
return unpack(std::forward<F>(firstSequenceable), std::forward<U>(unpacker),
|
||||
sequenceOf(identity_of(firstSequenceable)));
|
||||
sequence_of(identity_of(firstSequenceable)));
|
||||
}
|
||||
/// Calls the given unpacker with the content of the given sequenceables
|
||||
template <typename F, typename S, typename U>
|
||||
@ -348,8 +348,8 @@ constexpr auto unpack(F&& firstSequenceable, S&& secondSequenceable,
|
||||
U&& unpacker) {
|
||||
return unpack(std::forward<F>(firstSequenceable),
|
||||
std::forward<S>(secondSequenceable), std::forward<U>(unpacker),
|
||||
sequenceOf(identity_of(firstSequenceable)),
|
||||
sequenceOf(identity_of(secondSequenceable)));
|
||||
sequence_of(identity_of(firstSequenceable)),
|
||||
sequence_of(identity_of(secondSequenceable)));
|
||||
}
|
||||
|
||||
/// Applies the handler function to each element contained in the sequenceable
|
||||
|
||||
@ -9,6 +9,7 @@ set(LIB_SOURCES_DETAIL
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/hints.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/features.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/traits.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/transforms.hpp
|
||||
${CMAKE_SOURCE_DIR}/include/continuable/detail/util.hpp)
|
||||
set(TEST
|
||||
${CMAKE_CURRENT_LIST_DIR}/test-playground.cpp)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user