Hello list,

Right now [exec.sync.wait] item 1 says “`sync_wait` mandates that the input sender has exactly one value completion signature.” This seems overly restrictive for several reasons:

1. Teachability: Given `sync_wait(just())` evaluates to `std::optional<std::tuple<>>`, where `std::nullopt` indicates stoppedness and throwing indicates failure, a learner would expect that `sync_wait(just_stopped()) == std::nullopt` and that `sync_wait(just_error(e))` would have the same type but throw.

2. Main loop: In production code, I could see a `main()` function that ends in `return async_wait(snd).has_value() ? 0 : 1;`, where `snd` is essentially an infinite loop that can only fail or be stopped.
3. Genericity: By requiring exactly one value completion signature, that makes the value channel special. Arguably it is special, but does it need to be this special? And in the main-loop case, if a refactor makes `snd` never return a value, users are forced to change the `retrurn` line.

Possible solutions:

  1. If there is no value channel, return as though the sender sends void (so `std::optional<std::tuple<>>`). This is basically what it already does for `sync_wait(just()) -> std::optional<std::tuple<>>` where that optional is never not engaged even though statically we could know that the sender is unstoppable.

  2. Return using an (unspecified?) unconstructible type in place of the tuple, so `sync_wait(just_stopped()) -> std::optional<__unconstructible>`. This has the advantage of statically exposing the fact that there will never be any value returned.


Of these, I prefer (a) because it has the advantage of beginner-friendlyness and not being surprising. It is consistent with `sync_wait(just())` which isn’t `noexcept` (even though the sender can’t error) and returns optional even though it can’t be stopped. If advanced users want (b), then they can easily write their own that propagates non-error to `noexcept`, propagates non-stopping to non-optional (or an optional-like that is statically always engaged), propagates error-only as `[[noreturn]]`, or that, like the standard has it now, requires the sender have a value completion.


As for `sync_wait_with_variant(just_stopped())`, I think `std::optional<std::variant<>>` makes a lot of sense given `std::variant<>` is an unconstructible type indicating no value channel.


Do you think this is a defect that should be addressed?

Cheers,
Ben