/** /~` _ _ _|_. _ _ |_ | _ \_,(_)| | | || ||_|(_||_)|(/_ 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_BASE_HPP_INCLUDED__ #define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__ #include #include #include #ifndef CONTINUABLE_WITH_NO_EXCEPTIONS #include #else // CONTINUABLE_WITH_NO_EXCEPTIONS #include #endif // CONTINUABLE_WITH_NO_EXCEPTIONS #include #include #include #include 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 { /// A tag which is used to execute the continuation inside the current thread struct this_thread_executor_tag {}; /// A tag which is used to continue with an error struct dispatch_error_tag {}; #ifndef CONTINUABLE_WITH_NO_EXCEPTIONS /// Represents the error type when exceptions are enabled using error_type = std::exception_ptr; #else // CONTINUABLE_WITH_NO_EXCEPTIONS /// Represents the error type when exceptions are disabled using error_type = std::error_category; #endif // CONTINUABLE_WITH_NO_EXCEPTIONS /// Returns the signature hint of the given continuable template constexpr auto hint_of(traits::identity) { static_assert(traits::fail::value, "Expected a continuation with an existing signature hint!"); return traits::identity_of(); } /// Returns the signature hint of the given continuable template constexpr auto hint_of(traits::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&& next_callback, auto&&... args) // // The invoker decorates the result type in the following way // - void -> next_callback() // - ? -> next_callback(?) // - std::pair -> next_callback(?, ?) // - std::tuple -> next_callback(?...) // // When the result is a continuation itself pass the callback to it // - continuation -> result(next_callback); namespace decoration { /// Helper class wrapping the underlaying unwrapping lambda /// in order to extend it with a hint method. template class invoker : public T { public: constexpr 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(next_callback); template constexpr auto invoker_of(traits::identity>) { /// Get the hint of the unwrapped returned continuable using Type = decltype(attorney::materialize( std::declval>())); return make_invoker( [](auto&& callback, auto&& next_callback, auto&&... args) { auto continuation_ = std::forward(callback)( std::forward(args)...); attorney::invoke_continuation( std::move(continuation_), std::forward(next_callback)); }, hint_of(traits::identity_of())); } /// - ? -> next_callback(?) template constexpr auto invoker_of(traits::identity) { return make_invoker( [](auto&& callback, auto&& next_callback, auto&&... args) { auto result = std::forward(callback)( std::forward(args)...); std::forward(next_callback)(std::move(result)); }, traits::identity_of()); } /// - void -> next_callback() constexpr auto invoker_of(traits::identity) { return make_invoker( [](auto&& callback, auto&& next_callback, auto&&... args) { std::forward(callback)( std::forward(args)...); std::forward(next_callback)(); }, traits::identity<>{}); } /// Returns a sequenced invoker which is able to invoke /// objects where std::get is applicable. constexpr auto sequenced_unpack_invoker() { return [](auto&& callback, auto&& next_callback, auto&&... args) { auto result = std::forward(callback)( std::forward(args)...); traits::unpack(std::move(result), [&](auto&&... types) { /// TODO Add inplace resolution here std::forward(next_callback)( std::forward(types)...); }); }; } // - std::pair -> next_callback(?, ?) template constexpr auto invoker_of(traits::identity>) { return make_invoker(sequenced_unpack_invoker(), traits::identity{}); } // - std::tuple -> next_callback(?...) template constexpr auto invoker_of(traits::identity>) { return make_invoker(sequenced_unpack_invoker(), traits::identity{}); } } // end namespace decoration /// Invoke the callback immediately template void packed_dispatch(this_thread_executor_tag, Invoker&& invoker, Callback&& callback, NextCallback&& next_callback, Args&&... args) { // Invoke the callback with the decorated invoker immediately std::forward(invoker)(std::forward(callback), std::forward(next_callback), std::forward(args)...); } /// Invoke the callback through the given executor template void packed_dispatch(Executor&& executor, Invoker&& invoker, Callback&& callback, NextCallback&& next_callback, 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), next_callback = std::forward(next_callback), args = std::make_tuple(std::forward(args)...) ]() mutable { traits::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(next_callback), std::forward(captured_args)...); }); }; // Pass the work functional object to the executor std::forward(executor)(std::move(work)); } template struct result_proxy { Callback callback_; Executor executor_; NextCallback next_callback_; /// The operator which is called when the result was provided void operator()(Args... args) { // In order to retrieve the correct decorator we must know what the // result type is. auto result = traits::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(next_callback_), std::move(args)...); } /// The operator which is called when an error occurred void operator()(dispatch_error_tag tag, error_type error) { // Forward the error to the next callback std::move(next_callback_)(tag, std::move(error)); } }; /// 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&& next_callback) { result_proxy, std::decay_t, std::decay_t, Args...> proxy{std::forward(callback), std::forward(executor), std::forward(next_callback)}; // Invoke the continuation with a proxy callback. // The proxy callback is responsible for passing // the result to the callback as well as decorating it. attorney::invoke_continuation(std::forward(continuation), std::move(proxy)); } /// Returns the next hint when the callback is invoked with the given hint template constexpr auto next_hint_of(traits::identity /*callback*/, hints::signature_hint_tag /*current*/) { return decoration::invoker_of(traits::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(traits::identity_of(continuation)); auto next_hint = next_hint_of(traits::identity_of(partial_callable), hint); // TODO consume only the data here so the freeze isn't needed auto ownership_ = attorney::ownership_of(continuation); continuation.freeze(); return attorney::create( [ continuation = std::forward(continuation), partial_callable = std::move(partial_callable), executor = std::forward(executor) ](auto&& next_callback) mutable { invoke_proxy(hint_of(traits::identity_of(continuation)), std::move(continuation), std::move(partial_callable), std::move(executor), std::forward(next_callback)); }, 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 #endif // CONTINUABLE_DETAIL_BASE_HPP_INCLUDED__