mirror of
https://github.com/Naios/continuable.git
synced 2025-12-06 08:46:44 +08:00
1039 lines
39 KiB
C++
1039 lines
39 KiB
C++
|
|
/*
|
|
|
|
/~` _ _ _|_. _ _ |_ | _
|
|
\_,(_)| | | || ||_|(_||_)|(/_
|
|
|
|
https://github.com/Naios/continuable
|
|
v4.0.0
|
|
|
|
Copyright(c) 2015 - 2018 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_BASE_HPP_INCLUDED
|
|
#define CONTINUABLE_BASE_HPP_INCLUDED
|
|
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <continuable/continuable-primitives.hpp>
|
|
#include <continuable/continuable-result.hpp>
|
|
#include <continuable/detail/connection/connection-all.hpp>
|
|
#include <continuable/detail/connection/connection-any.hpp>
|
|
#include <continuable/detail/connection/connection-seq.hpp>
|
|
#include <continuable/detail/connection/connection.hpp>
|
|
#include <continuable/detail/core/base.hpp>
|
|
#include <continuable/detail/core/types.hpp>
|
|
#include <continuable/detail/features.hpp>
|
|
#include <continuable/detail/utility/traits.hpp>
|
|
#include <continuable/detail/utility/util.hpp>
|
|
|
|
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
|
#include <experimental/coroutine>
|
|
#include <continuable/detail/other/coroutines.hpp>
|
|
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
|
|
|
namespace cti {
|
|
/// \defgroup Base Base
|
|
/// provides classes and functions to create continuable_base objects.
|
|
/// \{
|
|
|
|
/// Deduces to a true_type if the given type is a continuable_base.
|
|
///
|
|
/// \since 3.0.0
|
|
template <typename T>
|
|
using is_continuable = detail::base::is_continuable<T>;
|
|
|
|
/// The main class of the continuable library, it provides the functionality
|
|
/// for chaining callbacks and continuations together to a unified hierarchy.
|
|
///
|
|
/// The most important method is the cti::continuable_base::then() method,
|
|
/// which allows to attach a callback to the continuable.
|
|
///
|
|
/// Use the continuable types defined in `continuable/continuable.hpp`,
|
|
/// in order to use this class.
|
|
///
|
|
/// \tparam Data The internal data which is used to store the current
|
|
/// continuation and intermediate lazy connection result.
|
|
///
|
|
/// \tparam Annotation The internal data used to store the current signature
|
|
/// hint or strategy used for combining lazy connections.
|
|
///
|
|
/// \note Nearly all methods of the cti::continuable_base are required to be
|
|
/// called as r-value. This is required because the continuable carries
|
|
/// variables which are consumed when the object is transformed as part
|
|
/// of a method call.
|
|
///
|
|
/// \attention The continuable_base objects aren't intended to be stored.
|
|
/// If you want to store a continuble_base you should always
|
|
/// call the continuable_base::freeze method for disabling the
|
|
/// invocation on destruction.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename Data, typename Annotation>
|
|
class continuable_base {
|
|
|
|
/// \cond false
|
|
using ownership = detail::util::ownership;
|
|
|
|
using annotation_trait = detail::annotation_trait<Annotation>;
|
|
|
|
template <typename, typename>
|
|
friend class continuable_base;
|
|
friend struct detail::base::attorney;
|
|
|
|
// The continuation type or intermediate result
|
|
Data data_;
|
|
// The transferable state which represents the validity of the object
|
|
ownership ownership_;
|
|
/// \endcond
|
|
|
|
/// Constructor accepting the data object while erasing the annotation
|
|
explicit continuable_base(Data data, ownership ownership)
|
|
: data_(std::move(data)), ownership_(std::move(ownership)) {
|
|
}
|
|
|
|
public:
|
|
/// Constructor accepting the data object while erasing the annotation
|
|
explicit continuable_base(Data data) : data_(std::move(data)) {
|
|
}
|
|
|
|
/// Constructor accepting any object convertible to the data object,
|
|
/// while erasing the annotation
|
|
template <typename OtherData,
|
|
std::enable_if_t<detail::base::can_accept_continuation<
|
|
Data, Annotation,
|
|
detail::traits::unrefcv_t<OtherData>>::value>* = nullptr>
|
|
continuable_base(OtherData&& data)
|
|
: data_(detail::base::proxy_continuable<
|
|
Annotation, detail::traits::unrefcv_t<OtherData>>(
|
|
std::forward<OtherData>(data))) {
|
|
}
|
|
|
|
/// Constructor taking the data of other continuable_base objects
|
|
/// while erasing the hint.
|
|
///
|
|
/// This constructor makes it possible to replace the internal data object of
|
|
/// the continuable by any object which is useful for type-erasure.
|
|
template <typename OData, typename OAnnotation>
|
|
continuable_base(continuable_base<OData, OAnnotation>&& other)
|
|
: continuable_base(std::move(other).finish().consume()) {
|
|
}
|
|
|
|
/// \cond false
|
|
continuable_base(continuable_base&&) = default;
|
|
continuable_base(continuable_base const&) = delete;
|
|
|
|
continuable_base& operator=(continuable_base&&) = default;
|
|
continuable_base& operator=(continuable_base const&) = delete;
|
|
/// \endcond
|
|
|
|
/// The destructor automatically invokes the continuable_base
|
|
/// if it wasn't consumed yet.
|
|
///
|
|
/// In order to invoke the continuable early you may call the
|
|
/// continuable_base::done() method.
|
|
///
|
|
/// The continuable_base::freeze method disables the automatic
|
|
/// invocation on destruction without invalidating the object.
|
|
///
|
|
/// \since 1.0.0
|
|
~continuable_base() {
|
|
if (ownership_.is_acquired() && !ownership_.is_frozen()) {
|
|
std::move(*this).done();
|
|
}
|
|
assert((!ownership_.is_acquired() || ownership_.is_frozen()) &&
|
|
"Ownership should be released!");
|
|
}
|
|
|
|
/// Main method of the continuable_base to chain the current continuation
|
|
/// with a new callback.
|
|
///
|
|
/// \param callback The callback which is used to process the current
|
|
/// asynchronous result on arrival. The callback is required to accept
|
|
/// the current result at least partially (or nothing of the result).
|
|
/// ```cpp
|
|
/// (http_request("github.com") && http_request("atom.io"))
|
|
/// .then([](std::string github, std::string atom) {
|
|
/// // We use the whole result
|
|
/// });
|
|
///
|
|
/// (http_request("github.com") && http_request("atom.io"))
|
|
/// .then([](std::string github) {
|
|
/// // We only use the result partially
|
|
/// });
|
|
///
|
|
/// (http_request("github.com") && http_request("atom.io"))
|
|
/// .then([] {
|
|
/// // We discard the result
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \param executor The optional executor which is used to dispatch
|
|
/// the callback. The executor needs to accept callable objects
|
|
/// callable through an `operator()` through its operator() itself.
|
|
/// The executor can be move-only, but it's not required to.
|
|
/// The default executor which is used when omitting the argument
|
|
/// dispatches the callback on the current executing thread.
|
|
/// Consider the example shown below:
|
|
/// ```cpp
|
|
/// auto executor = [](auto&& work) {
|
|
/// // Dispatch the work here or forward it to an executor of
|
|
/// // your choice.
|
|
/// std::forward<decltype(work)>(work)();
|
|
/// };
|
|
///
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) {
|
|
/// // Do something...
|
|
/// }, executor);
|
|
/// ```
|
|
///
|
|
/// \returns Returns a continuable_base with an asynchronous return type
|
|
/// depending on the return value of the callback:
|
|
/// | Callback returns | Resulting type |
|
|
/// | : ---------------------- : | : --------------------------------------- |
|
|
/// | `void` | `continuable_base with <>` |
|
|
/// | `Arg` | `continuable_base with <Arg>` |
|
|
/// | `std::pair<First, Second>` | `continuable_base with <First, Second>` |
|
|
/// | `std::tuple<Args...>` | `continuable_base with <Args...>` |
|
|
/// | `result<Args...>` | `continuable_base with <Args...>` |
|
|
/// | `continuable_base<Arg...>` | `continuable_base with <Args...>` |
|
|
/// Which means the result type of the continuable_base is equal to
|
|
/// the plain types the callback returns (`std::tuple` and
|
|
/// `std::pair` arguments are unwrapped).
|
|
/// A single continuable_base as argument is resolved and the result
|
|
/// type is equal to the resolved continuable_base.
|
|
/// A result<...> can be used to cancel the continuation or to
|
|
/// transition to the exception handler.
|
|
/// Consider the following examples:
|
|
/// ```cpp
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { return; })
|
|
/// .then([] { }); // <void>
|
|
///
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { return 0; })
|
|
/// .then([](int a) { }); // <int>
|
|
///
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { return std::make_pair(1, 2); })
|
|
/// .then([](int a, int b) { }); // <int, int>
|
|
///
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { return std::make_tuple(1, 2, 3); })
|
|
/// .then([](int a, int b, int c) { }); // <int, int, int>
|
|
///
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { return http_request("atom.io"); })
|
|
/// .then([](std::string atom) { }); // <std::string>
|
|
///
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) -> result<std::string> {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// })
|
|
/// .fail([] -> result<std::string> {
|
|
/// return recover("Hello World!");
|
|
/// })
|
|
/// .then([](std::string content) -> result<std::string> {
|
|
/// return cancel();
|
|
/// })
|
|
/// ```
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename T, typename E = detail::types::this_thread_executor_tag>
|
|
auto then(T&& callback,
|
|
E&& executor = detail::types::this_thread_executor_tag{}) && {
|
|
return detail::base::chain_continuation<detail::base::handle_results::yes,
|
|
detail::base::handle_errors::no>(
|
|
std::move(*this).finish(), std::forward<T>(callback),
|
|
std::forward<E>(executor));
|
|
}
|
|
|
|
/// Additional overload of the continuable_base::then() method
|
|
/// which is accepting a continuable_base itself.
|
|
///
|
|
/// \param continuation A continuable_base reflecting the continuation
|
|
/// which is used to continue the call hierarchy.
|
|
/// The result of the current continuable is discarded and the given
|
|
/// continuation is invoked as shown below.
|
|
/// ```cpp
|
|
/// http_request("github.com")
|
|
/// .then(http_request("atom.io"))
|
|
/// .then([](std::string atom) {
|
|
/// // ...
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \returns Returns a continuable_base representing the next asynchronous
|
|
/// result to continue within the asynchronous call hierarchy.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename OData, typename OAnnotation>
|
|
auto then(continuable_base<OData, OAnnotation>&& continuation) && {
|
|
return std::move(*this).then(
|
|
detail::base::wrap_continuation(std::move(continuation).finish()));
|
|
}
|
|
|
|
/// Main method of the continuable_base to catch exceptions and error codes
|
|
/// in case the asynchronous control flow failed and was resolved
|
|
/// through an error code or exception.
|
|
///
|
|
/// \param callback The callback which is used to process the current
|
|
/// asynchronous error result on arrival.
|
|
/// In case the continuable_base is using exceptions,
|
|
/// the usage is as shown below:
|
|
///
|
|
/// ```cpp
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { })
|
|
/// .fail([](std::exception_ptr ptr) {
|
|
/// // Handle the error here
|
|
/// try {
|
|
/// std::rethrow_exception(ptr);
|
|
/// } catch (std::exception& e) {
|
|
/// e.what(); // Handle the exception
|
|
/// }
|
|
/// });
|
|
/// ```
|
|
/// In case exceptions are disabled, `std::error_condition` is
|
|
/// used as error result instead of `std::exception_ptr`.
|
|
/// ```cpp
|
|
/// http_request("github.com")
|
|
/// .then([](std::string github) { })
|
|
/// .fail([](std::error_condition error) {
|
|
/// error.message(); // Handle the error here
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \param executor The optional executor which is used to dispatch
|
|
/// the callback. See the description in `then` above.
|
|
///
|
|
/// \returns Returns a continuable_base with an asynchronous return type
|
|
/// depending on the previous result type.
|
|
///
|
|
/// \since 2.0.0
|
|
template <typename T, typename E = detail::types::this_thread_executor_tag>
|
|
auto fail(T&& callback,
|
|
E&& executor = detail::types::this_thread_executor_tag{}) && {
|
|
return detail::base::chain_continuation<
|
|
detail::base::handle_results::no, detail::base::handle_errors::forward>(
|
|
std::move(*this).finish(),
|
|
detail::base::strip_exception_arg(std::forward<T>(callback)),
|
|
std::forward<E>(executor));
|
|
}
|
|
|
|
/// Additional overload of the continuable_base::fail() method
|
|
/// which is accepting a continuable_base itself.
|
|
///
|
|
/// \param continuation A continuable_base reflecting the continuation
|
|
/// which is used to continue the call hierarchy on errors.
|
|
/// The result of the current continuable is discarded and the given
|
|
/// continuation is invoked as shown below.
|
|
/// ```cpp
|
|
/// http_request("github.com")
|
|
/// .fail(http_request("atom.io"))
|
|
/// ```
|
|
///
|
|
/// \returns Returns a continuable_base with an asynchronous return type
|
|
/// depending on the previous result type.
|
|
///
|
|
/// \since 2.0.0
|
|
template <typename OData, typename OAnnotation>
|
|
auto fail(continuable_base<OData, OAnnotation>&& continuation) && {
|
|
return std::move(*this) //
|
|
.fail([continuation = std::move(continuation).freeze()] //
|
|
(exception_t) mutable {
|
|
std::move(continuation).done(); //
|
|
});
|
|
}
|
|
|
|
/// A method which allows to use an overloaded callable for the error
|
|
/// as well as the valid result path.
|
|
///
|
|
/// \param callback The callback which is used to process the current
|
|
/// asynchronous result and error on arrival.
|
|
///
|
|
/// ```cpp
|
|
/// struct my_callable {
|
|
/// void operator() (std::string result) {
|
|
/// // ...
|
|
/// }
|
|
/// void operator() (cti::exception_arg_t, cti::exception_t) {
|
|
/// // ...
|
|
/// }
|
|
///
|
|
/// // Will receive errors and results
|
|
/// http_request("github.com")
|
|
/// .next(my_callable{});
|
|
/// ```
|
|
///
|
|
/// \param executor The optional executor which is used to dispatch
|
|
/// the callback. See the description in `then` above.
|
|
///
|
|
/// \returns Returns a continuable_base with an asynchronous return type
|
|
/// depending on the current result type.
|
|
///
|
|
/// \since 2.0.0
|
|
template <typename T, typename E = detail::types::this_thread_executor_tag>
|
|
auto next(T&& callback,
|
|
E&& executor = detail::types::this_thread_executor_tag{}) && {
|
|
return detail::base::chain_continuation<
|
|
detail::base::handle_results::yes,
|
|
detail::base::handle_errors::forward>(std::move(*this).finish(),
|
|
std::forward<T>(callback),
|
|
std::forward<E>(executor));
|
|
}
|
|
|
|
/// Returns a continuable_base which will have its signature converted
|
|
/// to the given Args.
|
|
///
|
|
/// A signature can only be converted if it can be partially applied
|
|
/// from the previous one as shown below:
|
|
/// ```cpp
|
|
/// continuable<long> c = make_ready_continuable(0, 1, 2).as<long>();
|
|
/// ```
|
|
///
|
|
/// \returns Returns a continuable_base with an asynchronous return type
|
|
/// matching the given Args.
|
|
///
|
|
/// \since 4.0.0
|
|
template <typename... Args>
|
|
auto as() && {
|
|
return std::move(*this).then(detail::base::convert_to<Args...>{});
|
|
}
|
|
|
|
/// A method which allows to apply this continuable to the given callable.
|
|
///
|
|
/// \param transform A transform which shall accept this continuable
|
|
///
|
|
/// \returns Returns the result of the given transform when this
|
|
/// continuable is passed into it.
|
|
///
|
|
/// \since 2.0.0
|
|
template <typename T>
|
|
auto apply(T&& transform) && {
|
|
return std::forward<T>(transform)(std::move(*this).finish());
|
|
}
|
|
|
|
/// The pipe operator | is an alias for the continuable::then method.
|
|
///
|
|
/// \param right The argument on the right-hand side to connect.
|
|
///
|
|
/// \returns See the corresponding continuable_base::then method for the
|
|
/// explanation of the return type.
|
|
///
|
|
/// \since 2.0.0
|
|
template <typename T>
|
|
auto operator|(T&& right) && {
|
|
return std::move(*this).then(std::forward<T>(right));
|
|
}
|
|
|
|
/// The pipe operator | is an alias for the continuable::apply method.
|
|
///
|
|
/// \param transform The transformer which is applied.
|
|
///
|
|
/// \returns See the corresponding continuable_base::apply method for the
|
|
/// explanation of the return type.
|
|
///
|
|
/// \note You may create your own transformation through
|
|
/// calling make_transformation.
|
|
///
|
|
/// \since 3.0.0
|
|
template <typename T>
|
|
auto operator|(detail::types::transform<T> transform) && {
|
|
return std::move(*this).apply(std::move(transform));
|
|
}
|
|
|
|
/// Invokes both continuable_base objects parallel and calls the
|
|
/// callback with the result of both continuable_base objects.
|
|
///
|
|
/// \param right The continuable on the right-hand side to connect.
|
|
///
|
|
/// \returns Returns a continuable_base with a result type matching
|
|
/// the result of the left continuable_base combined with the
|
|
/// right continuable_base.
|
|
/// The returned continuable_base will be in an intermediate lazy
|
|
/// state, further calls to its continuable_base::operator &&
|
|
/// will add other continuable_base objects to the current
|
|
/// invocation chain.
|
|
/// ```cpp
|
|
/// (http_request("github.com") && http_request("atom.io"))
|
|
/// .then([](std::string github, std::string atom) {
|
|
/// // ...
|
|
/// });
|
|
///
|
|
/// auto request = http_request("github.com") && http_request("atom.io");
|
|
/// (std::move(request) && http_request("travis-ci.org"))
|
|
/// // All three requests are invoked in parallel although we added
|
|
/// // the request to "travis-ci.org" last.
|
|
/// .then([](std::string github, std::string atom, std::string travis) {
|
|
/// // ...
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \note The continuable_base objects are invoked all at onve,
|
|
/// because the `all` strategy tries to resolve
|
|
/// the continuations as fast as possible.
|
|
/// Sequential invocation is also supported through the
|
|
/// continuable_base::operator>> method.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename OData, typename OAnnotation>
|
|
auto operator&&(continuable_base<OData, OAnnotation>&& right) && {
|
|
return detail::connection::connect(
|
|
detail::connection::connection_strategy_all_tag{}, std::move(*this),
|
|
std::move(right));
|
|
}
|
|
|
|
/// Invokes both continuable_base objects parallel and calls the
|
|
/// callback once with the first result available.
|
|
///
|
|
/// \param right The continuable on the right-hand side to connect.
|
|
/// The right continuable is required to have the same
|
|
/// result as the left connected continuable_base.
|
|
///
|
|
/// \returns Returns a continuable_base with a result type matching
|
|
/// the combined result which of all connected
|
|
/// continuable_base objects.
|
|
/// The returned continuable_base will be in an intermediate lazy
|
|
/// state, further calls to its continuable_base::operator ||
|
|
/// will add other continuable_base objects to the current
|
|
/// invocation chain.
|
|
/// ```cpp
|
|
/// (http_request("github.com") || http_request("atom.io"))
|
|
/// .then([](std::string github_or_atom) {
|
|
/// // ...
|
|
/// });
|
|
///
|
|
/// (make_ready_continuable(10, 'A') || make_ready_continuable(29, 'B'))
|
|
/// .then([](int a, char b) {
|
|
/// // ...
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \note The continuable_base objects are invoked all at once,
|
|
/// however, the callback is only called once with
|
|
/// the first result or exception which becomes available.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename OData, typename OAnnotation>
|
|
auto operator||(continuable_base<OData, OAnnotation>&& right) && {
|
|
return detail::connection::connect(
|
|
detail::connection::connection_strategy_any_tag{}, std::move(*this),
|
|
std::move(right));
|
|
}
|
|
|
|
/// Invokes both continuable_base objects sequential and calls the
|
|
/// callback with the result of both continuable_base objects.
|
|
///
|
|
/// \param right The continuable on the right-hand side to connect.
|
|
///
|
|
/// \returns Returns a continuable_base with a result type matching
|
|
/// the result of the left continuable_base combined with the
|
|
/// right continuable_base.
|
|
/// ```cpp
|
|
/// (http_request("github.com") >> http_request("atom.io"))
|
|
/// .then([](std::string github, std::string atom) {
|
|
/// // The callback is called with the result of both requests,
|
|
/// // however, the request to atom was started after the request
|
|
/// // to github was finished.
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \note The continuable_base objects are invoked sequential one after
|
|
/// the previous one was finished. Parallel invocation is also
|
|
/// supported through the continuable_base::operator && method.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename OData, typename OAnnotation>
|
|
auto operator>>(continuable_base<OData, OAnnotation>&& right) && {
|
|
return detail::connection::seq::sequential_connect(std::move(*this),
|
|
std::move(right));
|
|
}
|
|
|
|
/// Invokes the continuation chain manually even before the
|
|
/// cti::continuable_base is destructed. This will release the object.
|
|
///
|
|
/// \see continuable_base::~continuable_base() for further details about
|
|
/// the continuation invocation on destruction.
|
|
///
|
|
/// \attention This method will trigger an assertion if the
|
|
/// continuable_base was released already.
|
|
///
|
|
/// \since 1.0.0
|
|
void done() && {
|
|
detail::base::finalize_continuation(std::move(*this));
|
|
}
|
|
|
|
/// Materializes the continuation expression template and finishes
|
|
/// the current applied strategy such that the resulting continuable
|
|
/// will always be a concrete type and Continuable::is_concrete holds.
|
|
///
|
|
/// This can be used in the case where we are chaining continuations lazily
|
|
/// through a strategy, for instance when applying operators for
|
|
/// expressing connections and then want to return a materialized
|
|
/// continuable_base which uses the strategy respectively.
|
|
/// ```cpp
|
|
/// auto do_both() {
|
|
/// return (wait(10s) || wait_key_pressed(KEY_SPACE)).finish();
|
|
/// }
|
|
///
|
|
/// // Without a call to finish() this would lead to
|
|
/// // an unintended evaluation strategy:
|
|
/// do_both() || wait(5s);
|
|
/// ```
|
|
///
|
|
/// \note When using a type erased continuable_base such as
|
|
/// `continuable<...>` this method doesn't need to be called
|
|
/// since the continuable_base is materialized automatically
|
|
/// on conversion.
|
|
///
|
|
/// \since 4.0.0
|
|
auto finish() && {
|
|
return annotation_trait::finish(std::move(*this));
|
|
}
|
|
|
|
/// Predicate to check whether the cti::continuable_base is frozen or not.
|
|
///
|
|
/// \returns Returns true when the continuable_base is frozen.
|
|
///
|
|
/// \see continuable_base::freeze for further details.
|
|
///
|
|
/// \attention This method will trigger an assertion if the
|
|
/// continuable_base was released already.
|
|
///
|
|
/// \since 1.0.0
|
|
bool is_frozen() const noexcept {
|
|
assert_acquired();
|
|
return ownership_.is_frozen();
|
|
}
|
|
|
|
/// Prevents the automatic invocation of the continuation chain
|
|
/// which happens on destruction of the continuable_base.
|
|
/// You may still invoke the chain through the continuable_base::done method.
|
|
///
|
|
/// This is useful for storing a continuable_base inside a continuation
|
|
/// chain while storing it for further usage.
|
|
///
|
|
/// \param enabled Indicates whether the freeze is enabled or disabled.
|
|
///
|
|
/// \see continuable_base::~continuable_base() for further details about
|
|
/// the continuation invocation on destruction.
|
|
///
|
|
/// \attention This method will trigger an assertion if the
|
|
/// continuable_base was released already.
|
|
///
|
|
/// \since 1.0.0
|
|
continuable_base& freeze(bool enabled = true) & noexcept {
|
|
ownership_.freeze(enabled);
|
|
return *this;
|
|
}
|
|
|
|
/// \copydoc continuable_base::freeze
|
|
continuable_base&& freeze(bool enabled = true) && noexcept {
|
|
ownership_.freeze(enabled);
|
|
return std::move(*this);
|
|
}
|
|
|
|
/// \cond false
|
|
#ifdef CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
|
/// \endcond
|
|
/// Implements the operator for awaiting on continuables using `co_await`.
|
|
///
|
|
/// The operator is only enabled if `CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE`
|
|
/// is defined and the toolchain supports experimental coroutines.
|
|
///
|
|
/// The return type of the `co_await` expression is specified as following:
|
|
/// | Continuation type | co_await returns |
|
|
/// | : ------------------------------- | : -------------------------------- |
|
|
/// | `continuable_base with <>` | `void` |
|
|
/// | `continuable_base with <Arg>` | `Arg` |
|
|
/// | `continuable_base with <Args...>` | `std::tuple<Args...>` |
|
|
///
|
|
/// When exceptions are used the usage is as intuitive as shown below:
|
|
/// ```cpp
|
|
/// // Handling the exception isn't required and
|
|
/// // the try catch clause may be omitted.
|
|
/// try {
|
|
/// std::string response = co_await http_request("github.com");
|
|
/// } (std::exception& e) {
|
|
/// e.what();
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// In case the library is configured to use error codes or a custom
|
|
/// exception type the return type of the co_await expression is changed.
|
|
/// The result is returned through a cti::result<...>.
|
|
/// | Continuation type | co_await returns |
|
|
/// | : ------------------------------- | : -------------------------------- |
|
|
/// | `continuable_base with <>` | `result<void>` |
|
|
/// | `continuable_base with <Arg>` | `result<Arg>` |
|
|
/// | `continuable_base with <Args...>` | `result<Args...>` |
|
|
///
|
|
/// \note Using continuable_base as return type for coroutines
|
|
/// is supported. The coroutine is initially stopped and
|
|
/// resumed when the continuation is requested in order to
|
|
/// keep the lazy evaluation semantics of the continuable_base.
|
|
/// ```cpp
|
|
/// cti::continuable<> resolve_async_void() {
|
|
/// co_await http_request("github.com");
|
|
/// // ...
|
|
/// co_return;
|
|
/// }
|
|
///
|
|
/// cti::continuable<int> resolve_async() {
|
|
/// co_await http_request("github.com");
|
|
/// // ...
|
|
/// co_return 0;
|
|
/// }
|
|
/// ```
|
|
/// It's possible to return multiple return values from coroutines
|
|
/// by wrapping those in a tuple like type:
|
|
/// ```cpp
|
|
/// cti::continuable<int, int, int> resolve_async_multiple() {
|
|
/// co_await http_request("github.com");
|
|
/// // ...
|
|
/// co_return std::make_tuple(0, 1, 2);
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// \since 2.0.0
|
|
auto operator co_await() && {
|
|
return detail::awaiting::create_awaiter(std::move(*this).finish());
|
|
}
|
|
/// \cond false
|
|
#endif // CONTINUABLE_HAS_EXPERIMENTAL_COROUTINE
|
|
/// \endcond
|
|
|
|
private:
|
|
void release() noexcept {
|
|
ownership_.release();
|
|
}
|
|
|
|
Data&& consume() && {
|
|
assert_acquired();
|
|
release();
|
|
return std::move(data_);
|
|
}
|
|
|
|
void assert_acquired() const {
|
|
assert(ownership_.is_acquired() && "Tried to use a released continuable!");
|
|
}
|
|
};
|
|
|
|
/// Creates a continuable_base from a promise/callback taking function.
|
|
///
|
|
/// \tparam Args The types (signature hint) the given promise is resolved with.
|
|
/// * **Some arguments** indicate the types the promise will be invoked with.
|
|
/// ```cpp
|
|
/// auto ct = cti::make_continuable<int, std::string>([](auto&& promise) {
|
|
/// promise.set_value(200, "<html>...</html>");
|
|
/// });
|
|
/// ```
|
|
/// * `void` **as argument** indicates that the promise will be invoked
|
|
/// with no arguments:
|
|
/// ```cpp
|
|
/// auto ct = cti::make_continuable<void>([](auto&& promise) {
|
|
/// promise.set_value();
|
|
/// });
|
|
/// ```
|
|
/// * **No arguments** Since version 3.0.0 make_continuable always requires
|
|
/// to be given valid arguments!
|
|
/// You should always give the type hint a callback is called with because
|
|
/// it's required for intermediate actions like connecting continuables.
|
|
/// You may omit the signature hint if you are erasing the type of
|
|
/// the continuable right after creation.
|
|
/// ```cpp
|
|
/// // This won't work because the arguments are missing:
|
|
/// auto ct = cti::make_continuable([](auto&& promise) {
|
|
/// promise.set_value(0.f, 'c');
|
|
/// });
|
|
///
|
|
/// // However, you are allowed to do this:
|
|
/// cti::continuable<float, char> ct = [](auto&& promise) {
|
|
/// promise.set_value(callback)(0.f, 'c');
|
|
/// };
|
|
/// ```
|
|
///
|
|
/// \param continuation The continuation the continuable is created from.
|
|
/// The continuation must be a callable type accepting a callback parameter
|
|
/// which represents the object invokable with the asynchronous result of this
|
|
/// continuable.
|
|
/// ```cpp
|
|
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
|
|
/// promise.set_value("result");
|
|
/// });
|
|
/// ```
|
|
/// The callback may be stored or moved.
|
|
/// In some cases the callback may be copied if supported by the underlying
|
|
/// callback chain, in order to invoke the call chain multiple times.
|
|
/// It's recommended to accept any callback instead of erasing it.
|
|
/// ```cpp
|
|
/// // Good practice:
|
|
/// auto ct = cti::make_continuable<std::string>([](auto&& promise) {
|
|
/// promise.set_value("result");
|
|
/// });
|
|
///
|
|
/// // Good practice using a callable object:
|
|
/// struct Continuation {
|
|
/// template<typename T>
|
|
/// void operator() (T&& continuation) && {
|
|
/// // ...
|
|
/// }
|
|
/// }
|
|
///
|
|
/// auto ct = cti::make_continuable<std::string>(Continuation{});
|
|
///
|
|
/// // Bad practice (because of unnecessary type erasure):
|
|
/// auto ct = cti::make_continuable<std::string>(
|
|
/// [](cti::promise<std::string> promise) {
|
|
/// promise.set_value("result");
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \returns A continuable_base with unspecified template parameters which
|
|
/// wraps the given continuation.
|
|
/// In order to convert the continuable_base to a known type
|
|
/// you need to apply type erasure through the
|
|
/// \link cti::continuable continuable\endlink or
|
|
/// \link cti::promise promise\endlink facilities.
|
|
///
|
|
/// \note You should always turn the callback/promise into a r-value if possible
|
|
/// (`std::move` or `std::forward`) for qualifier correct invokation.
|
|
/// Additionally it's important to know that all continuable promises
|
|
/// are callbacks and just expose their call operator nicely through
|
|
/// \link cti::promise_base::set_value set_value \endlink and
|
|
/// \link cti::promise_base::set_exception set_exception \endlink.
|
|
///
|
|
/// \since 1.0.0
|
|
template <typename... Args, typename Continuation>
|
|
constexpr auto make_continuable(Continuation&& continuation) {
|
|
static_assert(sizeof...(Args) > 0,
|
|
"Since version 3.0.0 make_continuable requires an exact "
|
|
"signature! If you did intend to create a void continuable "
|
|
"use make_continuable<void>(...). Continuables with an exact "
|
|
"signature may be created through make_continuable<Args...>.");
|
|
|
|
return detail::base::attorney::create_from(
|
|
std::forward<Continuation>(continuation),
|
|
typename detail::hints::from_args<Args...>::type{},
|
|
detail::util::ownership{});
|
|
}
|
|
|
|
/// Returns a continuable_base with no result which instantly resolves
|
|
/// the promise with no values.
|
|
///
|
|
/// \attention Usually using this function isn't needed at all since
|
|
/// the continuable library is capable of working with
|
|
/// plain values in most cases.
|
|
/// Try not to use it since it causes unneccessary recursive
|
|
/// function calls.
|
|
///
|
|
/// \since 3.0.0
|
|
template <typename... Args>
|
|
auto make_ready_continuable(Args&&... args) {
|
|
using detail::base::ready_continuation;
|
|
using detail::traits::identity;
|
|
using detail::traits::unrefcv_t;
|
|
return detail::base::attorney::create_from_raw(
|
|
ready_continuation<unrefcv_t<Args>...>{std::forward<Args>(args)...},
|
|
identity<unrefcv_t<Args>...>{}, detail::util::ownership{});
|
|
}
|
|
|
|
/// Returns a continuable_base with the parameterized result which instantly
|
|
/// resolves the promise with the given error type.
|
|
///
|
|
/// See an example below:
|
|
/// ```cpp
|
|
/// std::logic_error exception("Some issue!");
|
|
/// auto ptr = std::make_exception_ptr(exception);
|
|
/// auto ct = cti::make_exceptional_continuable<int>(ptr);
|
|
/// ```
|
|
///
|
|
/// \tparam Signature The fake signature of the returned continuable.
|
|
///
|
|
/// \since 3.0.0
|
|
template <typename... Signature, typename Exception>
|
|
constexpr auto make_exceptional_continuable(Exception&& exception) {
|
|
static_assert(sizeof...(Signature) > 0,
|
|
"Requires at least one type for the fake signature!");
|
|
|
|
return make_continuable<Signature...>( // ...
|
|
[exception = std::forward<Exception>(exception)](auto&& promise) mutable {
|
|
std::forward<decltype(promise)>(promise).set_exception(
|
|
std::move(exception));
|
|
});
|
|
}
|
|
|
|
/// Returns a continuable_base with the parameterized result which never
|
|
/// resolves its promise and thus cancels the asynchronous continuation chain.
|
|
///
|
|
/// This can be used to cancel an asynchronous continuation chain when
|
|
/// returning a continuable_base from a handler where other paths could
|
|
/// possibly continue the asynchronous chain. See an example below:
|
|
/// ```cpp
|
|
/// do_sth().then([weak = this->weak_from_this()]() -> continuable<> {
|
|
/// if (auto me = weak.lock()) {
|
|
/// return do_sth_more();
|
|
/// } else {
|
|
/// // Abort the asynchronous continuation chain since the
|
|
/// // weakly referenced object expired previously.
|
|
/// return make_cancelling_continuable<void>();
|
|
/// }
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \tparam Signature The fake signature of the returned continuable.
|
|
///
|
|
/// \since 4.0.0
|
|
template <typename... Signature>
|
|
auto make_cancelling_continuable() {
|
|
static_assert(sizeof...(Signature) > 0,
|
|
"Requires at least one type for the fake signature!");
|
|
|
|
return make_continuable<Signature...>([](auto&&) { /* ... */ });
|
|
}
|
|
|
|
/// Can be used to recover to from a failure handler,
|
|
/// the result handler which comes after will be called with the
|
|
/// corresponding result.
|
|
///
|
|
/// The \ref exceptional_result returned by this function can be returned
|
|
/// from any result or failure handler in order to rethrow the exception.
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) {
|
|
/// return recover(1, 2);
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) {
|
|
/// return recover(1, 2);
|
|
/// })
|
|
/// .then([](int a, int b) {
|
|
/// // Recovered from the failure
|
|
/// })
|
|
/// ```
|
|
/// A corresponding \ref result is returned by \ref recover:
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) -> cti::result<int, int> {
|
|
/// return recover(1, 2);
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) -> cti::result<int, int> {
|
|
/// return recover(1, 2);
|
|
/// })
|
|
/// .then([](int a, int b) -> cti::result<int, int> {
|
|
/// // Recovered from the failure
|
|
/// })
|
|
/// ```
|
|
///
|
|
/// \since 4.0.0
|
|
///
|
|
template <typename... Args>
|
|
result<detail::traits::unrefcv_t<Args>...> recover(Args&&... args) {
|
|
return make_result(std::forward<Args>(args)...);
|
|
}
|
|
|
|
/// Can be used to rethrow an exception to the asynchronous continuation chain,
|
|
/// the failure handler which comes after will be called with the
|
|
/// corresponding exception.
|
|
///
|
|
/// The \ref exceptional_result returned by this function can be returned
|
|
/// from any result or failure handler in order to rethrow the exception.
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// })
|
|
/// .next([](auto&&...) {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// });
|
|
/// ```
|
|
/// The returned \ref exceptional_result is convertible to
|
|
/// any \ref result as shown below:
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) -> cti::result<> {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) -> cti::result<> {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// })
|
|
/// .next([](auto&&...) -> cti::result<> {
|
|
/// return rethrow(std::make_exception_ptr(std::exception{}));
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \since 4.0.0
|
|
///
|
|
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
|
inline exceptional_result rethrow(exception_t exception) {
|
|
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
|
return exceptional_result{std::move(exception)};
|
|
}
|
|
|
|
/// Can be used to cancel an asynchronous continuation chain,
|
|
/// no handler which comes after cancel was received won't be called.
|
|
///
|
|
/// The \ref empty_result returned by this function can be returned from
|
|
/// any result or failure handler in order to cancel the chain.
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) {
|
|
/// return cancel();
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) {
|
|
/// return cancel();
|
|
/// })
|
|
/// .next([](auto&&...) {
|
|
/// return cancel();
|
|
/// });
|
|
/// ```
|
|
/// The returned \ref empty_result is convertible to
|
|
/// any \ref result as shown below:
|
|
/// ```cpp
|
|
/// http_request("example.com")
|
|
/// .then([](std::string content) -> cti::result<> {
|
|
/// return cancel();
|
|
/// })
|
|
/// .fail([](cti::exception_t exception) -> cti::result<> {
|
|
/// return cancel();
|
|
/// })
|
|
/// .next([](auto&&...) -> cti::result<> {
|
|
/// return cancel();
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// \since 4.0.0
|
|
///
|
|
inline empty_result cancel() {
|
|
return {};
|
|
}
|
|
|
|
/// \}
|
|
} // namespace cti
|
|
|
|
#endif // CONTINUABLE_BASE_HPP_INCLUDED
|