` 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));