C++ Logo

std-proposals

Advanced search

Re: [std-proposals] defect report: coexisting return_value and return_void in coroutines

From: Avi Kivity <avi_at_[hidden]>
Date: Sun, 27 Nov 2022 20:30:25 +0200
On Sun, 2020-10-11 at 18:56 +0300, Avi Kivity wrote:
> On 10/11/20 6:22 PM, Jason McKesson via Std-Proposals wrote:
> > It seems to me that what you want is the equivalent of a proper
> > tail
> > call for coroutines. That's a valid thing to want.
> >
> > But you shouldn't co-opt a mechanism like `return_value` to gain
> > this
> > functionality just because it's convenient. C++ doesn't let you use
> > `return <expr>` unless your function actually returns that value.
> > The
> > same ought to be true of `co_return <expr>`. If your coroutine is
> > not
> > actually returning the result of `<expr>`, then you shouldn't
> > express
> > it as `co_return`. And if your promise has a `return_value`
> > function,
> > then that function ought to do what it says: return a value of that
> > type through its future.
> >
> > Let's not take mechanisms and make them do weird things just
> > because
> > it's convenient. You can build what you want without a new keyword
> > just by employing a bit of indirection:
> >
> > ```
> > co_await tail_continue(yet_another());
> > co_return;
> > ```
> >
> > Where `tail_continue` is a type that stores the future returned by
> > `yet_another`. `co_await`ing on it will not suspend execution.
> > Instead, it does what your hypothetical call to `return_value`
> > would
> > do: change the promise to use the given future.
>
>
> Interesting idea. I agree with the general sense of requiring
> regularity
> in co_return return types.
>
>
> Your alternative implementation is nice (and easy to implement in my
> use-case), but gives up something: the uniqueness of the return
> statement. I can write
>
>
> co_await tail_continue(yet_another());
> co_await tail_continue(yet_another());
> co_return;
>
>
> And it can only be detected at run-time that I violated a constraint
> that there can be only one tail-call leading to a co_return (and
> another, not shown here, that it's illegal to co_await anything once
> you've co_awaited a tail_continue).
>
>
> But, I think that those problems are smaller than the one in my
> proposed
> fix.
>


I take it back. I just discovered another case where allowing
return_void and return_value to coexist, and the workaround caused a
subtle bug.


My other use case is to use return_value to return an exception object
without throwing it. Since I can't use return_value in a coroutine
returning void, I have to use co_await:

```
future<> f() {
    if (error) {
         co_await return_exception(std::runtime_error("blah"));
         // no co_return, awaiter destroys handle
    }
    co_await do_something();
    // implicit co_return
}
```

This looks sort-of-okay, but if the coroutine has a RAII object, it
silently stops working:

```
future<> f() {
    raii_object obj;
    if (error) {
        co_await return_exception(std::runtime_error("blah"));
        // no co_return, awaiter destroys handle
        // OOPS! obj destructor not called
    }
    co_await do_something();
    // implicit co_return
}
```

Of course, I could require co_return to be called after co_await
return_exception, but nothing enforces it, and it requires that I
allocate a variable in the promise object to communicate between
return_exception's awaiter and co_return.


So I do want return_void and return_value to coexist.

Received on 2022-11-27 18:30:30