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