What is the benefit of `await_ready` in C++ coroutine awaiters

Viewed 368

In C++ coroutines, awaiters-types (i. e. argument-types of co_await and return types of initial_suspend/final_suspend) require the three methods await_ready(), await_suspend(std::coroutine_handle<...>) and await_resume(). I am aware of the order of invocation1 in case of suspension/resumption of a coroutine.

But it is unclear to me what is the rationale of demanding the distinct await_ready-method. When returning true invocation of await_suspend is prevented (and with it suspension altogether). However, the same effect is achievable by simply returning true from await_suspend.

In this blog article the author David Mazières says

await_ready is an optimization. If it returns true, then co_await does not suspend the function. Of course, you could achieve the same effect in await_suspend, by resuming (or not suspending) the current coroutine, but before calling await_suspend, the compiler must bundle all state into the heap object referenced by the coroutine handle, which is potentially expensive.

However, to me this does not seem to be a valid argument: The (possibly heap-)allocated coroutine state needs to be created before promise_type::get_return_object() is invoked which is always invoked, since the promies_type-instance is part of the coroutine state. Therefore the coroutine state referenced by the handle is created anyway, regardless of whether we will invoke await_suspend or not.

Back to the question putting my thougths into code: I do not see in which case the rewriting of

struct awaiter
{
  bool await_ready() { /* <arbitrary-await-ready-body> */ }
  void /*| bool*/ await_suspend(std::coroutine_handle<...>) { /* <arbitrary-await-suspend-body> */ }

  <any> await_resume() { ... }
};

to

struct awaiter
{
  bool await_ready() { return false; } // always false
  bool await_suspend(std::coroutine_handle<...>)
  {
    if (await_ready_result()) // effectively moved await_ready into await_suspend
      return false;
    /* <arbitrary-await-suspend-body> */
    return true;
  }

  <any> await_resume() { ... }
private:
  bool await_ready_result() { /* <arbitrary-await-ready-body> */ }; // could just be "in-lined" into await_suspend
};

would not be applicable. In the latter case awaiter_ready is obsolete without giving away any flexibility2. So what is the use of distinct awaiter_ready at all?

One thing that came to my mind was that await_ready could be kept noexcept when await_suspend's implementation is arbitrarily complex, which might allow for some optimization but that seems overly specific to be the actual rationale.


1: See for example the pseudo code block in section The Awaiter Workflow of article https://www.modernescpp.com/index.php/a-generic-data-stream-with-coroutines-in-c-20

2: I neglect the case of await_suspend returning some std::coroutine_handle, mainly because I don't see the particular use of that either. After all you could simply just call some_handle.resume() as last statement instead of returning it and instead return void/true. Feel free to enlighten me about that one as well.

0 Answers
Related