4198. schedule_from isn't starting the schedule sender if decay-copying results throws

Section: 33.9.12.5 [exec.schedule.from] Status: New Submitter: Eric Niebler Opened: 2025-02-03 Last modified: 2025-02-11

Priority: 1

View other active issues in [exec.schedule.from].

View all other issues in [exec.schedule.from].

View all issues with New 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:
Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>. Let as-tuple be an alias template that transforms a completion signature Tag(Args...) into the tuple specialization decayed-tuple<Tag, Args...>. such that as-tuple<Tag(Args...)> denotes the tuple specialization decayed-tuple<Tag, Args...>, and let is-nothrow-decay-copy-sig be a variable template such that is-nothrow-decay-copy-sig<Tag(Args...)> is a core constant expression of type bool const and whose value is true if the types Args... are all nothrow decay-copyable, and false otherwise. Let error-completion be a pack consisting of the type set_error_t(exception_ptr) if (is-nothrow-decay-copy-sig<Sigs> &&...) is false, and an empty pack otherwise. Then variant_t denotes the type variant<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-10; LWG]

Direction seems right. Decay-copyable is not a defined term.

Proposed resolution: