From cffa423981fb1aa4a7923b801c7e1237b262319b Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Fri, 15 Dec 2023 08:01:23 -0800 Subject: [PATCH] port the specification of the `schedule_from` algorithm to `basic-sender` (#163) fixes #145 --- execution.bs | 169 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 55 deletions(-) diff --git a/execution.bs b/execution.bs index 4ed717f..44dc99b 100644 --- a/execution.bs +++ b/execution.bs @@ -4897,19 +4897,21 @@ enum class forward_progress_guarantee { 5. For two subexpressions `rcvr` and `expr`, let SET-VALUE(rcvr, expr) be `(expr, set_value(rcvr))` if the type of `expr` is `void`; - otherwise, it is `set_value(rcvr, expr)`. Let TRY-SET-VALUE(rcvr, - expr) be: + otherwise, it is `set_value(rcvr, expr)`. Let + TRY-EVAL(rcvr, expr) be:
             try {
-              SET-VALUE(rcvr, expr);
+              expr;
             } catch(...) {
               set_error(rcvr, current_exception());
             }
             
- if `expr` is potentially-throwing, except that `rcvr` is evaluated only once; - or SET-VALUE(rcvr, expr) otherwise. + if `expr` is potentially-throwing; otherwise, `expr`. Let + TRY-SET-VALUE(rcvr, expr) be + TRY-EVAL(rcvr, SET-VALUE(rcvr, expr)) + except that `rcvr` is evaluated only once. 6.
         template<class Default = default_domain, class Sndr>
@@ -5142,6 +5144,12 @@ enum class forward_progress_guarantee {
                   ...
                 Childn-1 childn-1;   // exposition only
               };
+
+              template <class Sndr>
+              using data-type = decltype((declval<Sndr>().data)); // exposition only
+
+              template <class Sndr, size_t N = 0>
+              using child-type = decltype((declval<Sndr>().childN)); // exposition only
               
2. It is unspecified whether instances of basic-sender can be @@ -5177,7 +5185,7 @@ enum class forward_progress_guarantee { with a callable object equal to the following lambda:
-              []<class Sndr>(Sndr&& sndr, auto& rcvr) noexcept -> decltype(auto) {
+              []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) {
                 return get<1>(std::forward<Sndr>(sndr));
               }
               
@@ -6029,74 +6037,125 @@ template<class Domain, class Tag, sender Sndr, class... Args> class="wg21note">`schedule_from` is not meant to be used in user code; it is used in the implementation of `transfer`. -3. The name `schedule_from` denotes a customization point object. For some +2. The name `schedule_from` denotes a customization point object. For some subexpressions `sch` and `sndr`, let `Sch` be `decltype((sch))` and `Sndr` be `decltype((sndr))`. If `Sch` does not satisfy `scheduler`, or `Sndr` does not - satisfy `sender`, `schedule_from` is ill-formed. Otherwise, the expression - `schedule_from(sch, sndr)` is expression-equivalent to: + satisfy `sender`, `schedule_from` is ill-formed. + +3. Otherwise, the expression `schedule_from(sch, sndr)` is expression-equivalent + to:
         transform_sender(
           query-or-default(get_domain, sch, default_domain()),
-          make-schedule-from-sender(sch, sndr));
+          make-sender(schedule_from, sch, sndr));
         
- where make-schedule-from-sender(sch, sndr) is expression-equivalent to - make-sender(schedule_from, sch, sndr) and returns a sender object - `sndr2` that behaves as follows: - 1. When `sndr2` is connected with some receiver `out_rcvr`, it: +4. The exposition-only class template impls-for + ([exec.snd.general]) is specialized for `schedule_from_t` as + follows: - 1. Constructs a receiver `rcvr` such that when a receiver completion - operation Tag(rcvr, args...) is called, it - decay-copies `args...` into `op_state` (see below) as `args2...` and - constructs a receiver `rcvr2` such that: +
+        template<>
+        struct impls-for<tag_t<schedule_from_t> : default-impls {
+          static constexpr auto get-attrs = see below;
+          static constexpr auto get-state = see below;
+          static constexpr auto complete = see below;
+        };
+        
- 1. When `set_value(rcvr2)` is called, it calls - Tag(out_rcvr, std::move(args2)...). + 1. The member impls-for<schedule_from_t>::get-attrs is initialized + with a callable object equal to the following lambda: - 2. `set_error(rcvr2, err)` is expression-equivalent to `set_error(out_rcvr, err)`. +
+          [](const auto& data, const auto& child) noexcept -> decltype(auto) {
+            return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));
+          }
+          
+ + 2. The member impls-for<schedule_from_t>::get-state is initialized + with a callable object equal to the following lambda: + +
+          []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr)
+              requires sender_in<child-type<Sndr>, env_of_t<Rcvr>> {
+            return apply(
+              [&]<class Sch, class Child>(auto, Sch sch, Child&& child) {
+                using variant-type = see below;
+                using receiver-type = see below;
+
+                struct state-type {
+                  Rcvr& rcvr;
+                  variant-type async_result;
+                  connect_result_t<schedule_result_t<Sch>, receiver-type> op_state2;
+
+                  explicit state-type(Sch sch, Rcvr& rcvr)
+                    : rcvr(rcvr), op_state2(connect(schedule(sch), receiver-type{{}, this})) {}
+                };
+                return state-type{sch, rcvr};
+              },
+              std::forward<Sndr>(sndr));
+          }
+          
+ + 1. Let `Sigs` be a pack of the arguments to the + `completion_signatures` specialiation named by + `completion_signatures_of_t>`. Let + `as-tuple` be an alias template that transforms a + completion signature `Tag(Args...)` into the `tuple` + specialization decayed-tuple<Tag, Args...>. + Then `variant-type` names the type + variant<monostate, as-tuple<Sigs>...>. - 3. `set_stopped(rcvr2)` is expression-equivalent to `set_stopped(out_rcvr)`. + 2. Let `receiver-type` denote the following class: - 4. `get_env(rcvr2)` is equal to `get_env(rcvr)`. +
+                struct receiver-type : receiver_adaptor<receiver-type> {
+                  state-type* state;
+
+                  Rcvr&& base() && noexcept { return std::move(state->rcvr); }
+                  const Rcvr& base() const & noexcept { return state->rcvr; }
+
+                  void set_value() && noexcept {
+                    visit(
+                      [this]<class Tuple>(Tuple& result) noexcept -> void {
+                        if constexpr (!same_as<monostate, Tuple>) {
+                          auto& [tag, ...args] = result;
+                          tag(std::move(state->rcvr), std::move(args)...);
+                        }
+                      },
+                      state->async_result);
+                  }
+                };
+                
- It then calls `schedule(sch)`, resulting in a sender `sndr3`. It then - calls `connect(sndr3, rcvr2)`, resulting in an operation state - `op_state3`. It then calls `start(op_state3)`. If any of these - throws an exception, it catches it and calls `set_error(out_rcvr, - current_exception())`. If any of these expressions would be - ill-formed, then Tag(rcvr, args...) is ill-formed. + 3. The member impls-for<schedule_from_t>::complete + is initialized with a callable object equal 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...>;
+            TRY-EVAL(std::move(rcvr), [&]() noexcept(nothrow) {
+              state.async_result.template emplace<result_t>(Tag(), std::forward(args)...); }());
+            if ((state.async_result.index() + 1) > 1) // not valueless and index != 0
+              start(state.op_state2);
+          };
+          
- 2. Calls `connect(sndr, rcvr)` resulting in an operation state `op_state2`. If - this expression would be ill-formed, `connect(sndr2, out_rcvr)` is - ill-formed. +5. Let the subexpression `out_sndr` denote the result of the invocation + `schedule_from(sch, sndr)` or an object copied or moved from such, and let + the subexpression `rcvr` denote a receiver such that the expression + `connect(out_sndr, rcvr)` is well-formed. The expression + `connect(out_sndr, rcvr)` has undefined behavior unless it creates an + asynchronous operation ([async.ops]) that, when started: - 3. Returns an operation state `op_state` that contains `op_state2`. When - `start(op_state)` is called, calls `start(op_state2)`. The lifetime - of `op_state3` ends when `op_state` is destroyed. - - 2. If a sender `out_sndr` returned from `schedule_from(sch, sndr)` is - connected with a receiver `rcvr` with environmment `env` such that - transform_sender(get-domain-late(out_sndr, env), out_sndr, - env) does not return a sender that completes on an execution - agent belonging to the associated execution resource of `sch` and - completing with the same async result ([async.ops]) as `sndr`, the - behavior of calling `connect(out_sndr, rcvr)` is undefined. + - eventually completes on an execution agent belonging to the associated + execution resource of `sch`, and + - completes with the same async result as `sndr`. -3. For a sender `out_sndr` returned from `schedule_from(sch, sndr)`, - `get_env(out_sndr)` shall return a queryable object `q` such that - `get_domain(q)` is expression-equivalent to `get_domain(sch)` and - `get_completion_scheduler(q)` returns a copy of `sch`, where `CPO` is - either `set_value_t` or `set_stopped_t`. The - `get_completion_scheduler` query is not implemented, as the - scheduler cannot be guaranteed in case an error is thrown while trying to - schedule work on the given scheduler object. For all other query objects - Q whose type satisfies - forwarding-query, the expression Q(q, - args...) shall be equivalent to Q(get_env(sndr), - args...). #### `execution::then`, `execution::upon_error`, `execution::upon_stopped` [exec.then] #### {#spec-execution.senders.adaptor.then}