[/
 / Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
 /
 / 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)
 /]

[section:cpp20_coroutines C++20 Coroutines Support]

Support for C++20 Coroutines is provided via the [link asio.reference.awaitable
`awaitable`] class template, the [link asio.reference.use_awaitable_t
`use_awaitable`] completion token, and the [link asio.reference.co_spawn
`co_spawn()`] function. These facilities allow programs to implement
asynchronous logic in a synchronous manner, in conjunction with the `co_await`
keyword, as shown in the following example:

  asio::co_spawn(executor, echo(std::move(socket)), asio::detached);

  // ...

  asio::awaitable<void> echo(tcp::socket socket)
  {
    try
    {
      char data[1024];
      for (;;)
      {
        std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
        co_await async_write(socket, asio::buffer(data, n), asio::use_awaitable);
      }
    }
    catch (std::exception& e)
    {
      std::printf("echo Exception: %s\n", e.what());
    }
  }

The first argument to `co_spawn()` is an [link asio.reference.Executor1
executor] that determines the context in which the coroutine is permitted to
execute. For example, a server's per-client object may consist of multiple
coroutines; they should all run on the same `strand` so that no explicit
synchronisation is required.

The second argument is an [link asio.reference.awaitable `awaitable<R>`],
that is the result of the coroutine's entry point function, and in the above
example is the result of the call to `echo`. (Alternatively, this argument can
be a function object that returns the [link asio.reference.awaitable
`awaitable<R>`].) The template parameter `R` is the type of return value
produced by the coroutine. In the above example, the coroutine returns `void`.

The third argument is a completion token, and this is used by `co_spawn()` to
produce a completion handler with signature `void(std::exception_ptr, R)`. This
completion handler is invoked with the result of the coroutine once it has
finished. In the above example we pass a completion token type, [link
asio.reference.detached `asio::detached`], which is used to explicitly ignore
the result of an asynchronous operation.

In this example the body of the coroutine is implemented in the `echo`
function. When the `use_awaitable` completion token is passed to an
asynchronous operation, the operation's initiating function returns an
`awaitable` that may be used with the `co_await` keyword:

  std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);

Where an asynchronous operation's handler signature has the form:

  void handler(asio::error_code ec, result_type result);

the resulting type of the `co_await` expression is `result_type`. In the
`async_read_some` example above, this is `size_t`. If the asynchronous
operation fails, the `error_code` is converted into a `system_error` exception
and thrown.

Where a handler signature has the form:

  void handler(asio::error_code ec);

the `co_await` expression produces a `void` result. As above, an error is
passed back to the coroutine as a `system_error` exception.

[heading Error Handling]

To perform explicit error handling, rather than the default exception-throwing
behaviour, use the [link asio.reference.as_tuple `as_tuple`] or
[link asio.reference.redirect_error `redirect_error`] completion token adapters.

The `as_tuple` completion token adapter packages the completion
handler arguments into a single tuple, which is then returned as the result of
the awaited operation. For example:

  asio::awaitable<void> echo(tcp::socket socket)
  {
    char data[1024];
    for (;;)
    {
      std::tuple<asio::error_code, std::size_t> result =
        co_await socket.async_read_some(asio::buffer(data),
          asio::as_tuple(asio::use_awaitable));
      if (!std::get<0>(result))
      {
        // success
      }

      // ...
    }
  }

The result can also be captured directly into a structured binding:

  asio::awaitable<void> echo(tcp::socket socket)
  {
    char data[1024];
    for (;;)
    {
      auto [ec, n] = co_await socket.async_read_some(
          asio::buffer(data), asio::as_tuple(asio::use_awaitable));
      if (!ec)
      {
        // success
      }

      // ...
    }
  }

Alternatively, the `redirect_error` completion token adapter may be used to
capture the error into a supplied `error_code` variable:

  asio::awaitable<void> echo(tcp::socket socket)
  {
    char data[1024];
    for (;;)
    {
      asio::error_code ec;
      std::size_t n = co_await socket.async_read_some(asio::buffer(data),
          asio::redirect_error(asio::use_awaitable, ec));
      if (!ec)
      {
        // success
      }

      // ...
    }
  }

[heading Coroutines and Per-Operation Cancellation]

