diff --git a/execution.bs b/execution.bs index 2c77308..f38ac5c 100644 --- a/execution.bs +++ b/execution.bs @@ -3996,6 +3996,13 @@ a new bullet as follows: [stopcallback.inplace.cons]) when a callback invocation exits via an exception. +* when a `run_loop` object is destroyed that is still in the `running` state + ([exec.run.loop]). + +* when `unhandled_stopped()` is called on a `with_awaitable_senders` object + ([exec.with.awaitable.senders]) whose continuation is not a handle to a + coroutine whose promise type has an `unhandled_stopped()` member function. + @@ -5382,7 +5389,7 @@ template<class Initializer> 2. Let `q` be a query object, let `args` be a (possibly empty) pack of subexpressions, let `env` be a subexpression that refers to a queryable - object `o` of type `O`, and let `cenv` be a subexpression refering to `o` + object `o` of type `O`, and let `cenv` be a subexpression referring to `o` such that `decltype((cenv))` is `const O&`. The expression `q(env, args...)` is equal to ([concepts.equality]) the expression `q(cenv, args...)`. @@ -6479,7 +6486,7 @@ namespace std::execution { 1. Effects: Equivalent to: - - If sender-for<Sndr, transfer_t> is `true`, + - If sender-for<Sndr, continues_on_t> is `true`, then return Domain(); where `Domain` is the type of the following expression: @@ -6872,18 +6879,18 @@ namespace std::execution { communicate to users why. 15.
-        template<sender Sndr, queryable Env>
+        template<sender Sndr, queryable Env>
           constexpr auto write-env(Sndr&& sndr, Env&& env); // exposition only
         
1. `write-env` is an exposition-only sender adaptor that, when connected with a receiver `rcvr`, connects the adapted sender with a receiver whose execution environment is the result of joining the - `queryable` argument `env` to the result of `get_env(rcvr)`. + `queryable` argument `env` to the result of `get_env(rcvr)`. 2. Let `write-env-t` be an exposition-only empty class type. - 3. *Returns:* make-sender(make-env-t(), std::forward<Env>(env), std::forward<Sndr>(sndr)). + 3. *Returns:* make-sender(write-env-t(), std::forward<Env>(env), std::forward<Sndr>(sndr)). 4. *Remarks:* The exposition-only class template `impls-for` ([exec.snd.general]) is specialized for @@ -7700,12 +7707,12 @@ namespace std::execution { except that `sndr` is evaluated only once. 4. The exposition-only class template `impls-for` is specialized - for `transfer_t` as follows: + for `continues_on_t` as follows:
         namespace std::execution {
           template<>
-          struct impls-for<transfer_t> : default-impls {
+          struct impls-for<continues_on_t> : default-impls {
             static constexpr auto get_attrs =
               [](const auto& data, const auto& child) noexcept -> decltype(auto) {
                 return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));
@@ -7715,7 +7722,7 @@ namespace std::execution {
         
5. Let `sndr` and `env` be subexpressions such that `Sndr` is `decltype((sndr))`. If - sender-for<Sndr, transfer_t> is `false`, then the expression + sender-for<Sndr, continues_on_t> is `false`, then the expression `continues_on.transform_sender(sndr, env)` is ill-formed; otherwise, it is equal to: @@ -7914,28 +7921,36 @@ namespace std::execution { `closure` with the async results of the sender, and that then transfers execution back to the execution resource on which `sndr` completed. -2. The name `on` denotes a customization point object. For some subexpressions +2. The name `on` denotes a pipeable sender adaptor object. For subexpressions `sch` and `sndr`, if `decltype((sch))` does not satisfy `scheduler`, or `decltype((sndr))` does not satisfy `sender`, `on(sch, sndr)` is ill-formed. + If `sndr` is a pipeable sender adaptor closure object, `on(sch, sndr)` is + ambiguous with the partial application of the overload described below. 3. Otherwise, the expression `on(sch, sndr)` is expression-equivalent to:
     transform_sender(
       query-or-default(get_domain, sch, default_domain()),
-      make-sender(on, sch, sndr));
+      make-sender(on, sch, sndr))
     
-4. For a subexpression `closure`, if `decltype((closure))` is not a sender - adaptor closure object ([exec.adapt.objects]), the expression `on(sndr, sch, - closure)` is ill-formed; otherwise, it is expression-equivalent to: + except that `sch` is evaluated only once. + +4. For subexpressions `sndr`, `sch`, and `closure`, if `decltype((sch))` does + not satisfy `scheduler`, or `decltype((sndr))` does not satisfy `sender`, or + `closure` is not a pipeable sender adaptor closure object + ([exec.adapt.objects]), the expression `on(sndr, sch, closure)` is + ill-formed; otherwise, it is expression-equivalent to:
     transform_sender(
       get-domain-early(sndr),
-      make-sender(on, product-type{sch, closure}, sndr));
+      make-sender(on, product-type{sch, closure}, sndr))
     
+ except that `sndr` is evaluated only once. + 5. Let `out_sndr` and `env` be subexpressions, let `OutSndr` be `decltype((out_sndr))`, and let `Env` be `decltype((env))`. If sender-for<OutSndr, on_t> is `false`, then the @@ -7955,25 +7970,25 @@ namespace std::execution { }; - ... where the member function `get_completion_signatures` returns an + where the member function `get_completion_signatures` returns an object of a type that is not a specialization of the `completion_signatures` class template. - 2. `on.transform_env(out_sndr, env)` is equivalent to: + 2. The expression `on.transform_env(out_sndr, env)` has effects equivalent to:
-        auto& [_, data, _] = out_sndr;
+        auto&& [_, data, _] = out_sndr;
         if constexpr (scheduler<decltype(data)>) {
-          return JOIN-ENV(SCHED-ENV(data), FWD-ENV(std::forward<Env>(env)));
+          return JOIN-ENV(SCHED-ENV(std::forward_like<OutSndr>(data)), FWD-ENV(std::forward<Env>(env)));
         } else {
           return std::forward<Env>(env);
         }
         
- 3. `on.transform_sender(out_sndr, env)` is equivalent to: + 3. The expression `on.transform_sender(out_sndr, env)` has effects equivalent to:
-        auto& [_, data, child] = out_sndr;
+        auto&& [_, data, child] = out_sndr;
         if constexpr (scheduler<decltype(data)>) {
           auto orig_sch =
             query-with-default(get_scheduler, env, not-a-scheduler());
@@ -8012,57 +8027,57 @@ namespace std::execution {
         inform users that their usage of `on` is incorrect because there is no
         available scheduler onto which to restore execution.
 
-LEWG is uncomfortable with specifying the semantic
-requirements of `on` customizations in terms of "semantic equivalence" to the
-lowered expressions. LEWG would like to clarify what effects are considered
-salient when determining semantic equivalence. The author thinks this is a fair
-request but has no recommendataions at present.
-
-6. Let the subexpression `out_sndr` denote the result of the invocation `on(sch,
-    sndr)` or an object equal to such, let `OutSndr` be `decltype((out_sndr))`,
-    let the subexpression `rcvr` denote a receiver such that
-    `sender_to` is `true`, and let
-    `sch_copy` and `sndr_copy` be lvalue subexpressions refering to objects
-    decay-copied from `sch` and `sndr` respectively.
+6. Let `out_sndr` be a subexpression denoting a sender returned from
+    `on(sch, sndr)` or one equal to such, and let `OutSndr` be the type
+    `decltype((out_sndr))`. Let `out_rcvr` be a subexpression denoting a
+    receiver that has an environment of type `Env` such that `sender_in` is `true`. Let `op` be an lvalue referring to the operation state that
+    results from connecting `out_sndr` with `out_rcvr`. Calling `start(op)`
+    shall:
+  
+    1. Remember the current scheduler, `get_scheduler(get_env(rcvr))`.
+  
+    2. Start `sndr` on an execution agent belonging to `sch`'s associated
+        execution resource.
+
+    4. Upon `sndr`'s completion, transfer execution back to the execution
+        resource associated with the scheduler remembered in step 1.
+
+    5. Forward `sndr`'s async result to `out_rcvr`.
+
+    If any scheduling operation fails, an error completion on `out_rcvr` shall
+    be executed on an unspecified execution agent.
+
+7. Let `out_sndr` be a subexpression denoting a sender returned from
+    `on(sndr, sch, closure)` or one equal to such, and let `OutSndr` be the type
+    `decltype((out_sndr))`. Let `out_rcvr` be a subexpression denoting a
+    receiver that has an environment of type `Env` such that `sender_in` is `true`. Let `op` be an lvalue referring to the operation state that
+    results from connecting `out_sndr` with `out_rcvr`. Calling `start(op)`
+    shall:
     
-    The expression `connect(out_sndr, rcvr)` has undefined behavior unless it
-    creates an asynchronous operation as if by calling `connect(S, rcvr)`, where
-    `S` is a sender expression semantically equivalent to:
+    1. Remember the current scheduler, which is the first of the following
+        expressions that is well-formed:
 
-    
-    continues_on(
-      starts_on(std::forward_like<OutSndr>(sch_copy), std::forward_like<OutSndr>(sndr_copy)),
-      orig_sch)
-    
+ - `get_completion_scheduler(get_env(sndr))` - where `orig_sch` is `get_scheduler(get_env(rcvr))`. + - `get_scheduler(get_env(rcvr))` + + 2. Start `sndr` on the current execution agent. -7. Let the subexpression `out_sndr2` denote the result of the invocation - `on(sndr, sch, closure)` or an object copied or moved from such, let - `OutSndr2` be `decltype((out_sndr2))`, let the subexpression `rcvr2` denote - a receiver such that `sender_to` - is `true`, and let `sndr_copy`, `sch_copy`, and `closure_copy` be lvalue - subexpressions refering to objects decay-copied from `sndr`, `sch`, and - `closure` respectively. + 3. Upon `sndr`'s completion, transfer execution to an agent owned by `sch`'s + associated execution resource. + + 4. Forward `sndr`'s async result as if by connecting and starting a + sender `closure(S)`, where `S` is a sender that completes + synchronously with `sndr`'s async result. - The expression `connect(out_sndr2, rcvr2)` has undefined behavior unless it - creates an asynchronous operation as if by calling `connect(S2, rcvr2)`, where - `S2` is a sender expression semantically equivalent to: - -
-    write-env(
-      continues_on(
-        std::forward_like<OutSndr2>(closure_copy)(
-          continues_on(
-            write-env(std::forward_like<OutSndr2>(sndr_copy), SCHED-ENV(orig_sch)),
-            sch_copy)),
-        orig_sch),
-      SCHED-ENV(sch_copy))
-    
+ 5. Upon completion of the operation started in step 4, transfer execution + back to the execution resource associated with the scheduler remembered + in step 1 and forward the operation's async result to `out_rcvr`. - where `orig_sch` is an lvalue refering to an object decay-copied from - `get_completion_scheduler(get_env(sndr_copy))` if that - expression is well-formed; otherwise, `get_scheduler(get_env(rcvr2))`. + If any scheduling operation fails, an error completion on `out_rcvr` shall + be executed on an unspecified execution agent. #### `execution::then`, `execution::upon_error`, `execution::upon_stopped` [exec.then] #### {#spec-execution.senders.adaptor.then} @@ -9665,7 +9680,7 @@ request but has no recommendataions at present. 1. A `run_loop` is an execution resource on which work can be scheduled. It maintains a thread-safe first-in-first-out queue of work. Its `run()` member function removes elements from the queue and executes them in a loop - on the thread of execution calls `run()`. + on the thread of execution that calls `run()`. 2. A `run_loop` instance has an associated count that corresponds to the number of work items that are in its queue. Additionally, a `run_loop` instance has an @@ -9692,11 +9707,9 @@ request but has no recommendataions at present. run_loop* loop; // exposition only run-loop-opstate-base* next; // exposition only }; - template<receiver_of<completion_signatures<set_value_t()>> Rcvr> + template<class Rcvr> using run-loop-opstate = unspecified; // exposition only - -TODO: kebab these: // [exec.run.loop.members] Member functions: run-loop-opstate-base* pop-front(); // exposition only void push-back(run-loop-opstate-base*); // exposition only @@ -9741,9 +9754,9 @@ class run-loop-scheduler; class run-loop-sender;
-1. `run-loop-sender` is an unspecified type such that for any - type `Env`, completion_signatures_of_t<run-loop-sender, Env> - is: +1. `run-loop-sender` is an exposition-only type that satisfies `sender`. + For any type `Env`, + completion_signatures_of_t<run-loop-sender, Env> is:
       completion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
@@ -9754,25 +9767,25 @@ class run-loop-sender;
 
 3. Let `sndr` be an expression of type
     `run-loop-sender`, let `rcvr` be an
-    expression such that decltype(rcvr) models the
-    `receiver_of` concept, and let `C` be either `set_value_t` or
-    `set_stopped_t`. Then:
+    expression such that receiver_of<decltype((rcvr)), CS>
+    is `true` where `CS` is the `completion_signatures` specialization above. Let
+    `C` be either `set_value_t` or `set_stopped_t`. Then:
 
     * The expression connect(sndr, rcvr) has type
-        run-loop-opstate<decay_t<decltype(rcvr)>>
-        and is potentially-throwing if and only if the initialiation of
-        decay_t<decltype(rcvr)> from
-        `rcvr` is potentially-throwing.
+        run-loop-opstate<decay_t<decltype((rcvr))>>
+        and is potentially-throwing if and only if (void(sndr),
+        auto(rcvr)) is potentially-throwing.
 
     * The expression
         get_completion_scheduler<C>(get_env(sndr)) is
-        not potentially-throwing, has type
+        potentially-throwing if and only if `sndr` is 
+        potentially-throwing, has type
         `run-loop-scheduler`, and compares equal to the
         `run-loop-scheduler` instance from which
         `sndr` was obtained.
 
 
-template<receiver_of<completion_signatures<set_value_t()>> Rcvr>
+template<class Rcvr>
   struct run-loop-opstate;
 
@@ -9792,7 +9805,7 @@ template<receiver_of<completion_signatures<set_value_t()>> Rcvr> * The type run-loop-opstate<Rcvr> overrides run-loop-opstate-base::execute() such that - o.execute() is equivalent to the following: + o.execute() is equivalent to:
         if (get_stop_token(REC(o)).stop_requested()) {
@@ -9802,12 +9815,11 @@ template<receiver_of<completion_signatures<set_value_t()>> Rcvr>
         }
         
- * The expression start(o) is equivalent to the - following: + * The expression start(o) is equivalent to:
         try {
-          o.loop->push-back(&o);
+          o.loop->push-back(addressof(o));
         } catch(...) {
           set_error(std::move(REC(o)), current_exception());
         }
@@ -9856,17 +9868,20 @@ void push-back(run-loop-opstate-base* item);
     operation that obtains `item`.
 
 
-run-loop-scheduler run_loop::get_scheduler();
+run-loop-scheduler get_scheduler();
 
-1. Returns: an instance of `run-loop-scheduler` that +1. Returns: An instance of `run-loop-scheduler` that can be used to schedule work onto this `run_loop` instance.
 void run();
 
-1. Effects: Equivalent to: +1. Precondition: state is starting. + +2. Effects: Sets the state to running. Then, + equivalent to:
     while (auto* op = pop-front()) {
@@ -9874,12 +9889,8 @@ void run();
     }
     
-2. Precondition: state is starting. - -3. Postcondition: state is finishing. - -4. Remarks: While the loop is executing, state is running. - When state changes, it does so without introducing data races. +3. Remarks: When state changes, it does so without introducing + data races.
 void finish();
@@ -9887,22 +9898,22 @@ void finish();
 
 1. Effects: Changes state to finishing.
 
-2. Synchronization: This operation synchronizes with all `pop-front`
-    operations on this object.
+2. Synchronization: `finish` synchronizes with the `pop-front`
+    operation that returns `nullptr`.
 
 ## Coroutine utilities [exec.coro.utils] ## {#spec-execution.coro_utils}
 
 ### `execution::as_awaitable` [exec.as.awaitable] ### {#spec-execution.coro_utils.as_awaitable}
 
 1. `as_awaitable` transforms an object into one that is awaitable within a
-    particular coroutine. This subclause makes use of the following
+    particular coroutine. [exec.coro.utils] makes use of the following
     exposition-only entities:
 
     
     namespace std::execution {
       template<class Sndr, class Promise>
         concept awaitable-sender =
-          single-sender<Sndr, env_of_t> &&
+          single-sender<Sndr, env_of_t<Promise>> &&
           sender_to<Sndr, awaitable-receiver> && // see below
           requires (Promise& p) {
             { p.unhandled_stopped() } -> convertible_to<coroutine_handle<>>;
@@ -9914,7 +9925,7 @@ void finish();
     
2. The type sender-awaitable<Sndr, Promise> is - equivalent to the following: + equivalent to:
         namespace std::execution {
@@ -9939,7 +9950,7 @@ void finish();
         }
         
- 1. `awaitable-receiver` is equivalent to the following: + 1. `awaitable-receiver` is equivalent to:
             struct awaitable-receiver {
@@ -9952,19 +9963,19 @@ void finish();
 
             Let `rcvr` be an rvalue expression of type
             `awaitable-receiver`, let `crcvr` be a `const`
-            lvalue that refers to `rcvr`, let `vs` be a pack of types
-            `Vs...`, and let `err` be an arbitrary expression of type `Err`.
+            lvalue that refers to `rcvr`, let `vs` be a pack of subexpressions,
+            and let `err` be an expression of type `Err`.
             Then:
 
-            1. If constructible_from<result-type, Vs...>
+            1. If constructible_from<result-type, decltype((vs))...>
                 is satisfied, the expression `set_value(rcvr, vs...)` is
                 equivalent to:
 
                 
                 try {
-                  rcvr.result-ptr->emplace<1>(vs...);
+                  rcvr.result-ptr->template emplace<1>(vs...);
                 } catch(...) {
-                  rcvr.result-ptr->emplace<2>(current_exception());
+                  rcvr.result-ptr->template emplace<2>(current_exception());
                 }
                 rcvr.continuation.resume();
                 
@@ -9974,30 +9985,33 @@ void finish(); 2. The expression `set_error(rcvr, err)` is equivalent to:
-                rcvr.result-ptr->emplace<2>(AS-EXCEPT-PTR(err)); // see [exec.general]
+                rcvr.result-ptr->template emplace<2>(AS-EXCEPT-PTR(err)); // see [exec.general]
                 rcvr.continuation.resume();
                 
- 3. The expression `set_stopped(rcvr)` is equivalent to - static_cast<coroutine_handle<>>(rcvr.continuation.promise().unhandled_stopped()).resume(). + 3. The expression `set_stopped(rcvr)` is equivalent to: + +
+                static_cast<coroutine_handle<>>(rcvr.continuation.promise().unhandled_stopped()).resume();
+                
4. For any expression `tag` whose type satisfies `forwarding-query` and for any pack of subexpressions `as`, `get_env(crcvr).query(tag, as...)` is expression-equivalent to tag(get_env(as_const(crcvr.continuation.promise())), - as...) when that expression is well-formed. + as...). - 2. sender-awaitable(Sndr&& sndr, Promise& p) + 2. sender-awaitable(Sndr&& sndr, Promise& p); - - Effects: initializes `state` with + 1. Effects: Initializes `state` with connect(std::forward<Sndr>(sndr), - awaitable-receiver{&result, + awaitable-receiver{addressof(result), coroutine_handle<Promise>::from_promise(p)}). - 3. value-type await_resume() + 3. value-type await_resume(); - - Effects: equivalent to: + 1. Effects: Equivalent to:
                 if (result.index() == 2)
@@ -10006,21 +10020,19 @@ void finish();
                   return std::forward<value-type>(get<1>(result));
                 
-2. `as_awaitable` is a customization point object. For some subexpressions +2. `as_awaitable` is a customization point object. For subexpressions `expr` and `p` where `p` is an lvalue, `Expr` names the type - `decltype((expr))` and `Promise` names the type `decltype((p))`, - `as_awaitable(expr, p)` is expression-equivalent to the following: + `decltype((expr))` and `Promise` names the type `decay_t`, + `as_awaitable(expr, p)` is expression-equivalent to: 1. `expr.as_awaitable(p)` if that expression is well-formed. * Mandates: is-awaitable<A, Promise> is `true`, where `A` is the type of the expression above. - 2. Otherwise, `expr` if is-awaitable<Expr, U> + 2. Otherwise, `void(p), expr` if is-awaitable<Expr, U> is `true`, where `U` is an unspecified class type that - lacks a member named `await_transform`. The - condition is not is-awaitable<Expr, Promise> as - that creates the potential for constraint recursion. + is not `Promise` and that lacks a member named `await_transform`. * Preconditions: is-awaitable<Expr, Promise> is `true` and the expression `co_await expr` in a @@ -10031,7 +10043,9 @@ void finish(); 3. Otherwise, sender-awaitable{expr, p} if awaitable-sender<Expr, Promise> is `true`. - 4. Otherwise, `expr`. + 4. Otherwise, `void(p), expr`. + + except that the evaluations of `expr` and `p` are indeterminately sequenced. ### `execution::with_awaitable_senders` [exec.with.awaitable.senders] ### {#spec-execution.coro_utils.with_awaitable_senders} @@ -10041,14 +10055,14 @@ void finish(); In addition, it provides a default implementation of `unhandled_stopped()` such that if a sender completes by calling `set_stopped`, it is treated as if an uncatchable "stopped" exception were thrown from the - await-expression. In practice, the coroutine is never resumed, and - the `unhandled_stopped` of the coroutine caller's promise type is called. + await-expression. The coroutine is never resumed, and + the `unhandled_stopped` of the coroutine caller's promise type is called.
     namespace std::execution {
       template<class-type Promise>
         struct with_awaitable_senders {
-          template<OtherPromise>
+          template<class OtherPromise>
             requires (!same_as<OtherPromise, void>)
           void set_continuation(coroutine_handle<OtherPromise> h) noexcept;
 
@@ -10061,21 +10075,25 @@ void finish();
           template<class Value>
           see below await_transform(Value&& value);
 
-          private:
+        private:
           // exposition only
-          [[noreturn]] static coroutine_handle<> default_unhandled_stopped(void*) noexcept {
+          [[noreturn]] static coroutine_handle<> default-unhandled-stopped(void*) noexcept {
             terminate();
           }
           coroutine_handle<> continuation{}; // exposition only
           // exposition only
-          coroutine_handle<> (*stopped-handler)(void*) noexcept = &default_unhandled_stopped;
+          coroutine_handle<> (*stopped-handler)(void*) noexcept = &default-unhandled-stopped;
         };
     }
     
-2. `void set_continuation(coroutine_handle h) noexcept` +2.
+    template<class OtherPromise>
+      requires (!same_as<OtherPromise, void>)
+    void set_continuation(coroutine_handle h) noexcept;
+    
- - Effects: equivalent to: + 1. Effects: Equivalent to:
       continuation = h;
@@ -10085,14 +10103,16 @@ void finish();
             .promise().unhandled_stopped();
         };
       } else {
-        stopped-handler = default_unhandled_stopped;
+        stopped-handler = &default-unhandled-stopped;
       }
       
-3. template<class Value>
- call-result-t<as_awaitable_t, Value, Promise&> await_transform(Value&& value)
+3.
+    template<class Value>
+    call-result-t<as_awaitable_t, Value, Promise&> await_transform(Value&& value);
+    
- - Effects: equivalent to: + 1. Effects: Equivalent to:
       return as_awaitable(std::forward<Value>(value), static_cast<Promise&>(*this));