blah2/lib/asio-1.26.0/asio/experimental/coro.hpp
2023-05-04 20:55:37 +09:30

293 lines
8 KiB
C++

//
// experimental/coro.hpp
// ~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2021-2022 Klemens D. Morgenstern
// (klemens dot morgenstern at gmx dot net)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASIO_EXPERIMENTAL_CORO_HPP
#define ASIO_EXPERIMENTAL_CORO_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include "asio/detail/config.hpp"
#include "asio/dispatch.hpp"
#include "asio/error.hpp"
#include "asio/error_code.hpp"
#include "asio/experimental/coro_traits.hpp"
#include "asio/experimental/detail/coro_promise_allocator.hpp"
#include "asio/experimental/detail/partial_promise.hpp"
#include "asio/post.hpp"
#include "asio/detail/push_options.hpp"
namespace asio {
namespace experimental {
namespace detail {
template <typename Signature, typename Return,
typename Executor, typename Allocator>
struct coro_promise;
template <typename T, typename Coroutine>
struct coro_with_arg;
} // namespace detail
/// The main type of a resumable coroutine.
/**
* Template parameter @c Yield specifies type or signature used by co_yield,
* @c Return specifies the type used for co_return, and @c Executor specifies
* the underlying executor type.
*/
template <typename Yield = void, typename Return = void,
typename Executor = any_io_executor,
typename Allocator = std::allocator<void>>
struct coro
{
/// The traits of the coroutine. See asio::experimental::coro_traits
/// for details.
using traits = coro_traits<Yield, Return, Executor>;
/// The value that can be passed into a symmetrical cororoutine. @c void if
/// asymmetrical.
using input_type = typename traits::input_type;
/// The type that can be passed out through a co_yield.
using yield_type = typename traits::yield_type;
/// The type that can be passed out through a co_return.
using return_type = typename traits::return_type;
/// The type received by a co_await or async_resume. Its a combination of
/// yield and return.
using result_type = typename traits::result_type;
/// The signature used by the async_resume.
using signature_type = typename traits::signature_type;
/// Whether or not the coroutine is noexcept.
constexpr static bool is_noexcept = traits::is_noexcept;
/// The error type of the coroutine. Void for noexcept
using error_type = typename traits::error_type;
/// Completion handler type used by async_resume.
using completion_handler = typename traits::completion_handler;
/// The internal promise-type of the coroutine.
using promise_type = detail::coro_promise<Yield, Return, Executor, Allocator>;
#if !defined(GENERATING_DOCUMENTATION)
template <typename T, typename Coroutine>
friend struct detail::coro_with_arg;
#endif // !defined(GENERATING_DOCUMENTATION)
/// The executor type.
using executor_type = Executor;
/// The allocator type.
using allocator_type = Allocator;
#if !defined(GENERATING_DOCUMENTATION)
friend struct detail::coro_promise<Yield, Return, Executor, Allocator>;
#endif // !defined(GENERATING_DOCUMENTATION)
/// The default constructor, gives an invalid coroutine.
coro() = default;
/// Move constructor.
coro(coro&& lhs) noexcept
: coro_(std::exchange(lhs.coro_, nullptr))
{
}
coro(const coro&) = delete;
/// Move assignment.
coro& operator=(coro&& lhs) noexcept
{
std::swap(coro_, lhs.coro_);
return *this;
}
coro& operator=(const coro&) = delete;
/// Destructor. Destroys the coroutine, if it holds a valid one.
/**
* @note This does not cancel an active coroutine. Destructing a resumable
* coroutine, i.e. one with a call to async_resume that has not completed, is
* undefined behaviour.
*/
~coro()
{
if (coro_ != nullptr)
{
struct destroyer
{
detail::coroutine_handle<promise_type> handle;
destroyer(const detail::coroutine_handle<promise_type>& handle)
: handle(handle)
{ }
destroyer(destroyer&& lhs)
: handle(std::exchange(lhs.handle, nullptr))
{
}
destroyer(const destroyer&) = delete;
void operator()() {}
~destroyer()
{
if (handle)
handle.destroy();
}
};
auto handle =
detail::coroutine_handle<promise_type>::from_promise(*coro_);
if (handle)
asio::dispatch(coro_->get_executor(), destroyer{handle});
}
}
/// Get the used executor.
executor_type get_executor() const
{
if (coro_)
return coro_->get_executor();
if constexpr (std::is_default_constructible_v<Executor>)
return Executor{};
else
throw std::logic_error("Coroutine has no executor");
}
/// Get the used allocator.
allocator_type get_allocator() const
{
if (coro_)
return coro_->get_allocator();
if constexpr (std::is_default_constructible_v<Allocator>)
return Allocator{};
else
throw std::logic_error(
"Coroutine has no available allocator without a constructed promise");
}
/// Resume the coroutine.
/**
* @param token The completion token of the async resume.
*
* @attention Calling an invalid coroutine with a noexcept signature is
* undefined behaviour.
*
* @note This overload is only available for coroutines without an input
* value.
*/
template <typename CompletionToken>
requires std::is_void_v<input_type>
auto async_resume(CompletionToken&& token) &
{
return async_initiate<CompletionToken,
typename traits::completion_handler>(
initiate_async_resume(this), token);
}
/// Resume the coroutine.
/**
* @param token The completion token of the async resume.
*
* @attention Calling an invalid coroutine with a noexcept signature is
* undefined behaviour.
*
* @note This overload is only available for coroutines with an input value.
*/
template <typename CompletionToken, detail::convertible_to<input_type> T>
auto async_resume(T&& ip, CompletionToken&& token) &
{
return async_initiate<CompletionToken,
typename traits::completion_handler>(
initiate_async_resume(this), token, std::forward<T>(ip));
}
/// Operator used for coroutines without input value.
auto operator co_await() requires (std::is_void_v<input_type>)
{
return awaitable_t{*this};
}
/// Operator used for coroutines with input value.
/**
* @param ip The input value
*
* @returns An awaitable handle.
*
* @code
* coro<void> push_values(coro<double(int)> c)
* {
* std::optional<double> res = co_await c(42);
* }
* @endcode
*/
template <detail::convertible_to<input_type> T>
auto operator()(T&& ip)
{
return detail::coro_with_arg<std::decay_t<T>, coro>{
std::forward<T>(ip), *this};
}
/// Check whether the coroutine is open, i.e. can be resumed.
bool is_open() const
{
if (coro_)
{
auto handle =
detail::coroutine_handle<promise_type>::from_promise(*coro_);
return handle && !handle.done();
}
else
return false;
}
/// Check whether the coroutine is open, i.e. can be resumed.
explicit operator bool() const { return is_open(); }
private:
struct awaitable_t;
struct initiate_async_resume;
explicit coro(promise_type* const cr) : coro_(cr) {}
promise_type* coro_{nullptr};
};
/// A generator is a coro that returns void and yields value.
template<typename T, typename Executor = asio::any_io_executor,
typename Allocator = std::allocator<void>>
using generator = coro<T, void, Executor, Allocator>;
/// A task is a coro that does not yield values
template<typename T, typename Executor = asio::any_io_executor,
typename Allocator = std::allocator<void>>
using task = coro<void(), T, Executor, Allocator>;
} // namespace experimental
} // namespace asio
#include "asio/detail/pop_options.hpp"
#include "asio/experimental/impl/coro.hpp"
#endif // ASIO_EXPERIMENTAL_CORO_HPP