Title
`schedule_from` isn't starting the schedule sender if decay-copying results throws
Status
ready
Section
[exec.schedule.from]
Submitter
Eric Niebler

Created on 2025-02-03.00:00:00 last changed 2 months ago

Messages

Date: 2025-02-13.17:25:31

Proposed resolution:

This wording is relative to N5001.

  1. Modify [exec.schedule.from] p8 as indicated:

    -8- 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 type decayed-tuple<Tag, Args...>, and let is-nothrow-decay-copy-sig be a variable template such that auto(is-nothrow-decay-copy-sig<Tag(Args...)>) is a constant expression of type `bool` and equal to (is_nothrow_constructible_v<decay_t<Args>, Args> && ...). 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.

  2. Modify [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);
    };
    
Date: 2025-02-13.17:25:31

[ Hagenberg 2025-02-12; move to Ready ]

Date: 2025-02-12.00:00:00

[ 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.

Date: 2025-02-11.15:11:16

[ Hagenberg 2025-02-11; LWG ]

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

Date: 2025-02-15.00:00:00

[ 2025-02-07; Reflector poll ]

Set priority to 1 after reflector poll.

Date: 2025-02-03.16:56:11

[ This touches the same text as LWG 4203. ]

Date: 2025-02-11.15:32:32

Imported from cplusplus/sender-receiver #304.

[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.

History
Date User Action Args
2025-02-13 17:25:31adminsetmessages: + msg14640
2025-02-13 17:25:31adminsetstatus: new -> ready
2025-02-12 06:38:49adminsetmessages: + msg14639
2025-02-12 06:38:49adminsetmessages: + msg14638
2025-02-11 15:11:16adminsetmessages: + msg14631
2025-02-07 22:17:52adminsetmessages: + msg14610
2025-02-03 16:56:11adminsetmessages: + msg14563
2025-02-03 00:00:00admincreate