diff --git a/include/continuable/continuable-base.hpp b/include/continuable/continuable-base.hpp index f22d353..d79020f 100644 --- a/include/continuable/continuable-base.hpp +++ b/include/continuable/continuable-base.hpp @@ -31,852 +31,20 @@ #ifndef CONTINUABLE_BASE_HPP_INCLUDED__ #define CONTINUABLE_BASE_HPP_INCLUDED__ -#include #include #include -#include -#include -#include #include #include #include #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 -using signature_hint_tag = util::identity; -/// Represents an absent signature hint -struct absent_signature_hint_tag {}; - -template -struct is_absent_hint : std::false_type {}; -template <> -struct is_absent_hint : 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 -/// - Chaining a continuation together with a callback -/// base::chain_continuation(base::continuation continuation, -/// auto&& callback) -/// -> base::continuation -/// - Finally invoking the continuation chain -/// base::finalize_continuation(base::continuation continuation) -/// -> void -namespace base { -/// Returns the signature hint of the given continuable -template -constexpr auto hint_of(util::identity) { - static_assert(util::fail::value, - "Expected a continuation with an existing signature hint!"); - return util::identity_of(); -} -/// Returns the signature hint of the given continuable -template -constexpr auto -hint_of(util::identity>>) { - return signature_hint_tag{}; -} - -template -struct is_continuation : std::false_type {}; -template -struct is_continuation> : 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 - static auto create(T&& continuation, A /*hint*/, util::ownership ownership_) { - return continuable_base, std::decay_t>( - std::forward(continuation), ownership_); - } - - /// Invokes a continuation object in a reference correct way - template - static auto - invoke_continuation(continuable_base&& continuation, - Callback&& callback) { - auto materialized = std::move(continuation).materialize(); - materialized.release(); - return materialized.data_(std::forward(callback)); - } - - template - static auto materialize(continuable_base&& continuation) { - return std::move(continuation).materialize(); - } - - template - static Data&& - consume_data(continuable_base&& continuation) { - return std::move(continuation).consume_data(); - } - - template - 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 -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 -constexpr auto make_invoker(T&& invoke, signature_hint_tag) { - return invoker, signature_hint_tag>( - std::forward(invoke)); -} - -/// - continuable -> result(nextCallback); -template -constexpr auto invoker_of(util::identity>) { - /// Get the hint of the unwrapped returned continuable - using Type = decltype(attorney::materialize( - std::declval>())); - - return make_invoker( - [](auto&& callback, auto&& nextCallback, auto&&... args) { - auto continuation_ = std::forward(callback)( - std::forward(args)...); - - attorney::invoke_continuation( - std::move(continuation_), - std::forward(nextCallback)); - }, - hint_of(util::identity_of())); -} - -/// - ? -> nextCallback(?) -template -auto invoker_of(util::identity) { - return make_invoker( - [](auto&& callback, auto&& nextCallback, auto&&... args) { - auto result = std::forward(callback)( - std::forward(args)...); - - std::forward(nextCallback)(std::move(result)); - }, - util::identity_of()); -} - -/// - void -> nextCallback() -inline auto invoker_of(util::identity) { - return make_invoker( - [](auto&& callback, auto&& nextCallback, auto&&... args) { - std::forward(callback)( - std::forward(args)...); - - std::forward(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(callback)( - std::forward(args)...); - - util::unpack(std::move(result), [&](auto&&... types) { - /// TODO Add inplace resolution here - - std::forward(nextCallback)( - std::forward(types)...); - }); - }; -} - -// - std::pair -> nextCallback(?, ?) -template -constexpr auto invoker_of(util::identity>) { - return make_invoker(sequenced_unpack_invoker(), - util::identity{}); -} - -// - std::tuple -> nextCallback(?...) -template -constexpr auto invoker_of(util::identity>) { - return make_invoker(sequenced_unpack_invoker(), util::identity{}); -} -} // end namespace decoration - -/// Invoke the callback immediately -template -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)(std::forward(callback), - std::forward(nextCallback), - std::forward(args)...); -} - -/// Invoke the callback through the given executor -template -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), - callback = std::forward(callback), - nextCallback = std::forward(nextCallback), - args = std::make_tuple(std::forward(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(captured_args)...); - }); - }; - - // Pass the work functional object to the executor - std::forward(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 -void invoke_proxy(signature_hint_tag, 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), [ - callback = std::forward(callback), - executor = std::forward(executor), - nextCallback = std::forward(nextCallback) - ](Args... args) mutable { - - // In order to retrieve the correct decorator we must know what the - // result type is. - auto result = - util::identity_of(); - - // 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 -constexpr auto next_hint_of(util::identity /*callback*/, - signature_hint_tag /*current*/) { - return decoration::invoker_of(util::identity_of()( - std::declval()...))>()) - .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 -auto chain_continuation(Continuation&& continuation, Callback&& callback, - Executor&& executor) { - static_assert(is_continuation>{}, - "Expected a continuation!"); - - // Wrap the callback into a partial callable callback - auto partial_callable = [callback = std::forward(callback)]( - auto&&... args) mutable { - return util::partial_invoke(std::move(callback), - std::forward(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), - partial_callable = std::move(partial_callable), - executor = std::forward(executor) - ](auto&& nextCallback) mutable { - invoke_proxy(hint_of(util::identity_of(continuation)), - std::move(continuation), std::move(partial_callable), - std::move(executor), - std::forward(nextCallback)); - }, - next_hint, ownership_); -} - -/// Workaround for GCC bug: -/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 -struct empty_callback { - template - void operator()(Args...) const { - } -}; - -/// Final invokes the given continuation chain: -/// -/// For example given: -/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }> -template -void finalize_continuation(Continuation&& continuation) { - attorney::invoke_continuation(std::forward(continuation), - empty_callback{}); -} - -/// Workaround for GCC bug: -/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 -template -class supplier_callback { - T data_; - -public: - explicit supplier_callback(T data) : data_(std::move(data)) { - } - - template - auto operator()(Args...) { - return std::move(data_); - } -}; - -/// Returns a continuable into a functional object returning the continuable -template -auto wrap_continuation(Continuation&& continuation) { - continuation.freeze(); - return supplier_callback>( - std::forward(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 -struct is_strategy : std::false_type {}; -template <> -struct is_strategy : std::true_type {}; -template <> -struct is_strategy : 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) noexcept { - return signature_hint_tag<>{}; -} -/// All other hints are the obvious hints... -template -constexpr auto make_hint_of(util::identity 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 -constexpr auto extract(util::identity /*type*/, - util::identity 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 -constexpr void assign(util::size_constant /*pos*/, T& /*storage*/) { - // ... -} -template -void assign(util::size_constant pos, T& storage, Current&& current, - Args&&... args) { - std::get(storage) = std::forward(current); - assign(pos + util::size_constant_of<1>(), storage, - std::forward(args)...); -} - -/// Caches the partial results and invokes the callback when all results -/// are arrived. This class is thread safe. -template -class all_result_submitter : public std::enable_shared_from_this< - all_result_submitter>, - public util::non_movable { - - T callback_; - std::atomic left_; - std::tuple 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 - auto create_callback(util::size_constant from, - util::size_constant 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(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(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 -class any_result_submitter - : public std::enable_shared_from_this>, - 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(args)...); - }; - } - -private: - // Invokes the callback with the given arguments - template - void invoke(Args&&... args) { - std::call_once(flag_, std::move(callback_), std::forward(args)...); - } -}; -} // end namespace detail - -/// Adds the given continuation tuple to the left composition -template -auto chain_composition(std::tuple leftPack, - std::tuple 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 ::value>* = nullptr> -auto normalize(Strategy /*strategy*/, - continuable_base&& 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 ::value>* = nullptr> -auto normalize(Strategy /*strategy*/, - continuable_base&& 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 -auto normalize(Strategy /*strategy*/, - continuable_base&& 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 -auto connect(Strategy strategy, continuable_base&& left, - continuable_base&& 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 -auto make_all_result_submitter(Callback&& callback, - util::size_constant, - util::identity) { - return std::make_shared, Submissions, Args...>>( - std::forward(callback)); -} - -/// Finalizes the all logic of a given composition -template -auto finalize_composition( - continuable_base&& 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> - // ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ - // 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(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(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 -auto make_any_result_submitter(Callback&& callback) { - return std::make_shared< - detail::any_result_submitter>>( - std::forward(callback)); -} - -template -constexpr T first_of(util::identity) noexcept; - -template -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{}, [&](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 -constexpr auto common_result_of(Signature signature, First first, - Args... args) { - using Common = - util::identity>; - - 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 -auto finalize_composition( - continuable_base&& 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(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(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 -auto sequential_connect(Left&& left, Right&& right) { - left.freeze(right.is_frozen()); - right.freeze(); - - return std::forward(left).then([right = std::forward(right)]( - auto&&... args) mutable { - return std::move(right).then([previous = std::make_tuple( - std::forward(args)...)]( - auto&&... args) mutable { - return util::merge( - std::move(previous), - std::make_tuple(std::forward(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 -struct future_trait { - /// The promise type used to create the future - using promise_t = std::promise>; - /// 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; - /// Boxes the argument pack into void - static void resolve(promise_t& promise) { - promise.set_value(); - } -}; -template -struct future_trait { - /// The promise type used to create the future - using promise_t = std::promise; - /// Boxes the argument pack into nothing - static void resolve(promise_t& promise, First first) { - promise.set_value(std::move(first)); - } -}; - -template -class promise_callback; - -template -class promise_callback> - : public future_trait { - - typename future_trait::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 -auto as_future(continuable_base&& continuable) { - // Create the promise which is able to supply the current arguments - auto hint = base::hint_of(util::identity_of(continuable)); - - promise_callback> 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 class continuable_base { /// \cond false @@ -1024,9 +192,9 @@ public: /// ``` /// /// \since version 1.0.0 - template + template 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(callback), std::forward(executor)); @@ -1093,8 +261,8 @@ public: /// \since version 1.0.0 template auto operator&&(continuable_base&& 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 auto operator||(continuable_base&& 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 auto operator>>(continuable_base&& 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 ::value>* = nullptr> + template < + typename OData, typename OAnnotation, + std::enable_if_t::value>* = + nullptr> static auto materializeImpl(continuable_base&& continuable) { return std::move(continuable); } - template ::value>* = nullptr> + template < + typename OData, typename OAnnotation, + std::enable_if_t::value>* = + nullptr> static auto materializeImpl(continuable_base&& 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 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{}); diff --git a/include/continuable/detail/base.hpp b/include/continuable/detail/base.hpp index 9faef4d..632cfe4 100644 --- a/include/continuable/detail/base.hpp +++ b/include/continuable/detail/base.hpp @@ -31,11 +31,377 @@ #ifndef CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__ #define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__ +#include +#include +#include + #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 +/// - Chaining a continuation together with a callback +/// base::chain_continuation(base::continuation continuation, +/// auto&& callback) +/// -> base::continuation +/// - Finally invoking the continuation chain +/// base::finalize_continuation(base::continuation continuation) +/// -> void +namespace base { +struct this_thread_executor_tag {}; + +/// Returns the signature hint of the given continuable +template +constexpr auto hint_of(util::identity) { + static_assert(util::fail::value, + "Expected a continuation with an existing signature hint!"); + return util::identity_of(); +} +/// Returns the signature hint of the given continuable +template +constexpr auto +hint_of(util::identity< + continuable_base>>) { + return hints::signature_hint_tag{}; +} + +template +struct is_continuation : std::false_type {}; +template +struct is_continuation> : 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 + static auto create(T&& continuation, A /*hint*/, util::ownership ownership_) { + return continuable_base, std::decay_t>( + std::forward(continuation), ownership_); + } + + /// Invokes a continuation object in a reference correct way + template + static auto + invoke_continuation(continuable_base&& continuation, + Callback&& callback) { + auto materialized = std::move(continuation).materialize(); + materialized.release(); + return materialized.data_(std::forward(callback)); + } + + template + static auto materialize(continuable_base&& continuation) { + return std::move(continuation).materialize(); + } + + template + static Data&& + consume_data(continuable_base&& continuation) { + return std::move(continuation).consume_data(); + } + + template + 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 +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 +constexpr auto make_invoker(T&& invoke, hints::signature_hint_tag) { + return invoker, hints::signature_hint_tag>( + std::forward(invoke)); +} + +/// - continuable -> result(nextCallback); +template +constexpr auto invoker_of(util::identity>) { + /// Get the hint of the unwrapped returned continuable + using Type = decltype(attorney::materialize( + std::declval>())); + + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + auto continuation_ = std::forward(callback)( + std::forward(args)...); + + attorney::invoke_continuation( + std::move(continuation_), + std::forward(nextCallback)); + }, + hint_of(util::identity_of())); +} + +/// - ? -> nextCallback(?) +template +auto invoker_of(util::identity) { + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + auto result = std::forward(callback)( + std::forward(args)...); + + std::forward(nextCallback)(std::move(result)); + }, + util::identity_of()); +} + +/// - void -> nextCallback() +inline auto invoker_of(util::identity) { + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + std::forward(callback)( + std::forward(args)...); + + std::forward(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(callback)( + std::forward(args)...); + + util::unpack(std::move(result), [&](auto&&... types) { + /// TODO Add inplace resolution here + + std::forward(nextCallback)( + std::forward(types)...); + }); + }; +} + +// - std::pair -> nextCallback(?, ?) +template +constexpr auto invoker_of(util::identity>) { + return make_invoker(sequenced_unpack_invoker(), + util::identity{}); +} + +// - std::tuple -> nextCallback(?...) +template +constexpr auto invoker_of(util::identity>) { + return make_invoker(sequenced_unpack_invoker(), util::identity{}); +} +} // end namespace decoration + +/// Invoke the callback immediately +template +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)(std::forward(callback), + std::forward(nextCallback), + std::forward(args)...); +} + +/// Invoke the callback through the given executor +template +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), + callback = std::forward(callback), + nextCallback = std::forward(nextCallback), + args = std::make_tuple(std::forward(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(captured_args)...); + }); + }; + + // Pass the work functional object to the executor + std::forward(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 +void invoke_proxy(hints::signature_hint_tag, + 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), [ + callback = std::forward(callback), + executor = std::forward(executor), + nextCallback = std::forward(nextCallback) + ](Args... args) mutable { + + // In order to retrieve the correct decorator we must know what the + // result type is. + auto result = + util::identity_of(); + + // 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 +constexpr auto next_hint_of(util::identity /*callback*/, + hints::signature_hint_tag /*current*/) { + return decoration::invoker_of(util::identity_of()( + std::declval()...))>()) + .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 +auto chain_continuation(Continuation&& continuation, Callback&& callback, + Executor&& executor) { + static_assert(is_continuation>{}, + "Expected a continuation!"); + + // Wrap the callback into a partial callable callback + auto partial_callable = [callback = std::forward(callback)]( + auto&&... args) mutable { + return util::partial_invoke(std::move(callback), + std::forward(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), + partial_callable = std::move(partial_callable), + executor = std::forward(executor) + ](auto&& nextCallback) mutable { + invoke_proxy(hint_of(util::identity_of(continuation)), + std::move(continuation), std::move(partial_callable), + std::move(executor), + std::forward(nextCallback)); + }, + next_hint, ownership_); +} + +/// Workaround for GCC bug: +/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 +struct empty_callback { + template + void operator()(Args...) const { + } +}; + +/// Final invokes the given continuation chain: +/// +/// For example given: +/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }> +template +void finalize_continuation(Continuation&& continuation) { + attorney::invoke_continuation(std::forward(continuation), + empty_callback{}); +} + +/// Workaround for GCC bug: +/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 +template +class supplier_callback { + T data_; + +public: + explicit supplier_callback(T data) : data_(std::move(data)) { + } + + template + auto operator()(Args...) { + return std::move(data_); + } +}; + +/// Returns a continuable into a functional object returning the continuable +template +auto wrap_continuation(Continuation&& continuation) { + continuation.freeze(); + return supplier_callback>( + std::forward(continuation)); +} +} // end namespace base + } // namespace detail } // namespace cti diff --git a/include/continuable/detail/composition.hpp b/include/continuable/detail/composition.hpp index 7e32f7e..182f5a8 100644 --- a/include/continuable/detail/composition.hpp +++ b/include/continuable/detail/composition.hpp @@ -31,11 +31,400 @@ #ifndef CONTINUABLE_DETAIL_COMPOSITION_HPP_INCLUDED__ #define CONTINUABLE_DETAIL_COMPOSITION_HPP_INCLUDED__ +#include +#include +#include +#include +#include + #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 +struct is_strategy : std::false_type {}; +template <> +struct is_strategy : std::true_type {}; +template <> +struct is_strategy : 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) noexcept { + return hints::signature_hint_tag<>{}; +} +/// All other hints are the obvious hints... +template +constexpr auto make_hint_of(util::identity 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 +constexpr auto extract(util::identity /*type*/, + util::identity 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 +constexpr void assign(util::size_constant /*pos*/, T& /*storage*/) { + // ... +} +template +void assign(util::size_constant pos, T& storage, Current&& current, + Args&&... args) { + std::get(storage) = std::forward(current); + assign(pos + util::size_constant_of<1>(), storage, + std::forward(args)...); +} + +/// Caches the partial results and invokes the callback when all results +/// are arrived. This class is thread safe. +template +class all_result_submitter : public std::enable_shared_from_this< + all_result_submitter>, + public util::non_movable { + + T callback_; + std::atomic left_; + std::tuple 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 + auto create_callback(util::size_constant from, + util::size_constant 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(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(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 +class any_result_submitter + : public std::enable_shared_from_this>, + 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(args)...); + }; + } + +private: + // Invokes the callback with the given arguments + template + void invoke(Args&&... args) { + std::call_once(flag_, std::move(callback_), std::forward(args)...); + } +}; +} // end namespace detail + +/// Adds the given continuation tuple to the left composition +template +auto chain_composition(std::tuple leftPack, + std::tuple 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 ::value>* = nullptr> +auto normalize(Strategy /*strategy*/, + continuable_base&& 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 ::value>* = nullptr> +auto normalize(Strategy /*strategy*/, + continuable_base&& 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 +auto normalize(Strategy /*strategy*/, + continuable_base&& 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 +auto connect(Strategy strategy, continuable_base&& left, + continuable_base&& 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 +auto make_all_result_submitter(Callback&& callback, + util::size_constant, + util::identity) { + return std::make_shared, Submissions, Args...>>( + std::forward(callback)); +} + +/// Finalizes the all logic of a given composition +template +auto finalize_composition( + continuable_base&& 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> + // ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ + // 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(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(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 +auto make_any_result_submitter(Callback&& callback) { + return std::make_shared< + detail::any_result_submitter>>( + std::forward(callback)); +} + +template +constexpr T first_of(util::identity) noexcept; + +template +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{}, [&](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 +constexpr auto common_result_of(Signature signature, First first, + Args... args) { + using Common = + util::identity>; + + 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 +auto finalize_composition( + continuable_base&& 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(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(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 +auto sequential_connect(Left&& left, Right&& right) { + left.freeze(right.is_frozen()); + right.freeze(); + + return std::forward(left).then([right = std::forward(right)]( + auto&&... args) mutable { + return std::move(right).then([previous = std::make_tuple( + std::forward(args)...)]( + auto&&... args) mutable { + return util::merge( + std::move(previous), + std::make_tuple(std::forward(args)...)); + }); + }); +} +} // namespace composition } // namespace detail } // namespace cti diff --git a/include/continuable/detail/hints.hpp b/include/continuable/detail/hints.hpp index 1fbf513..af368d0 100644 --- a/include/continuable/detail/hints.hpp +++ b/include/continuable/detail/hints.hpp @@ -31,11 +31,25 @@ #ifndef CONTINUABLE_DETAIL_HINTS_HPP_INCLUDED__ #define CONTINUABLE_DETAIL_HINTS_HPP_INCLUDED__ +#include + #include "continuable/detail/api.hpp" +#include "continuable/detail/util.hpp" namespace cti { namespace detail { -// +namespace hints { +/// Represents a present signature hint +template +using signature_hint_tag = util::identity; +/// Represents an absent signature hint +struct absent_signature_hint_tag {}; + +template +struct is_absent_hint : std::false_type {}; +template <> +struct is_absent_hint : std::true_type {}; +} // namespace hints } // namespace detail } // namespace cti diff --git a/include/continuable/detail/transforms.hpp b/include/continuable/detail/transforms.hpp new file mode 100644 index 0000000..72b5547 --- /dev/null +++ b/include/continuable/detail/transforms.hpp @@ -0,0 +1,120 @@ + +/** + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2017 Denis Blank + + 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 + +#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 +struct future_trait { + /// The promise type used to create the future + using promise_t = std::promise>; + /// 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; + /// Boxes the argument pack into void + static void resolve(promise_t& promise) { + promise.set_value(); + } +}; +template +struct future_trait { + /// The promise type used to create the future + using promise_t = std::promise; + /// Boxes the argument pack into nothing + static void resolve(promise_t& promise, First first) { + promise.set_value(std::move(first)); + } +}; + +template +class promise_callback; + +template +class promise_callback> + : public future_trait { + + typename future_trait::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 +auto as_future(continuable_base&& continuable) { + // Create the promise which is able to supply the current arguments + auto hint = base::hint_of(util::identity_of(continuable)); + + promise_callback> 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__ diff --git a/include/continuable/detail/util.hpp b/include/continuable/detail/util.hpp index dc9e79d..9bfe147 100644 --- a/include/continuable/detail/util.hpp +++ b/include/continuable/detail/util.hpp @@ -236,7 +236,7 @@ constexpr auto pack_size_of(identity) noexcept { /// Returns an index sequence of the given type template -constexpr auto sequenceOf(T&& /*sequenceable*/) noexcept { +constexpr auto sequence_of(T&& /*sequenceable*/) noexcept { return std::make_index_sequence()))::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 -constexpr auto isLessThen(size_constant end) noexcept { +constexpr auto is_less_than(size_constant 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 -constexpr auto validatorOf(Check&& check) noexcept( +constexpr auto validator_of(Check&& check) noexcept( std::is_nothrow_move_constructible>::value) { return [check = std::forward(check)](auto&& matchable) { return is_valid(std::forward(matchable), check); @@ -340,7 +340,7 @@ constexpr auto unpack(F&& firstSequenceable, S&& secondSequenceable, template auto unpack(F&& firstSequenceable, U&& unpacker) { return unpack(std::forward(firstSequenceable), std::forward(unpacker), - sequenceOf(identity_of(firstSequenceable))); + sequence_of(identity_of(firstSequenceable))); } /// Calls the given unpacker with the content of the given sequenceables template @@ -348,8 +348,8 @@ constexpr auto unpack(F&& firstSequenceable, S&& secondSequenceable, U&& unpacker) { return unpack(std::forward(firstSequenceable), std::forward(secondSequenceable), std::forward(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 diff --git a/test/playground/CMakeLists.txt b/test/playground/CMakeLists.txt index 0eeb826..d6eb3e6 100644 --- a/test/playground/CMakeLists.txt +++ b/test/playground/CMakeLists.txt @@ -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)