All threads of execution created by `co_spawn` have a cancellation state that
records the current state of any cancellation requests made to the coroutine.
To access this state, use [link asio.reference.this_coro__cancellation_state
`this_coro::cancellation_state`] as follows:

  asio::awaitable<void> my_coroutine()
  {
    asio::cancellation_state cs
      = co_await asio::this_coro::cancellation_state;

    // ...

    if (cs.cancelled() != asio::cancellation_type::none)
      // ...
  }

When first created by `co_spawn`, the thread of execution has a cancellation
state that supports `cancellation_type::terminal` values only. To change the
cancellation state, call [link
asio.reference.this_coro__reset_cancellation_state
`this_coro::reset_cancellation_state`].

By default, continued execution of a cancelled coroutine will trigger an
exception from any subsequent `co_await` of an `awaitable<>` object. This
behaviour can be changed by using [link
asio.reference.this_coro__throw_if_cancelled `this_coro::throw_if_cancelled`].

[heading Co-ordinating Parallel Coroutines]

[note This is an experimental feature.]

The logical operators `||` and `&&` have been overloaded for `awaitable<>`, to
allow coroutines to be trivially awaited in parallel.

When awaited using `&&`, the `co_await` expression waits until both operations
have completed successfully. As a "short-circuit" evaluation, if one
operation fails with an exception, the other is immediately cancelled.
For example:

  std::tuple<std::size_t, std::size_t> results =
    co_await (
      async_read(socket, input_buffer, use_awaitable)
        && async_write(socket, output_buffer, use_awaitable)
    );

Following completion of a `&&` operation, the results of all operations are
concatenated into a tuple. In the above example, the first `size_t` represents
the non-exceptional component of the `async_read` result, and the second
`size_t` is the result of the `async_write`.

When awaited using `||`, the `co_await` expression waits until either
operation succeeds. As a "short-circuit" evaluation, if one operation
succeeds without throwing an exception, the other is immediately cancelled.
For example:

  std::variant<std::size_t, std::monostate> results =
    co_await (
      async_read(socket, input_buffer, use_awaitable)
        || timer.async_wait(use_awaitable)
    );

Following completion of a `||` operation, the result of the first operation to
complete non-exceptionally is placed into a `std::variant`. The active index
of the variant reflects which of the operations completed first.
In the above example, index `0` corresponds to the `async_read` operation.

These operators may be enabled by adding the `#include`:

  #include <asio/experimental/awaitable_operators.hpp>

and then bringing the contents of the `experimental::awaitable_operators`
namespace into scope:

  using namespace asio::experimental::awaitable_operators;

[heading Lightweight Coroutines Implementing Asynchonous Operations]

The `co_composed` template facilitates a lightweight implementation of
user-defined asynchronous operations using C++20 coroutines. The following
example illustrates a simple asynchronous operation that implements an echo
protocol in terms of a coroutine:

  template <typename CompletionToken>
  auto async_echo(tcp::socket& socket,
      CompletionToken&& token)
  {
    return asio::async_initiate<
      CompletionToken, void(asio::error_code)>(
        asio::co_composed<
          void(asio::error_code)>(
            [](auto state, tcp::socket& socket) -> void
            {
              try
              {
                state.throw_if_cancelled(true);
                state.reset_cancellation_state(
                  asio::enable_terminal_cancellation());

                for (;;)
                {
                  char data[1024];
                  std::size_t n = co_await socket.async_read_some(
                      asio::buffer(data), asio::deferred);

                  co_await asio::async_write(socket,
                      asio::buffer(data, n), asio::deferred);
                }
              }
              catch (const asio::system_error& e)
              {
                co_return {e.code()};
              }
            }, socket),
        token, std::ref(socket));
  }

[heading See Also]

[link asio.reference.co_spawn co_spawn],
[link asio.reference.detached detached],
[link asio.reference.as_tuple as_tuple],
[link asio.reference.redirect_error redirect_error],
[link asio.reference.awaitable awaitable],
[link asio.reference.use_awaitable_t use_awaitable_t],
[link asio.reference.use_awaitable use_awaitable],
[link asio.reference.this_coro__executor this_coro::executor],
[link asio.reference.co_composed co_composed],
[link asio.examples.cpp17_examples.coroutines_ts_support Coroutines examples],
[link asio.overview.composition.coro Resumable C++20 Coroutines],
[link asio.overview.composition.spawn Stackful Coroutines],
[link asio.overview.composition.coroutine Stackless Coroutines].

[endsect]
