schedule_from isn't starting the schedule sender if decay-copying results throwsSection: 33.9.12.7 [exec.schedule.from] Status: WP Submitter: Eric Niebler Opened: 2025-02-03 Last modified: 2025-06-23
Priority: 1
View all other issues in [exec.schedule.from].
View all issues with WP status.
Discussion:
Imported from cplusplus/sender-receiver #304.
33.9.12.7 [exec.schedule.from]/p11 specifies schedule_from's completion operation as follows:
[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept
-> void {
using result_t = decayed-tuple<Tag, Args...>;
constexpr bool nothrow = is_nothrow_constructible_v<result_t, Tag, Args...>;
try {
state.async-result.template emplace<result_t>(Tag(), std::forward<Args>(args)...);
} catch (...) {
if constexpr (!nothrow) {
set_error(std::move(rcvr), current_exception());
return;
}
}
start(state.op-state);
};
so if emplacing the result tuple throws, set_error is immediately called on the downstream receiver. no attempt is made to post the completion to the specified scheduler. this is probably not the right behavior.
Suggested resolution
The right thing, i think, is to catch the exception, emplace the exception_ptr into the async-result variant, and then start the schedule operation, as shown below:
[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept
-> void {
using result_t = decayed-tuple<Tag, Args...>;
constexpr bool nothrow = is_nothrow_constructible_v<result_t, Tag, Args...>;
try {
state.async-result.template emplace<result_t>(Tag(), std::forward<Args>(args)...);
} catch(...) {
if constexpr (!nothrow)
state.async-result.template emplace<tuple<set_error_t, exception_ptr>>(set_error, current_exception());
}
start(state.op-state);
}
we also need to change how we specify the variant type of
state.async-result:
LetSigsbe a pack of the arguments to thecompletion_signaturesspecialization named bycompletion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>. Letas-tuplebe an alias templatethat transforms a completion signaturesuch thatTag(Args...)into the tuple specializationdecayed-tuple<Tag, Args...>.as-tuple<Tag(Args...)>denotes the tuple specializationdecayed-tuple<Tag, Args...>, and letis-nothrow-decay-copy-sigbe a variable template such thatis-nothrow-decay-copy-sig<Tag(Args...)>is a core constant expression of typebool constand whose value istrueif the typesArgs...are all nothrow decay-copyable, andfalseotherwise. Leterror-completionbe a pack consisting of the typeset_error_t(exception_ptr)if(is-nothrow-decay-copy-sig<Sigs> &&...)isfalse, and an empty pack otherwise. Thenvariant_tdenotes the typevariant<monostate, as-tuple<Sigs>..., error-completion...>, except with duplicate types removed.
[This touches the same text as LWG 4203.]
[2025-02-07; Reflector poll]
Set priority to 1 after reflector poll.
[Hagenberg 2025-02-11; LWG]
Direction seems right. Decay-copyable is not a defined term.
[2025-02-12 Tim adds PR]
This also corrects the issue that nothrow is currently relying
on the unspecified exception specification of tuple's constructor.
[Hagenberg 2025-02-12; move to Ready]
[Sofia 2025-06-21; Status changed: Voting → WP.]
Proposed resolution:
This wording is relative to N5001.
-8- Let
Sigsbe a pack of the arguments to thecompletion_signaturesspecialization named bycompletion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>. Letas-tuplebe an alias templatethat transforms a completion signaturesuch thatTag(Args...)into the tuple specializationdecayed-tuple<Tag, Args...>.as-tuple<Tag(Args...)>denotes the typedecayed-tuple<Tag, Args...>, and letis-nothrow-decay-copy-sigbe a variable template such thatauto(is-nothrow-decay-copy-sig<Tag(Args...)>)is a constant expression of typebooland equal to(is_nothrow_constructible_v<decay_t<Args>, Args> && ...). Leterror-completionbe a pack consisting of the typeset_error_t(exception_ptr)if(is-nothrow-decay-copy-sig<Sigs> &&...)isfalse, and an empty pack otherwise. Thenvariant_tdenotes the typevariant<monostate, as-tuple<Sigs>..., error-completion...>, except with duplicate types removed.
Modify 33.9.12.7 [exec.schedule.from] p11 as indicated:
-11- The member
impls-for<schedule_from_t>::completeis initialized with a callable object equivalent to the following lambda:[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void { using result_t = decayed-tuple<Tag, Args...>; constexpr bool nothrow =is_nothrow_constructible_v<result_t, Tag, Args...>(is_nothrow_constructible_v<decay_t<Args>, Args> && ...); try { state.async-result.template emplace<result_t>(Tag(), std::forward<Args>(args)...); } catch (...) { if constexpr (!nothrow){state.async-result.template emplace<tuple<set_error_t, exception_ptr>>(set_error, current_exception());set_error(std::move(rcvr), current_exception());return;}} start(state.op-state); };