Split more functionality into seperate header

This commit is contained in:
Denis Blank 2017-09-25 03:28:00 +02:00
parent bd68d14b34
commit bc2d46ff40
7 changed files with 919 additions and 859 deletions

View File

@ -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...>{});

View File

@ -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

View File

@ -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

View File

@ -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

View 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__

View File

@ -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

View File

@ -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)