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:
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.
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?