C++ Logo

std-proposals

Advanced search

[std-proposals] Coroutines: Add explicit initialization by "co_init" operator

From: Stefan Sichler <stsichler_at_[hidden]>
Date: Wed, 9 Aug 2023 17:05:53 +0200
Hi,

I recently tried to add C++20 coroutine support to the I/O framework code of
the company I'm working at and ran into very specific problems with the
current implementation.

There are also several oddities that literally instantly caught my eye, so
I'd like to share my thoughts here:





* Motivation



- There is currently no way to implement a well-defined initialization of
the promise_type from within the Coroutine's implementation body (and pass
some parameters to the promise_type constructor).

- When looking through code, it is not directly obvious whether a function
is a coroutine or not, because for deciding that, all of the function body
has to be searched for a call to co_await, co_yield or co_return operators.
This is very odd, because the compiler needs to add initialization code
right at the very beginning of the function body anyway, if the function is
a Coroutine.

- The return type of a Coroutine is forced to expose "promise_type" as part
of the Coroutine signature, because it needs to be defined in its return
type, but this should actually be an implementation detail of the Coroutine
and thus should not be directly visible in the signature.

- The promise_type cannot have a member field of type of the return type,
because the promise_type needs to be declared inside the return type, so the
type definition of the return type is incomplete at that point.

- The return object is created by promise_type::get_return_object() *before*
the Coroutine implementation body had any chance of initializing the promise
object by any means. (this was a major problem in my case that a had to work
around in very ugly ways!)





* My Proposal



*All* of these points could be resolved by removing the requirement of
defining a type "promise_type" in the return type of a Coroutine and instead
adding a mandatory explicit coroutine initialization call within the
Coroutine's implementation body, let's say a call to some new "co_init"
operator.

The return type of the expression passed to the co_init operator shall then
define the promise type (and the promise object could be move-constructed
from the return value of this expression).



So, a coroutine body may then look like this:



return_type my_coroutine(...)

{

  co_init make_my_cool_promise(args...);

  ...

  co_await...

  ...

}



or (perhaps an even better syntax?):



return_type my_coroutine(...)

  : co_init(make_my_cool_promise(args...))

{

  ...

  co_await...

  ...

}





This approach has several benefits:



- By forcing the co_init operator to be right at the beginning of the
function body, one could clearly see that this function is actually a
coroutine instead of a traditional function, so the compiler would instantly
know that it has to inject specific initialization code right here.

- There would be no more correlation required between the return type of the
Coroutine and its promise type, so one could use standard types like
std::any or similar as return types to implement interfaces that hide the
implementation detail of a function being a coroutine or not (which was
required in what I tried to do).

- One could also be able to implement Coroutines with different promise
types returning identical return types which is currently AFAIK not
possible.

- The coroutine implementation would now have a very simple way to influence
the creation of the return object: it now explicitly initializes the promise
object by move-construction from the return expression of the co_init
operator *before* the return object is created by a call to the promise
object's get_return_object() method. This is straight forward!

- The type name "promise_type" would no longer need to be a reserved type
name, because the name of the promise type would no longer matter - this
feels like a much cleaner approach than the current implementation.

- Coroutine Templates would be easy to achieve, because this approach allows
templated promise types, depending on the template parameters of the
Coroutine.





* Outlook



Coroutines, defined the way they are currently, are restricted to the body
of a *single* function, meaning that there is currently no way to co_await
in a sub-function called from the Coroutine and cause suspension of the
calling Coroutine by this.

But even this limitation can be overcome by an explicit co_init operator,
because it also clearly defines the point to return to when the Coroutine is
suspended. So now, when a sub-function is called from a Coroutine which is
itself _not_ a Coroutine (because it misses co_init), but somewhere a
co_await operator is encountered, then the *calling* Coroutine - if any -
could be suspended, otherwise the call would be illegal. Of course, this
would require a try/catch/throw-like compiler support.





So, what do you think?



I'm sorry that my proposal isn't clearly technically worked out now, and I'm
sure that it requires major refinements from this point on, but wouldn't
this actually be a very useful and reasonable modification of the current
Coroutines specification?



Best Regards

Stefan




Received on 2023-08-09 15:05:56