On Fri, 2025-09-12 at 11:48 -0400, Jason McKesson via Std-Proposals wrote:
On Fri, Sep 12, 2025 at 11:40 AM Avi Kivity <avi@scylladb.com> wrote:

On Thu, 2025-09-11 at 17:27 -0400, Jason McKesson via Std-Proposals wrote:

On Thu, Sep 11, 2025 at 7:21 AM Avi Kivity via Std-Proposals
<std-proposals@lists.isocpp.org> wrote:


The Standard says

If searches for the names return_void and return_value in the scope

of the promise type each find any declarations, the program is ill-
formed.

However, there are cases where one wants both "co_return;" and
co_return something;" statements in the same coroutine.

The cases are when the coroutine returns a sum type such as std::future
or std::expected, and one of the options is void (e.g.
std::future<void> or std::expected<void, something>. In these cases,
one would want


  co_return;

to select the void branch of the sum type

and

  co_return std::make_exception_ptr(...) // for std::future<void>

or

  co_return something(...)  // for std::expected<void, something>.


to select the non-void branch.

Early versions of gcc did not error when both return_value() and
return_void() were defined; no problems were observed.


From a conceptual perspective, `return` and `co_return` should behave
similarly. As it currently stands, any function for which `return
<expr>;` is valid is one where `return;` would not be valid. So it
conceptually makes sense that `co_return` would behave the same way.

That having been said, there's a reasonable argument to be made that
the behavior of `co_return` is already fairly divergent from `return`.
Of particular note is the fact that the relationship between the
returned expression type (if any) and the function's apparent return
type is not nearly as clear as it is for `return`. You may have to dig
deeply into the coroutine machinery to figure out exactly what type
you need to provide, if any.

Also, there's the fact that coroutines can already have *multiple*
different "return types": the type you can put into `co_return`. So
you can already do `co_return X;` and `co_return Y;` where those two
types have no conversion relationship between them or with some other
third type. Adding `co_return;` to that list seems pretty reasonable,
even if it does call a differently named function internally.




Even for plain `return`, sum types make the case for both return; and return expr; in the same function.

A function f that returns std::expected<T, E> can "return T();" or it can "return std::unexpected(E());".

That's made possible via implicit conversions with a known return
type. Try that in a deduced return function and it will fail.


But I'm trying it in a function that explicitly return std::expected<T, E>.



A function g that returns std::unexpected<void, E> cannot do the analogous and "return;" for the good case and "return std::unexpected(E());" for the bad case.

And it shouldn't. `return;` should mean "exit without returning a value".



That's exactly what a return type of std::expected<void, E> conveys. Either we return with no value, or we return with an unexpected value.

We have to emulate no value with {} or with std::expected<void, E>(). Given low usage of std::expected so far and the low cost of returning {}, that's bearable. To be clear I'm not advocating a change to "return" and if someone points to this thread I will claim a cat walked over my keyboard.

However, future<void> is very common in async code, and the ability to either co_return; or co_return an exception_ptr is very desirable, and low cost to implement.