Date: Tue, 1 Oct 2024 09:13:13 -0400
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
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
Received on 2024-10-01 13:13:31