This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Ready status.
schedule_from
isn't starting the schedule sender if decay-copying results throwsSection: 33.9.12.5 [exec.schedule.from] Status: Ready Submitter: Eric Niebler Opened: 2025-02-03 Last modified: 2025-02-13
Priority: 1
View other active issues in [exec.schedule.from].
View all other issues in [exec.schedule.from].
View all issues with Ready status.
Discussion:
Imported from cplusplus/sender-receiver #304.
33.9.12.5 [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
:
LetSigs
be a pack of the arguments to thecompletion_signatures
specialization named bycompletion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>
. Letas-tuple
be 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-sig
be a variable template such thatis-nothrow-decay-copy-sig<Tag(Args...)>
is a core constant expression of typebool const
and whose value istrue
if the typesArgs...
are all nothrow decay-copyable, andfalse
otherwise. Leterror-completion
be a pack consisting of the typeset_error_t(exception_ptr)
if(is-nothrow-decay-copy-sig<Sigs> &&...)
isfalse
, and an empty pack otherwise. Thenvariant_t
denotes the typevariant<monostate, as-tuple<Sigs>..., error-completion...>
, except with duplicate types removed.
[This touches the same text as LWG 4203(i).]
[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]
Proposed resolution:
This wording is relative to N5001.
-8- Let
Sigs
be a pack of the arguments to thecompletion_signatures
specialization named bycompletion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>
. Letas-tuple
be 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-sig
be a variable template such thatauto(is-nothrow-decay-copy-sig<Tag(Args...)>)
is a constant expression of typebool
and equal to(is_nothrow_constructible_v<decay_t<Args>, Args> && ...)
. Leterror-completion
be a pack consisting of the typeset_error_t(exception_ptr)
if(is-nothrow-decay-copy-sig<Sigs> &&...)
isfalse
, and an empty pack otherwise. Thenvariant_t
denotes the typevariant<monostate, as-tuple<Sigs>..., error-completion...>
, except with duplicate types removed.
Modify 33.9.12.5 [exec.schedule.from] p11 as indicated:
-11- The member
impls-for<schedule_from_t>::complete
is 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); };