|  | Home | Libraries | People | FAQ | More | 
| ![[Note]](../../../../../doc/src/images/note.png) | Note | 
|---|---|
| This is an experimental feature. | 
          The experimental::coro
          class provides support for a universal C++20 coroutine. These coroutines
          can be used as tasks, generators and transfomers, depending on their signature.
        
coro<std::string_view> line_reader(tcp::socket stream)
{
   while (stream.is_open())
   {
     std::array<char, 4096> buf;
     auto read = co_await stream.async_read_some(
         boost::asio::buffer(buf), experimental::use_coro);
     if (read == 0u)
       continue;
     co_yield std::string_view { buf.data(), read };
   }
}
coro<void, std::size_t> line_logger(tcp::socket stream)
{
  std::size_t lines_read = 0u;
  auto reader = line_reader(std::move(stream));
  while (auto l = co_await reader)
  {
    std::cout << "Read: '" << *l << "'" << std::endl;
    lines_read++;
  }
  co_return lines_read;
}
void read_lines(tcp::socket sock)
{
  co_spawn(line_logger(std::move(sock),
      [](std::exception_ptr, std::size_t lines)
      {
        std::clog << "Read " << lines << " lines" << std::endl;
      }));
}
          A coro
          is highly configurable, so that it can cover a set of different use cases.
        
template<
    typename Yield,
    typename Return = void,
    typename Executor = any_io_executor>
struct coro;
          The Yield parameter designates how a co_yield
          statement behaves. It can either be a type, like int or a
          signature with zero or one types:
        
coro<void> // A coroutine with no yield coro<int> // A coroutine that can yield int coro<void()> // A coroutine with no yield coro<int()> // A coroutine that can yield int coro<int(double)> // A coroutine that can yield int and receive double
          Receiving a value means that the co_yield statement returns
          a value.
        
coro<int(int)> my_sum(any_io_executor)
{
  int value = 0;
  while (true)
    value += co_yield value; //sum up all values
}
Putting values into a coroutine can be done it two ways: either by direct resumption (from another coro) or through async_resume. The first value gets ignored because the coroutines are lazy.
coro<void> c(any_io_executor exec)
{
  auto sum = my_sum(exec);
  assert(0  == co_await sum(-1));
  assert(0  == co_await sum(10));
  assert(10 == co_await sum(15));
  assert(25 == co_await sum(0));
}
awaitable<void> a()
{
  auto sum = my_sum(co_await this_coro::executor);
  assert(0  == co_await sum.async_resume(-1, use_awaitable));
  assert(0  == co_await sum.async_resume(10, use_awaitable));
  assert(10 == co_await sum.async_resume(15, use_awaitable));
  assert(25 == co_await sum.async_resume(0, use_awaitable));
}
noexcept
        A coro may be noexcept:
coro<void() noexcept> c; coro<int() noexcept> c; coro<int(double) noexcept> c;
          This will change its @c async_resume signature, from void(std::exception_ptr)
          to void() or void(std::exception_ptr, T) to
          void(T). A noexcept coro that ends with an exception will
          cause std::terminate to be called.
        
          Furthermore, calls of async_resume and co_await
          of an expired noexcept coro will cause undefined behaviour.
        
          A coro can also define a type that can be used with co_return:
        
coro<void() noexcept, int> c(any_io_executor)
{
  co_return 42;
}
          A coro can have both a Yield and Return that
          are non void at the same time.
        
          The result type of a coroutine is dermined by both Yield and
          Return. Note that in the follwing table only the yield output
          value is considered, i.e. T(U) means T.
        
Table 2. :result_type Result type deduction
| Yield | Return | 
                     | 
                     | 
                     | 
|---|---|---|---|---|
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
| 
                     | 
                     | 
                     | 
                     | 
                     | 
          Every coroutine needs to have its own executor. Since the coroutine gets
          called multiple times, it cannot take the executor from the caller like
          an awaitable. Therefore a coro requires to get
          an executor or an execution_context passed in as the first parameter.
        
coro<int> with_executor(any_io_executor); coro<int> with_context(io_context &);
          It is to note, that an execution_context is defined as loosely as possible.
          An execution context is any object that has a get_executor()
          function, which returns an executor that can be transformed into the executor_type
          of the coroutine. This allows most io_objects to be used as the source
          of the executor:
        
coro<int> with_socket(tcp::socket);
          Additionally, a coro that is a member function will check
          the this pointer as well, either if it's an executor or an
          execution context:
        
struct my_io_object
{
  any_io_executor get_executor();
  coro<int> my_coro();
};
Finally, a member coro can be given an explicit executor or execution context, to override the one of the object:
struct my_io_object
{
  any_io_executor get_executor();
  coro<int> my_coro(any_io_executor exec); // it will use exec
};
co_await
        
          The @c co_await within a coro is not the same as async_resume(use_coro),
          unless both coros use different executors. If they use the same, the coro
          will direclty suspend and resume the executor, without any usage of the
          executor.
        
          co_await this_coro:: behaves the same as coroutines that use
          @c boost::asio::awaitable.
        
          As the coro member function async_resume is an
          asynchronous operation, it may also be used in conjunction with awaitable
          coroutines in a single control flow. For example:
        
#include <asio.hpp>
#include <boost/asio/experimental/coro.hpp>
using boost::asio::ip::tcp;
boost::asio::experimental::coro<std::string> reader(tcp::socket& sock)
{
  std::string buf;
  while (sock.is_open())
  {
    std::size_t n = co_await boost::asio::async_read_until(
        sock, boost::asio::dynamic_buffer(buf), '\n',
        boost::asio::experimental::use_coro);
    co_yield buf.substr(0, n);
    buf.erase(0, n);
  }
}
boost::asio::awaitable<void> consumer(tcp::socket sock)
{
  auto r = reader(sock);
  auto msg1 = co_await r.async_resume(boost::asio::use_awaitable);
  std::cout << "Message 1: " << msg1.value_or("\n");
  auto msg2 = co_await r.async_resume(boost::asio::use_awaitable);
  std::cout << "Message 2: " << msg2.value_or("\n");
}
boost::asio::awaitable<void> listen(tcp::acceptor& acceptor)
{
  for (;;)
  {
    co_spawn(
        acceptor.get_executor(),
        consumer(co_await acceptor.async_accept(boost::asio::use_awaitable)),
        boost::asio::detached);
  }
}
int main()
{
  boost::asio::io_context ctx;
  tcp::acceptor acceptor(ctx, {tcp::v4(), 54321});
  co_spawn(ctx, listen(acceptor), boost::asio::detached);
  ctx.run();
}
co_spawn, experimental::coro, C++20 Coroutines, Stackful Coroutines, Stackless Coroutines.