mirror of
https://github.com/Naios/continuable.git
synced 2025-12-06 08:46:44 +08:00
936 lines
34 KiB
C++
936 lines
34 KiB
C++
|
|
/*
|
|
|
|
/~` _ _ _|_. _ _ |_ | _
|
|
\_,(_)| | | || ||_|(_||_)|(/_
|
|
|
|
https://github.com/Naios/continuable
|
|
v4.0.0
|
|
|
|
Copyright(c) 2015 - 2019 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_BASE_HPP_INCLUDED
|
|
#define CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
|
|
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <continuable/continuable-primitives.hpp>
|
|
#include <continuable/continuable-result.hpp>
|
|
#include <continuable/detail/core/annotation.hpp>
|
|
#include <continuable/detail/core/types.hpp>
|
|
#include <continuable/detail/features.hpp>
|
|
#include <continuable/detail/utility/result-trait.hpp>
|
|
#include <continuable/detail/utility/traits.hpp>
|
|
#include <continuable/detail/utility/util.hpp>
|
|
|
|
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
|
#include <exception>
|
|
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
|
|
|
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_from(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 {
|
|
template <typename T>
|
|
struct is_continuable : std::false_type {};
|
|
template <typename Data, typename Annotation>
|
|
struct is_continuable<continuable_base<Data, Annotation>> : std::true_type {};
|
|
|
|
template <typename... Args>
|
|
struct ready_continuation {
|
|
std::tuple<Args...> values_;
|
|
|
|
ready_continuation() = delete;
|
|
~ready_continuation() = default;
|
|
ready_continuation(ready_continuation&&) = default;
|
|
ready_continuation(ready_continuation const&) = delete;
|
|
ready_continuation& operator=(ready_continuation&&) = default;
|
|
ready_continuation& operator=(ready_continuation const&) = delete;
|
|
|
|
explicit ready_continuation(Args... values) : values_(std::move(values)...) {
|
|
}
|
|
|
|
template <typename Callback>
|
|
void operator()(Callback&& callback) {
|
|
traits::unpack(std::forward<Callback>(callback), std::move(values_));
|
|
}
|
|
|
|
bool operator()(is_ready_arg_t) const noexcept {
|
|
return true;
|
|
}
|
|
|
|
std::tuple<Args...> operator()(query_arg_t) {
|
|
return std::move(values_);
|
|
}
|
|
};
|
|
template <>
|
|
struct ready_continuation<> {
|
|
ready_continuation() = default;
|
|
~ready_continuation() = default;
|
|
ready_continuation(ready_continuation&&) = default;
|
|
ready_continuation(ready_continuation const&) = delete;
|
|
ready_continuation& operator=(ready_continuation&&) = default;
|
|
ready_continuation& operator=(ready_continuation const&) = delete;
|
|
|
|
template <typename Callback>
|
|
void operator()(Callback&& callback) {
|
|
util::invoke(std::forward<Callback>(callback));
|
|
}
|
|
|
|
bool operator()(is_ready_arg_t) const noexcept {
|
|
return true;
|
|
}
|
|
|
|
std::tuple<> operator()(query_arg_t) {
|
|
return std::make_tuple();
|
|
}
|
|
};
|
|
|
|
template <typename Hint, typename Continuation>
|
|
struct proxy_continuable;
|
|
template <typename... Args, typename Continuation>
|
|
struct proxy_continuable<identity<Args...>, Continuation> : Continuation {
|
|
|
|
explicit proxy_continuable(Continuation continuation)
|
|
: Continuation(std::move(continuation)) {
|
|
}
|
|
~proxy_continuable() = default;
|
|
proxy_continuable(proxy_continuable&&) = default;
|
|
proxy_continuable(proxy_continuable const&) = delete;
|
|
proxy_continuable& operator=(proxy_continuable&&) = default;
|
|
proxy_continuable& operator=(proxy_continuable const&) = delete;
|
|
|
|
using Continuation::operator();
|
|
|
|
bool operator()(is_ready_arg_t) const noexcept {
|
|
return false;
|
|
}
|
|
|
|
std::tuple<Args...> operator()(query_arg_t) {
|
|
util::unreachable();
|
|
}
|
|
};
|
|
|
|
struct attorney {
|
|
/// Creates a continuable_base from the given continuation,
|
|
/// annotation and ownership.
|
|
template <typename T, typename Annotation>
|
|
static auto create_from_raw(T&& continuation, Annotation,
|
|
util::ownership ownership) {
|
|
using continuation_t = continuable_base<traits::unrefcv_t<T>, //
|
|
traits::unrefcv_t<Annotation>>;
|
|
return continuation_t{std::forward<T>(continuation), ownership};
|
|
}
|
|
|
|
/// Creates a continuable_base from the given continuation,
|
|
/// annotation and ownership.
|
|
/// This wraps the continuable to contain the is_ready and query method
|
|
/// implemented empty.
|
|
template <typename T, typename Hint>
|
|
static auto create_from(T&& continuation, Hint, util::ownership ownership) {
|
|
using hint_t = traits::unrefcv_t<Hint>;
|
|
using proxy_t = proxy_continuable<hint_t, traits::unrefcv_t<T>>;
|
|
return continuable_base<proxy_t, hint_t>{
|
|
proxy_t{std::forward<T>(continuation)}, ownership};
|
|
}
|
|
|
|
/// Returns the ownership of the given continuable_base
|
|
template <typename Continuable>
|
|
static util::ownership ownership_of(Continuable&& continuation) noexcept {
|
|
return continuation.ownership_;
|
|
}
|
|
|
|
template <typename Data, typename Annotation>
|
|
static Data&& consume(continuable_base<Data, Annotation>&& continuation) {
|
|
return std::move(continuation).consume();
|
|
}
|
|
|
|
template <typename Continuable>
|
|
static bool is_ready(Continuable&& continuation) noexcept {
|
|
return util::as_const(continuation.data_)(is_ready_arg_t{});
|
|
}
|
|
|
|
template <typename Data, typename Annotation>
|
|
static auto query(continuable_base<Data, Annotation>&& continuation) {
|
|
return std::move(continuation).consume()(query_arg_t{});
|
|
}
|
|
};
|
|
|
|
/// Returns the signature hint of the given continuable
|
|
template <typename Data, typename... Args>
|
|
constexpr identity<Args...>
|
|
annotation_of(identity<continuable_base<Data, //
|
|
identity<Args...>>>) {
|
|
return {};
|
|
}
|
|
|
|
/// Invokes a continuation object in a reference correct way
|
|
template <typename Data, typename Annotation, typename Callback>
|
|
void invoke_continuation(continuable_base<Data, Annotation>&& continuation,
|
|
Callback&& callback) noexcept {
|
|
util::invoke(attorney::consume(std::move(continuation).finish()),
|
|
std::forward<Callback>(callback));
|
|
}
|
|
|
|
// 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 callable 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 <typename T, typename Hint>
|
|
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 {};
|
|
}
|
|
};
|
|
|
|
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
|
#define CONTINUABLE_BLOCK_TRY_BEGIN try {
|
|
#define CONTINUABLE_BLOCK_TRY_END \
|
|
} \
|
|
catch (...) { \
|
|
std::forward<decltype(next_callback)>(next_callback)( \
|
|
exception_arg_t{}, std::current_exception()); \
|
|
}
|
|
|
|
#else // CONTINUABLE_HAS_EXCEPTIONS
|
|
#define CONTINUABLE_BLOCK_TRY_BEGIN {
|
|
#define CONTINUABLE_BLOCK_TRY_END }
|
|
#endif // CONTINUABLE_HAS_EXCEPTIONS
|
|
|
|
/// Invokes the callback partially, keeps the exception_arg_t such that
|
|
/// we don't jump accidentally from the exception path to the result path.
|
|
template <typename T, typename... Args>
|
|
constexpr auto invoke_callback(T&& callable, exception_arg_t exception_arg,
|
|
Args&&... args) {
|
|
return util::partial_invoke(std::integral_constant<std::size_t, 01>{},
|
|
std::forward<T>(callable), exception_arg,
|
|
std::forward<Args>(args)...);
|
|
}
|
|
template <typename T, typename... Args>
|
|
constexpr auto invoke_callback(T&& callable, Args&&... args) {
|
|
return util::partial_invoke(std::integral_constant<std::size_t, 0U>{},
|
|
std::forward<T>(callable),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
|
|
/// Invokes the given callable object with the given arguments while
|
|
/// marking the operation as non exceptional.
|
|
template <typename T, typename... Args>
|
|
constexpr void invoke_no_except(T&& callable, Args&&... args) noexcept {
|
|
std::forward<T>(callable)(std::forward<Args>(args)...);
|
|
}
|
|
|
|
template <typename... Args, typename T>
|
|
void invoke_void_no_except(identity<exception_arg_t, Args...>,
|
|
T&& /*callable*/) noexcept {
|
|
// Don't invoke the next failure handler when being in an exception handler
|
|
}
|
|
template <typename... Args, typename T>
|
|
void invoke_void_no_except(identity<Args...>, T&& callable) noexcept {
|
|
std::forward<T>(callable)();
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
constexpr auto make_invoker(T&& invoke, identity<Args...>) {
|
|
return invoker<std::decay_t<T>, identity<Args...>>(std::forward<T>(invoke));
|
|
}
|
|
|
|
/// - continuable<?...> -> result(next_callback);
|
|
template <typename Data, typename Annotation>
|
|
constexpr auto invoker_of(identity<continuable_base<Data, Annotation>>) {
|
|
/// Get the hint of the unwrapped returned continuable
|
|
using Type =
|
|
decltype(std::declval<continuable_base<Data, Annotation>>().finish());
|
|
|
|
auto constexpr const hint = base::annotation_of(identify<Type>{});
|
|
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
auto continuation_ =
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
invoke_continuation(
|
|
std::move(continuation_),
|
|
std::forward<decltype(next_callback)>(next_callback));
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
hint);
|
|
}
|
|
|
|
/// - ? -> next_callback(?)
|
|
template <typename T>
|
|
constexpr auto invoker_of(identity<T>) {
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
auto result =
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
|
|
std::move(result));
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
identify<T>{});
|
|
}
|
|
|
|
/// - void -> next_callback()
|
|
inline auto invoker_of(identity<void>) {
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
invoke_void_no_except(
|
|
identity<traits::unrefcv_t<decltype(args)>...>{},
|
|
std::forward<decltype(next_callback)>(next_callback));
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
identity<>{});
|
|
}
|
|
|
|
/// - empty_result -> <cancel>
|
|
inline auto invoker_of(identity<empty_result>) {
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
(void)next_callback;
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
empty_result result =
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
// Don't invoke anything here since returning an empty result
|
|
// cancels the asynchronous chain effectively.
|
|
(void)result;
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
identity<>{});
|
|
}
|
|
|
|
/// - exceptional_result -> <throw>
|
|
inline auto invoker_of(identity<exceptional_result>) {
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
util::unused(callback, next_callback, args...);
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
exceptional_result result =
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
// Forward the exception to the next available handler
|
|
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
|
|
exception_arg_t{},
|
|
std::move(result).get_exception());
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
identity<>{});
|
|
}
|
|
|
|
/// - result<?...> -> next_callback(?...)
|
|
template <typename... Args>
|
|
auto invoker_of(identity<result<Args...>>) {
|
|
return make_invoker(
|
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
result<Args...> result =
|
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
if (result.is_value()) {
|
|
// Workaround for MSVC not capturing the reference
|
|
// correctly inside the lambda.
|
|
using Next = decltype(next_callback);
|
|
|
|
traits::unpack(
|
|
[&](auto&&... values) {
|
|
invoke_no_except(std::forward<Next>(next_callback),
|
|
std::forward<decltype(values)>(values)...);
|
|
},
|
|
std::move(result));
|
|
|
|
} else if (result.is_exception()) {
|
|
// Forward the exception to the next available handler
|
|
invoke_no_except(
|
|
std::forward<decltype(next_callback)>(next_callback),
|
|
exception_arg_t{}, std::move(result).get_exception());
|
|
}
|
|
|
|
// Otherwise the result is empty and we are cancelling our
|
|
// asynchronous chain.
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
},
|
|
identity<Args...>{});
|
|
}
|
|
|
|
/// Returns a sequenced invoker which is able to invoke
|
|
/// objects where std::get is applicable.
|
|
inline auto sequenced_unpack_invoker() {
|
|
return [](auto&& callback, auto&& next_callback, auto&&... args) {
|
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
|
auto result = invoke_callback(std::forward<decltype(callback)>(callback),
|
|
std::forward<decltype(args)>(args)...);
|
|
|
|
// Workaround for MSVC not capturing the reference
|
|
// correctly inside the lambda.
|
|
using Next = decltype(next_callback);
|
|
|
|
traits::unpack(
|
|
[&](auto&&... values) {
|
|
invoke_no_except(std::forward<Next>(next_callback),
|
|
std::forward<decltype(values)>(values)...);
|
|
},
|
|
std::move(result));
|
|
CONTINUABLE_BLOCK_TRY_END
|
|
};
|
|
} // namespace decoration
|
|
|
|
// - std::pair<?, ?> -> next_callback(?, ?)
|
|
template <typename First, typename Second>
|
|
constexpr auto invoker_of(identity<std::pair<First, Second>>) {
|
|
return make_invoker(sequenced_unpack_invoker(), identity<First, Second>{});
|
|
}
|
|
|
|
// - std::tuple<?...> -> next_callback(?...)
|
|
template <typename... Args>
|
|
constexpr auto invoker_of(identity<std::tuple<Args...>>) {
|
|
return make_invoker(sequenced_unpack_invoker(), identity<Args...>{});
|
|
}
|
|
|
|
#undef CONTINUABLE_BLOCK_TRY_BEGIN
|
|
#undef CONTINUABLE_BLOCK_TRY_END
|
|
} // namespace decoration
|
|
|
|
/// Invoke the callback immediately
|
|
template <typename Invoker, typename... Args>
|
|
void on_executor(types::this_thread_executor_tag, Invoker&& invoker,
|
|
Args&&... args) {
|
|
|
|
// Invoke the callback with the decorated invoker immediately
|
|
std::forward<Invoker>(invoker)(std::forward<Args>(args)...);
|
|
}
|
|
|
|
/// Invoke the callback through the given executor
|
|
template <typename Executor, typename Invoker, typename... Args>
|
|
void on_executor(Executor&& executor, Invoker&& invoker, Args&&... args) {
|
|
|
|
// Create a worker object which when invoked calls the callback with the
|
|
// the returned arguments.
|
|
auto work = [
|
|
invoker = std::forward<Invoker>(invoker),
|
|
args = std::make_tuple(std::forward<Args>(args)...)
|
|
]() mutable {
|
|
traits::unpack(
|
|
[&](auto&&... captured_args) {
|
|
// Just use the packed dispatch method which dispatches the work on
|
|
// the current thread.
|
|
on_executor(types::this_thread_executor_tag{}, std::move(invoker),
|
|
std::forward<decltype(captured_args)>(captured_args)...);
|
|
},
|
|
std::move(args));
|
|
};
|
|
|
|
// Pass the work callable object to the executor
|
|
std::forward<Executor>(executor)(std::move(work));
|
|
}
|
|
|
|
/// Tells whether we potentially move the chain upwards and handle the result
|
|
enum class handle_results {
|
|
no, //< The result is forwarded to the next callable
|
|
yes //< The result is handled by the current callable
|
|
};
|
|
|
|
// Silences a doxygen bug, it tries to map forward to std::forward
|
|
/// \cond false
|
|
/// Tells whether we handle the error through the callback
|
|
enum class handle_errors {
|
|
no, //< The error is forwarded to the next callable
|
|
forward //< The error is forwarded to the callable while keeping its tag
|
|
};
|
|
/// \endcond
|
|
|
|
namespace callbacks {
|
|
namespace proto {
|
|
template <handle_results HandleResults, typename Base, typename Hint>
|
|
struct result_handler_base;
|
|
template <typename Base, typename... Args>
|
|
struct result_handler_base<handle_results::no, Base, identity<Args...>> {
|
|
void operator()(Args... args) && {
|
|
// Forward the arguments to the next callback
|
|
std::move(static_cast<Base*>(this)->next_callback_)(std::move(args)...);
|
|
}
|
|
};
|
|
template <typename Base, typename... Args>
|
|
struct result_handler_base<handle_results::yes, Base, identity<Args...>> {
|
|
/// 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.
|
|
constexpr auto result = identify<decltype(decoration::invoke_callback(
|
|
std::move(static_cast<Base*>(this)->callback_), std::move(args)...))>{};
|
|
|
|
// Pick the correct invoker that handles decorating of the result
|
|
auto invoker = decoration::invoker_of(result);
|
|
|
|
// Invoke the callback
|
|
on_executor(std::move(static_cast<Base*>(this)->executor_),
|
|
std::move(invoker),
|
|
std::move(static_cast<Base*>(this)->callback_),
|
|
std::move(static_cast<Base*>(this)->next_callback_),
|
|
std::move(args)...);
|
|
}
|
|
};
|
|
|
|
template <handle_errors HandleErrors, typename Base>
|
|
struct error_handler_base;
|
|
template <typename Base>
|
|
struct error_handler_base<handle_errors::no, Base> {
|
|
/// The operator which is called when an error occurred
|
|
void operator()(exception_arg_t tag, exception_t exception) && {
|
|
// Forward the error to the next callback
|
|
std::move(static_cast<Base*>(this)->next_callback_)(tag,
|
|
std::move(exception));
|
|
}
|
|
};
|
|
template <typename Base>
|
|
struct error_handler_base<handle_errors::forward, Base> {
|
|
/// The operator which is called when an error occurred
|
|
void operator()(exception_arg_t, exception_t exception) && {
|
|
constexpr auto result = identify<decltype(decoration::invoke_callback(
|
|
std::move(static_cast<Base*>(this)->callback_), exception_arg_t{},
|
|
std::move(exception)))>{};
|
|
|
|
auto invoker = decoration::invoker_of(result);
|
|
|
|
// Invoke the error handler
|
|
on_executor(std::move(static_cast<Base*>(this)->executor_),
|
|
std::move(invoker),
|
|
std::move(static_cast<Base*>(this)->callback_),
|
|
std::move(static_cast<Base*>(this)->next_callback_),
|
|
exception_arg_t{}, std::move(exception));
|
|
}
|
|
};
|
|
} // namespace proto
|
|
|
|
template <typename Hint, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Callback, typename Executor,
|
|
typename NextCallback>
|
|
struct callback_base;
|
|
|
|
template <typename... Args, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Callback, typename Executor,
|
|
typename NextCallback>
|
|
struct callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
|
|
Executor, NextCallback>
|
|
: proto::result_handler_base<
|
|
HandleResults,
|
|
callback_base<identity<Args...>, HandleResults, HandleErrors,
|
|
Callback, Executor, NextCallback>,
|
|
identity<Args...>>,
|
|
proto::error_handler_base<
|
|
HandleErrors,
|
|
callback_base<identity<Args...>, HandleResults, HandleErrors,
|
|
Callback, Executor, NextCallback>>,
|
|
util::non_copyable {
|
|
|
|
Callback callback_;
|
|
Executor executor_;
|
|
NextCallback next_callback_;
|
|
|
|
explicit callback_base(Callback callback, Executor executor,
|
|
NextCallback next_callback)
|
|
: callback_(std::move(callback)), executor_(std::move(executor)),
|
|
next_callback_(std::move(next_callback)) {
|
|
}
|
|
|
|
/// Pull the result handling operator() in
|
|
using proto::result_handler_base<
|
|
HandleResults,
|
|
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
|
|
Executor, NextCallback>,
|
|
identity<Args...>>::operator();
|
|
|
|
/// Pull the error handling operator() in
|
|
using proto::error_handler_base<
|
|
HandleErrors,
|
|
callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
|
|
Executor, NextCallback>>::operator();
|
|
|
|
/// Resolves the continuation with the given values
|
|
void set_value(Args... args) {
|
|
std::move (*this)(std::move(args)...);
|
|
}
|
|
|
|
/// Resolves the continuation with the given error variable.
|
|
void set_exception(exception_t error) {
|
|
std::move (*this)(exception_arg_t{}, std::move(error));
|
|
}
|
|
};
|
|
|
|
template <typename Hint, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Callback, typename Executor,
|
|
typename NextCallback>
|
|
auto make_callback(Callback&& callback, Executor&& executor,
|
|
NextCallback&& next_callback) {
|
|
return callback_base<Hint, HandleResults, HandleErrors,
|
|
std::decay_t<Callback>, std::decay_t<Executor>,
|
|
std::decay_t<NextCallback>>{
|
|
std::forward<Callback>(callback), std::forward<Executor>(executor),
|
|
std::forward<NextCallback>(next_callback)};
|
|
}
|
|
|
|
/// Represents the last callback in the asynchronous continuation chain
|
|
template <typename... Args>
|
|
struct final_callback : util::non_copyable {
|
|
void operator()(Args... /*args*/) && {
|
|
}
|
|
|
|
void operator()(exception_arg_t, exception_t error) && {
|
|
(void)error;
|
|
#ifndef CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
|
|
// There were unhandled errors inside the asynchronous call chain!
|
|
// Define `CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS` in order
|
|
// to ignore unhandled errors!"
|
|
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
|
|
try {
|
|
std::rethrow_exception(error);
|
|
} catch (std::exception const& exception) {
|
|
(void)exception;
|
|
util::trap();
|
|
} catch (...) {
|
|
util::trap();
|
|
}
|
|
#else
|
|
util::trap();
|
|
#endif
|
|
#endif // CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS
|
|
}
|
|
|
|
void set_value(Args... args) {
|
|
std::move (*this)(std::forward<Args>(args)...);
|
|
}
|
|
|
|
void set_exception(exception_t error) {
|
|
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
|
std::move (*this)(exception_arg_t{}, std::move(error));
|
|
}
|
|
};
|
|
} // namespace callbacks
|
|
|
|
/// Returns the next hint when the callback is invoked with the given hint
|
|
template <typename T, typename... Args>
|
|
constexpr auto
|
|
next_hint_of(std::integral_constant<handle_results, handle_results::yes>,
|
|
identity<T> /*callback*/, identity<Args...> /*current*/) {
|
|
// Partial Invoke the given callback
|
|
using Result = decltype(
|
|
decoration::invoke_callback(std::declval<T>(), std::declval<Args>()...));
|
|
|
|
// Return the hint of thr given invoker
|
|
return decltype(decoration::invoker_of(identify<Result>{}).hint()){};
|
|
}
|
|
/// Don't progress the hint when we don't continue
|
|
template <typename T, typename... Args>
|
|
constexpr auto
|
|
next_hint_of(std::integral_constant<handle_results, handle_results::no>,
|
|
identity<T> /*callback*/, identity<Args...> current) {
|
|
return current;
|
|
}
|
|
|
|
namespace detail {
|
|
template <typename Callable>
|
|
struct exception_stripper_proxy {
|
|
Callable callable_;
|
|
template <typename... Args>
|
|
auto operator()(exception_arg_t, Args&&... args)
|
|
-> decltype(util::invoke(std::declval<Callable>(), //
|
|
std::declval<Args>()...)) {
|
|
return util::invoke(std::move(callable_), //
|
|
std::forward<decltype(args)>(args)...);
|
|
}
|
|
};
|
|
} // namespace detail
|
|
|
|
/// Removes the exception_arg_t from the arguments passed to the given callable
|
|
template <typename Callable>
|
|
auto strip_exception_arg(Callable&& callable) {
|
|
using proxy = detail::exception_stripper_proxy<traits::unrefcv_t<Callable>>;
|
|
return proxy{std::forward<Callable>(callable)};
|
|
}
|
|
|
|
template <typename Hint, typename NextHint, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Continuation, typename Callback,
|
|
typename Executor>
|
|
struct chained_continuation;
|
|
template <typename... Args, typename... NextArgs, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Continuation, typename Callback,
|
|
typename Executor>
|
|
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
|
HandleResults, HandleErrors, Continuation, Callback,
|
|
Executor> {
|
|
|
|
Continuation continuation_;
|
|
Callback callback_;
|
|
Executor executor_;
|
|
|
|
explicit chained_continuation(Continuation continuation, Callback callback,
|
|
Executor executor)
|
|
: continuation_(std::move(continuation)), callback_(std::move(callback)),
|
|
executor_(std::move(executor)) {
|
|
}
|
|
|
|
chained_continuation() = delete;
|
|
~chained_continuation() = default;
|
|
chained_continuation(chained_continuation const&) = delete;
|
|
chained_continuation(chained_continuation&&) = default;
|
|
chained_continuation& operator=(chained_continuation const&) = delete;
|
|
chained_continuation& operator=(chained_continuation&&) = default;
|
|
|
|
template <typename NextCallback>
|
|
void operator()(NextCallback&& next_callback) {
|
|
// 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: []() { }
|
|
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
|
|
HandleErrors>(
|
|
std::move(callback_), std::move(executor_),
|
|
std::forward<decltype(next_callback)>(next_callback));
|
|
|
|
// Check whether the continuation is ready
|
|
bool const is_ready = util::as_const(continuation_)(is_ready_arg_t{});
|
|
|
|
if (is_ready) {
|
|
// Invoke the proxy callback directly with the result to
|
|
// avoid a potential type erasure.
|
|
traits::unpack(std::move(proxy), std::move(continuation_)(query_arg_t{}));
|
|
} else {
|
|
// Invoke the continuation with a proxy callback.
|
|
// The proxy callback is responsible for passing
|
|
// the result to the callback as well as decorating it.
|
|
util::invoke(std::move(continuation_), std::move(proxy));
|
|
}
|
|
}
|
|
|
|
bool operator()(is_ready_arg_t) const noexcept {
|
|
return false;
|
|
}
|
|
|
|
std::tuple<NextArgs...> operator()(query_arg_t) {
|
|
util::unreachable();
|
|
}
|
|
};
|
|
// Specialization to unpack ready continuables directly
|
|
template <typename... Args, typename... NextArgs, handle_results HandleResults,
|
|
handle_errors HandleErrors, typename Callback, typename Executor>
|
|
struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
|
HandleResults, HandleErrors,
|
|
ready_continuation<Args...>, Callback, Executor> {
|
|
|
|
ready_continuation<Args...> continuation_;
|
|
Callback callback_;
|
|
Executor executor_;
|
|
|
|
explicit chained_continuation(ready_continuation<Args...> continuation,
|
|
Callback callback, Executor executor)
|
|
: continuation_(std::move(continuation)), callback_(std::move(callback)),
|
|
executor_(std::move(executor)) {
|
|
}
|
|
|
|
chained_continuation() = delete;
|
|
~chained_continuation() = default;
|
|
chained_continuation(chained_continuation const&) = delete;
|
|
chained_continuation(chained_continuation&&) = default;
|
|
chained_continuation& operator=(chained_continuation const&) = delete;
|
|
chained_continuation& operator=(chained_continuation&&) = default;
|
|
|
|
template <typename NextCallback>
|
|
void operator()(NextCallback&& next_callback) {
|
|
auto proxy = callbacks::make_callback<identity<Args...>, HandleResults,
|
|
HandleErrors>(
|
|
std::move(callback_), std::move(executor_),
|
|
std::forward<decltype(next_callback)>(next_callback));
|
|
|
|
// Extract the result out of the ready continuable
|
|
traits::unpack(std::move(proxy), std::move(continuation_)(query_arg_t{}));
|
|
}
|
|
|
|
bool operator()(is_ready_arg_t) const noexcept {
|
|
return false;
|
|
}
|
|
|
|
std::tuple<NextArgs...> operator()(query_arg_t) {
|
|
util::unreachable();
|
|
}
|
|
};
|
|
|
|
/// 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 <handle_results HandleResults, handle_errors HandleErrors,
|
|
typename Continuation, typename Callback, typename Executor>
|
|
auto chain_continuation(Continuation&& continuation, Callback&& callback,
|
|
Executor&& executor) {
|
|
static_assert(is_continuable<std::decay_t<Continuation>>{},
|
|
"Expected a continuation!");
|
|
|
|
using Hint = decltype(base::annotation_of(identify<Continuation>()));
|
|
constexpr auto next_hint =
|
|
next_hint_of(std::integral_constant<handle_results, HandleResults>{},
|
|
identify<decltype(callback)>{}, Hint{});
|
|
|
|
auto ownership = attorney::ownership_of(continuation);
|
|
auto data =
|
|
attorney::consume(std::forward<Continuation>(continuation).finish());
|
|
|
|
using continuation_t = chained_continuation<
|
|
Hint, traits::unrefcv_t<decltype(next_hint)>, HandleResults, HandleErrors,
|
|
decltype(data), traits::unrefcv_t<Callback>, traits::unrefcv_t<Executor>>;
|
|
|
|
return attorney::create_from_raw(
|
|
continuation_t(std::move(data), std::forward<Callback>(callback),
|
|
std::forward<Executor>(executor)),
|
|
next_hint, ownership);
|
|
}
|
|
|
|
/// Final invokes the given continuation chain:
|
|
///
|
|
/// For example given:
|
|
/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }>
|
|
template <typename Data, typename... Args>
|
|
void finalize_continuation(
|
|
continuable_base<Data, identity<Args...>>&& continuation) noexcept {
|
|
#ifdef CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
|
|
invoke_continuation(std::move(continuation),
|
|
CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK<Args...>{});
|
|
#else // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
|
|
invoke_continuation(std::move(continuation),
|
|
callbacks::final_callback<Args...>{});
|
|
#endif // CONTINUABLE_WITH_CUSTOM_FINAL_CALLBACK
|
|
}
|
|
|
|
/// Deduces to a true type if the given callable data can be wrapped
|
|
/// with the given hint and converted to the given Data.
|
|
template <typename Data, typename Annotation, typename Continuation>
|
|
struct can_accept_continuation : std::false_type {};
|
|
template <typename Data, typename... Args, typename Continuation>
|
|
struct can_accept_continuation<Data, identity<Args...>, Continuation>
|
|
: traits::conjunction<
|
|
traits::is_invocable<Continuation,
|
|
callbacks::final_callback<Args...>>,
|
|
std::is_convertible<
|
|
proxy_continuable<identity<Args...>, Continuation>, Data>> {};
|
|
|
|
/// 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 callable 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));
|
|
}
|
|
|
|
/// Callback which converts its input to the given set of arguments
|
|
template <typename... Args>
|
|
struct convert_to {
|
|
std::tuple<Args...> operator()(Args... args) {
|
|
return std::make_tuple(std::move(args)...);
|
|
}
|
|
};
|
|
template <typename T>
|
|
struct convert_to<T> {
|
|
T operator()(T arg) noexcept {
|
|
return std::move(arg);
|
|
}
|
|
};
|
|
template <>
|
|
struct convert_to<> {
|
|
void operator()() noexcept {
|
|
}
|
|
};
|
|
} // namespace base
|
|
} // namespace detail
|
|
} // namespace cti
|
|
|
|
#endif // CONTINUABLE_DETAIL_BASE_HPP_INCLUDED
|