Suppose I have two coroutines, coroutine_a
and coroutine_b
. In coroutine_a
it calls:
co_await awaiter_a;
The awaiter_a::await_suspend
returns a coroutine handler of coroutine_b
. As a result, coroutine_b
is resumed. Then, in coroutine_b
, it calls:
co_await awaiter_b;
The awaiter_b::await_suspend
returns void. As a result, control should be returned to its caller/resumer coroutine_a
. What will happen when control is returned to coroutine_a
?
I think there are two options:
coroutine_a
returns to its caller.coroutine_a
stays suspended until someone resumes it.Which one is correct? Or is it something else?
This is explained by [expr.await]/5:
The await-expression evaluates the (possibly-converted) o expression and the await-ready expression, then:
If the result of await-ready is
false
, the coroutine is considered suspended. Then:
- If the type of await-suspend is
std::coroutine_handle<Z>
, await-suspend.resume()
is evaluated.
[Note 1: This resumes the coroutine referred to by the result of await-suspend. Any number of coroutines can be successively resumed in this fashion, eventually returning control flow to the current coroutine caller or resumer ([dcl.fct.def.coroutine]). — end note]- Otherwise, if the type of await-suspend is
bool
, await-suspend is evaluated, and the coroutine is resumed if the result isfalse
.- Otherwise, await-suspend is evaluated.
If the evaluation of await-suspend exits via an exception, the exception is caught, the coroutine is resumed, and the exception is immediately rethrown ([except.throw]). Otherwise, control flow returns to the current coroutine caller or resumer ([dcl.fct.def.coroutine]) without exiting any scopes ([stmt.jump]). The point in the coroutine immediately prior to control returning to its caller or resumer is a coroutine suspend point.
If the result of await-ready is
true
, or when the coroutine is resumed other than by rethrowing an exception from await-suspend, the await-resume expression is evaluated, and its result is the result of the await-expression.
So, co_await awaiter_a
first suspends coroutine A, then goes to the first subbullet and calls .resume()
on the handle for coroutine B. When coroutine B is suspended and returns control to coroutine A, then we've just finished the second subbullet in coroutine A, which means we go to the paragraph at the end of the first bullet, and control flow returns to the caller or resumer of coroutine A.
(Note 1 also explains this. Returning a coroutine handle from await-suspend basically just inserts a coroutine at the front of the queue to be resumed before the coroutine that would otherwise be resumed if await-suspend were of type void
.)