33 Execution control library [exec] (original) (raw)
33.1 General [exec.general]
This Clause describes components supporting execution of function objects ([function.objects]).
The following subclauses describe the requirements, concepts, and components for execution control primitives as summarized in Table 155.
Table 155 — Execution control library summary [tab:exec.summary]
🔗 | Subclause | Header |
---|---|---|
🔗[exec.sched] | Schedulers | <execution> |
🔗[exec.recv] | Receivers | |
🔗[exec.opstate] | Operation states | |
🔗[exec.snd] | Senders |
Table 156 — Types of customization point objects in the execution control library [tab:exec.pos]
🔗Customization point | Purpose | Examples |
---|---|---|
🔗object type | ||
🔗core | provide core execution functionality, and connection between core components | e.g., connect, start |
🔗completion functions | called by senders to announce the completion of the work (success, error, or cancellation) | set_value, set_error, set_stopped |
🔗senders | allow the specialization of the provided sender algorithms | sender factories (e.g., schedule, just, read_env)sender adaptors (e.g., continues_on, then, let_value)sender consumers (e.g., sync_wait) |
🔗queries | allow querying different properties of objects | general queries (e.g., get_allocator, get_stop_token)environment queries (e.g., get_scheduler, get_delegation_scheduler)scheduler queries (e.g., get_forward_progress_guarantee)sender attribute queries (e.g., get_completion_scheduler) |
This clause makes use of the following exposition-only entities.
For a subexpression expr, let MANDATE-NOTHROW(expr) be expression-equivalent to expr.
Mandates: noexcept(expr) is true.
For function types F1 and F2 denotingR1(Args1...) and R2(Args2...), respectively,MATCHING-SIG(F1, F2) is true if and only ifsame_as<R1(Args1&&...), R2(Args2&&...)>is true.
For a subexpression err, let Err be decltype((err)) and let AS-EXCEPT-PTR(err) be:
- err if decay_t<Err> denotes the type exception_ptr.
Preconditions: !err is false. - Otherwise,make_exception_ptr(system_error(err))if decay_t<Err> denotes the type error_code.
- Otherwise, make_exception_ptr(err).
For a subexpression expr, let AS-CONST(expr) be expression-equivalent to[](const auto& x) noexcept -> const auto& { return x; }(expr)
33.2 Queries and queryables [exec.queryable]
33.2.1 General [exec.queryable.general]
A queryable object is a read-only collection of key/value pair where each key is a customization point object known as a query object.
A query is an invocation of a query object with a queryable object as its first argument and a (possibly empty) set of additional arguments.
A query imposes syntactic and semantic requirements on its invocations.
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 referring to osuch that decltype((cenv)) is const O&.
The expression q(env, args...) is equal to ([concepts.equality]) the expression q(cenv, args...).
The type of a query expression cannot be void.
The expression q(env, args...) is equality-preserving ([concepts.equality]) and does not modify the query object or the arguments.
If the expression env.query(q, args...) is well-formed, then it is expression-equivalent to q(env, args...).
Unless otherwise specified, the result of a query is valid as long as the queryable object is valid.
33.2.2 queryable concept [exec.queryable.concept]
namespace std { template<class T> concept queryable = destructible<T>; }
The exposition-only queryable concept specifies the constraints on the types of queryable objects.
Let env be an object of type Env.
The type Env models queryableif for each callable object q and a pack of subexpressions args, if requires { q(env, args...) } is true thenq(env, args...) meets any semantic requirements imposed by q.
33.3 Asynchronous operations [exec.async.ops]
An execution resource is a program entity that manages a (possibly dynamic) set of execution agents ([thread.req.lockable.general]), which it uses to execute parallel work on behalf of callers.
[Example 1:
The currently active thread, a system-provided thread pool, and uses of an API associated with an external hardware accelerator are all examples of execution resources.
— _end example_]
Execution resources execute asynchronous operations.
An execution resource is either valid or invalid.
An asynchronous operation is a distinct unit of program execution that
- can be explicitly started once at most;
- once started, eventually completes exactly once with a (possibly empty) set of result datums and in exactly one of three dispositions: success, failure, or cancellation;
- A successful completion, also known as a value completion, can have an arbitrary number of result datums.
- A failure completion, also known as an error completion, has a single result datum.
- A cancellation completion, also known as a stopped completion, has no result datum.
An asynchronous operation's async resultis its disposition and its (possibly empty) set of result datums.
- can complete on a different execution resource than the execution resource on which it started; and
- can create and start other asynchronous operations called child operations.
A child operation is an asynchronous operation that is created by the parent operation and, if started, completes before the parent operation completes.
A parent operation is the asynchronous operation that created a particular child operation.
[Note 1:
An asynchronous operation can execute synchronously; that is, it can complete during the execution of its start operation on the thread of execution that started it.
— _end note_]
An asynchronous operation has associated state known as its operation state.
An asynchronous operation has an associated environment.
An environment is a queryable object ([exec.queryable]) representing the execution-time properties of the operation's caller.
The caller of an asynchronous operation is its parent operation or the function that created it.
An asynchronous operation has an associated receiver.
A receiver is an aggregation of three handlers for the three asynchronous completion dispositions:
- a value completion handler for a value completion,
- an error completion handler for an error completion, and
- a stopped completion handler for a stopped completion.
A receiver has an associated environment.
An asynchronous operation's operation state owns the operation's receiver.
The environment of an asynchronous operation is equal to its receiver's environment.
For each completion disposition, there is a completion function.
A completion function is a customization point object ([customization.point.object]) that accepts an asynchronous operation's receiver as the first argument and the result datums of the asynchronous operation as additional arguments.
The value completion function invokes the receiver's value completion handler with the value result datums; likewise for the error completion function and the stopped completion function.
A completion function has an associated type known as its completion tagthat is the unqualified type of the completion function.
A valid invocation of a completion function is called a completion operation.
The lifetime of an asynchronous operation, also known as the operation's async lifetime, begins when its start operation begins executing and ends when its completion operation begins executing.
If the lifetime of an asynchronous operation's associated operation state ends before the lifetime of the asynchronous operation, the behavior is undefined.
After an asynchronous operation executes a completion operation, its associated operation state is invalid.
Accessing any part of an invalid operation state is undefined behavior.
An asynchronous operation shall not execute a completion operation before its start operation has begun executing.
After its start operation has begun executing, exactly one completion operation shall execute.
The lifetime of an asynchronous operation's operation state can end during the execution of the completion operation.
A sender is a factory for one or more asynchronous operations.
Connecting a sender and a receiver creates an asynchronous operation.
The asynchronous operation's associated receiver is equal to the receiver used to create it, and its associated environment is equal to the environment associated with the receiver used to create it.
The lifetime of an asynchronous operation's associated operation state does not depend on the lifetimes of either the sender or the receiver from which it was created.
A sender is started when it is connected to a receiver and the resulting asynchronous operation is started.
A sender's async result is the async result of the asynchronous operation created by connecting it to a receiver.
A sender sends its results by way of the asynchronous operation(s) it produces, and a receiver receives those results.
A sender is either valid or invalid; it becomes invalid when its parent sender (see below) becomes invalid.
A scheduler is an abstraction of an execution resource with a uniform, generic interface for scheduling work onto that resource.
It is a factory for senders whose asynchronous operations execute value completion operations on an execution agent belonging to the scheduler's associated execution resource.
A schedule-expression obtains such a sender from a scheduler.
A schedule sender is the result of a schedule expression.
On success, an asynchronous operation produced by a schedule sender executes a value completion operation with an empty set of result datums.
Multiple schedulers can refer to the same execution resource.
A scheduler can be valid or invalid.
A scheduler becomes invalid when the execution resource to which it refers becomes invalid, as do any schedule senders obtained from the scheduler, and any operation states obtained from those senders.
An asynchronous operation has one or more associated completion schedulers for each of its possible dispositions.
A completion scheduler is a scheduler whose associated execution resource is used to execute a completion operation for an asynchronous operation.
A value completion scheduler is a scheduler on which an asynchronous operation's value completion operation can execute.
Likewise for error completion schedulers and stopped completion schedulers.
A sender has an associated queryable object ([exec.queryable]) known as its attributesthat describes various characteristics of the sender and of the asynchronous operation(s) it produces.
For each disposition, there is a query object for reading the associated completion scheduler from a sender's attributes; i.e., a value completion scheduler query object for reading a sender's value completion scheduler, etc.
If a completion scheduler query is well-formed, the returned completion scheduler is unique for that disposition for any asynchronous operation the sender creates.
A schedule sender is required to have a value completion scheduler attribute whose value is equal to the scheduler that produced the schedule sender.
A completion signature is a function type that describes a completion operation.
An asynchronous operation has a finite set of possible completion signatures corresponding to the completion operations that the asynchronous operation potentially evaluates ([basic.def.odr]).
For a completion function set, receiver rcvr, and pack of arguments args, let c be the completion operation set(rcvr, args...), and let F be the function type decltype(auto(set))(decltype((args))...).
A completion signature Sig is associated with cif and only if_MATCHING-SIG_(Sig, F) is true ([exec.general]).
Together, a sender type and an environment type Env determine the set of completion signatures of an asynchronous operation that results from connecting the sender with a receiver that has an environment of type Env.
The type of the receiver does not affect an asynchronous operation's completion signatures, only the type of the receiver's environment.
A sender algorithm is a function that takes and/or returns a sender.
There are three categories of sender algorithms:
- A sender factory is a function that takes non-senders as arguments and that returns a sender.
- A sender adaptor is a function that constructs and returns a parent sender from a set of one or more child senders and a (possibly empty) set of additional arguments.
An asynchronous operation created by a parent sender is a parent operation to the child operations created by the child senders. - A sender consumer is a function that takes one or more senders and a (possibly empty) set of additional arguments, and whose return type is not the type of a sender.
33.4 Header synopsis [execution.syn]
The exposition-only type variant-or-empty<Ts...>is defined as follows:
- If sizeof...(Ts) is greater than zero,variant-or-empty<Ts...> denotes variant<Us...>where Us... is the pack decay_t<Ts>...with duplicate types removed.
- Otherwise, variant-or-empty<Ts...> denotes the exposition-only class type:namespace std::execution { struct empty-variant { empty-variant() = delete;};}
For types Sndr and Env,single-sender-value-type<Sndr, Env> is an alias for:
- value_types_of_t<Sndr, Env, decay_t, type_identity_t>if that type is well-formed,
- Otherwise, voidif value_types_of_t<Sndr, Env, tuple, variant> isvariant<tuple<>> or variant<>,
- Otherwise, value_types_of_t<Sndr, Env, _decayed-tuple_, type_identity_t>if that type is well-formed,
- Otherwise, single-sender-value-type<Sndr, Env> is ill-formed.
The exposition-only concept single-sender is defined as follows:namespace std::execution { template<class Sndr, class Env> concept single-sender = sender_in<Sndr, Env> && requires { typename single-sender-value-type<Sndr, Env>;};}
33.5 Queries [exec.queries]
33.5.1 forwarding_query [exec.fwd.env]
forwarding_query asks a query object whether it should be forwarded through queryable adaptors.
The name forwarding_query denotes a query object.
For some query object q of type Q,forwarding_query(q) is expression-equivalent to:
- MANDATE-NOTHROW(q.query(forwarding_query))if that expression is well-formed.
Mandates: The expression above has type bool and is a core constant expression if q is a core constant expression. - Otherwise, true if derived_from<Q, forwarding_query_t> is true.
33.5.2 get_allocator [exec.get.allocator]
get_allocator asks a queryable object for its associated allocator.
The name get_allocator denotes a query object.
For a subexpression env,get_allocator(env) is expression-equivalent to_MANDATE-NOTHROW_(AS-CONST(env).query(get_allocator)).
forwarding_query(get_allocator) is a core constant expression and has value true.
33.5.3 get_stop_token [exec.get.stop.token]
get_stop_token asks a queryable object for an associated stop token.
The name get_stop_token denotes a query object.
For a subexpression env,get_stop_token(env) is expression-equivalent to:
- MANDATE-NOTHROW(AS-CONST(env).query(get_stop_token))if that expression is well-formed.
- Otherwise, never_stop_token{}.
forwarding_query(get_stop_token) is a core constant expression and has value true.
33.5.4 execution​::​get_env [exec.get.env]
execution​::​get_env is a customization point object.
For a subexpression o,execution​::​get_env(o) is expression-equivalent to:
- MANDATE-NOTHROW(AS-CONST(o).get_env())if that expression is well-formed.
Mandates: The type of the expression above satisfiesqueryable ([exec.queryable]).
The value of get_env(o) shall be valid while o is valid.
[Note 1:
When passed a sender object,get_env returns the sender's associated attributes.
When passed a receiver,get_env returns the receiver's associated execution environment.
— _end note_]
33.5.5 execution​::​get_domain [exec.get.domain]
get_domain asks a queryable object for its associated execution domain tag.
The name get_domain denotes a query object.
For a subexpression env,get_domain(env) is expression-equivalent to_MANDATE-NOTHROW_(AS-CONST(env).query(get_domain)).
forwarding_query(execution​::​get_domain) is a core constant expression and has value true.
33.5.6 execution​::​get_scheduler [exec.get.scheduler]
get_scheduler asks a queryable object for its associated scheduler.
The name get_scheduler denotes a query object.
For a subexpression env,get_scheduler(env) is expression-equivalent to_MANDATE-NOTHROW_(AS-CONST(env).query(get_scheduler)).
Mandates: If the expression above is well-formed, its type satisfies scheduler.
forwarding_query(execution​::​get_scheduler) is a core constant expression and has value true.
33.5.7 execution​::​get_delegation_scheduler [exec.get.delegation.scheduler]
get_delegation_scheduler asks a queryable object for a scheduler that can be used to delegate work to for the purpose of forward progress delegation ([intro.progress]).
The name get_delegation_scheduler denotes a query object.
For a subexpression env,get_delegation_scheduler(env) is expression-equivalent to_MANDATE-NOTHROW_(AS-CONST(env).query(get_delegation_scheduler)).
Mandates: If the expression above is well-formed, its type satisfies scheduler.
forwarding_query(execution​::​get_delegation_scheduler) is a core constant expression and has value true.
33.5.8 execution​::​get_forward_progress_guarantee [exec.get.fwd.progress]
namespace std::execution { enum class forward_progress_guarantee { concurrent, parallel, weakly_parallel};}
get_forward_progress_guarantee asks a scheduler about the forward progress guarantee of execution agents created by that scheduler's associated execution resource ([intro.progress]).
The name get_forward_progress_guarantee denotes a query object.
For a subexpression sch, let Sch be decltype((sch)).
If Sch does not satisfy scheduler,get_forward_progress_guarantee is ill-formed.
Otherwise,get_forward_progress_guarantee(sch) is expression-equivalent to:
- MANDATE-NOTHROW(AS-CONST(sch).query(get_forward_progress_guarantee)), if that expression is well-formed.
Mandates: The type of the expression above is forward_progress_guarantee. - Otherwise, forward_progress_guarantee​::​weakly_parallel.
If get_forward_progress_guarantee(sch) for some scheduler schreturns forward_progress_guarantee​::​concurrent, all execution agents created by that scheduler's associated execution resource shall provide the concurrent forward progress guarantee.
If it returns forward_progress_guarantee​::​parallel, all such execution agents shall provide at least the parallel forward progress guarantee.
33.5.9 execution​::​get_completion_scheduler [exec.get.compl.sched]
get_completion_scheduler<completion-tag> obtains the completion scheduler associated with a completion tag from a sender's attributes.
The name get_completion_scheduler denotes a query object template.
For a subexpression q, the expression get_completion_scheduler<_completion-tag_>(q)is ill-formed if completion-tag is not one ofset_value_t, set_error_t, or set_stopped_t.
Otherwise, get_completion_scheduler<completion-tag_>(q)is expression-equivalent to_MANDATE-NOTHROW(AS-CONST(q).query(get_completion_scheduler<_completion-tag_>)) Mandates: If the expression above is well-formed, its type satisfies scheduler.
Let completion-fn be a completion function ([exec.async.ops]); let completion-tag be the associated completion tag of completion-fn; let args be a pack of subexpressions; and let sndr be a subexpression such that sender<decltype((sndr))> is true andget_completion_scheduler<_completion-tag_>(get_env(sndr))is well-formed and denotes a scheduler sch.
If an asynchronous operation created by connecting sndr with a receiver rcvrcauses the evaluation of completion-fn(rcvr, args...), the behavior is undefined unless the evaluation happens on an execution agent that belongs to sch's associated execution resource.
The expressionforwarding_query(get_completion_scheduler<_completion-tag_>)is a core constant expression and has value true.
33.6 Schedulers [exec.sched]
The scheduler concept defines the requirements of a scheduler type ([exec.async.ops]).
schedule is a customization point object that accepts a scheduler.
A valid invocation of schedule is a schedule-expression.
namespace std::execution { template<class Sch> concept scheduler = derived_from<typename remove_cvref_t<Sch>::scheduler_concept, scheduler_t> && queryable<Sch> && requires(Sch&& sch) { { schedule(std::forward<Sch>(sch)) } -> sender;{ auto(get_completion_scheduler<set_value_t>( get_env(schedule(std::forward<Sch>(sch))))) } -> same_as<remove_cvref_t<Sch>>;} && equality_comparable<remove_cvref_t<Sch>> && copyable<remove_cvref_t<Sch>>;}
Let Sch be the type of a scheduler and let Env be the type of an execution environment for which sender_in<schedule_result_t<Sch>, Env>is satisfied.
Then sender-in-of<schedule_result_t<Sch>, Env>shall be modeled.
No operation required bycopyable<remove_cvref_t<Sch>> andequality_comparable<remove_cvref_t<Sch>>shall exit via an exception.
None of these operations, nor a scheduler type's schedule function, shall introduce data races as a result of potentially concurrent ([intro.races]) invocations of those operations from different threads.
For any two values sch1 and sch2of some scheduler type Sch,sch1 == sch2 shall return trueonly if both sch1 and sch2 share the same associated execution resource.
For a given scheduler expression sch, the expressionget_completion_scheduler<set_value_t>(get_env(schedule(sch)))shall compare equal to sch.
For a given scheduler expression sch, if the expression get_domain(sch) is well-formed, then the expression get_domain(get_env(schedule(sch)))is also well-formed and has the same type.
A scheduler type's destructor shall not block pending completion of any receivers connected to the sender objects returned from schedule.
[Note 1:
The ability to wait for completion of submitted function objects can be provided by the associated execution resource of the scheduler.
— _end note_]
33.7 Receivers [exec.recv]
33.7.1 Receiver concepts [exec.recv.concepts]
A receiver represents the continuation of an asynchronous operation.
The receiver concept defines the requirements for a receiver type ([exec.async.ops]).
The receiver_of concept defines the requirements for a receiver type that is usable as the first argument of a set of completion operations corresponding to a set of completion signatures.
The get_env customization point object is used to access a receiver's associated environment.
namespace std::execution { template<class Rcvr> concept receiver = derived_from<typename remove_cvref_t<Rcvr>::receiver_concept, receiver_t> && requires(const remove_cvref_t<Rcvr>& rcvr) { { get_env(rcvr) } -> queryable;} && move_constructible<remove_cvref_t<Rcvr>> && constructible_from<remove_cvref_t<Rcvr>, Rcvr>; template<class Signature, class Rcvr> concept valid-completion-for = requires (Signature* sig) { []<class Tag, class... Args>(Tag(*)(Args...)) requires callable<Tag, remove_cvref_t<Rcvr>, Args...> {}(sig);};template<class Rcvr, class Completions> concept has-completions = requires (Completions* completions) { []<valid-completion-for<Rcvr>...Sigs>(completion_signatures<Sigs...>*) {}(completions);};template<class Rcvr, class Completions> concept receiver_of = receiver<Rcvr> && has-completions<Rcvr, Completions>;}
Class types that are marked final do not model the receiver concept.
Let rcvr be a receiver and let op_state be an operation state associated with an asynchronous operation created by connecting rcvr with a sender.
Let token be a stop token equal toget_stop_token(get_env(rcvr)).
token shall remain valid for the duration of the asynchronous operation's lifetime ([exec.async.ops]).
[Note 1:
This means that, unless it knows about further guarantees provided by the type of rcvr, the implementation of op_state cannot use tokenafter it executes a completion operation.
This also implies that any stop callbacks registered on token must be destroyed before the invocation of the completion operation.
— _end note_]
33.7.2 execution​::​set_value [exec.set.value]
set_value is a value completion function ([exec.async.ops]).
Its associated completion tag is set_value_t.
The expression set_value(rcvr, vs...)for a subexpression rcvr and pack of subexpressions vs is ill-formed if rcvr is an lvalue or an rvalue of const type.
Otherwise, it is expression-equivalent to_MANDATE-NOTHROW_(rcvr.set_value(vs...)).
33.7.3 execution​::​set_error [exec.set.error]
set_error is an error completion function ([exec.async.ops]).
Its associated completion tag is set_error_t.
The expression set_error(rcvr, err)for some subexpressions rcvr and err is ill-formed if rcvr is an lvalue or an rvalue of const type.
Otherwise, it is expression-equivalent to_MANDATE-NOTHROW_(rcvr.set_error(err)).
33.7.4 execution​::​set_stopped [exec.set.stopped]
set_stopped is a stopped completion function ([exec.async.ops]).
Its associated completion tag is set_stopped_t.
The expression set_stopped(rcvr)for a subexpression rcvr is ill-formed if rcvr is an lvalue or an rvalue of const type.
Otherwise, it is expression-equivalent to_MANDATE-NOTHROW_(rcvr.set_stopped()).
33.8 Operation states [exec.opstate]
33.8.1 General [exec.opstate.general]
The operation_state concept defines the requirements of an operation state type ([exec.async.ops]).
namespace std::execution { template<class O> concept operation_state = derived_from<typename O::operation_state_concept, operation_state_t> && is_object_v<O> && requires (O& o) { { start(o) } noexcept;};}
If an operation_state object is destroyed during the lifetime of its asynchronous operation ([exec.async.ops]), the behavior is undefined.
[Note 1:
The operation_state concept does not impose requirements on any operations other than destruction and start, including copy and move operations.
Invoking any such operation on an object whose type models operation_state can lead to undefined behavior.
— _end note_]
The program is ill-formed if it performs a copy or move construction or assignment operation on an operation state object created by connecting a library-provided sender.
33.8.2 execution​::​start [exec.opstate.start]
The name start denotes a customization point object that starts ([exec.async.ops]) the asynchronous operation associated with the operation state object.
For a subexpression op, the expression start(op) is ill-formed if op is an rvalue.
Otherwise, it is expression-equivalent to_MANDATE-NOTHROW_(op.start()).
If op.start() does not start ([exec.async.ops]) the asynchronous operation associated with the operation state op, the behavior of calling start(op) is undefined.
33.9 Senders [exec.snd]
33.9.1 General [exec.snd.general]
Subclauses [exec.factories] and [exec.adapt] define customizable algorithms that return senders.
Each algorithm has a default implementation.
Let sndr be the result of an invocation of such an algorithm or an object equal to the result ([concepts.equality]), and let Sndr be decltype((sndr)).
Let rcvr be a receiver of type Rcvrwith associated environment env of type Envsuch that sender_to<Sndr, Rcvr> is true.
For the default implementation of the algorithm that produced sndr, connecting sndr to rcvr and starting the resulting operation state ([exec.async.ops]) necessarily results in the potential evaluation ([basic.def.odr]) of a set of completion operations whose first argument is a subexpression equal to rcvr.
Let Sigs be a pack of completion signatures corresponding to this set of completion operations.
Then the type of the expression get_completion_signatures(sndr, env) is a specialization of the class template completion_signatures ([exec.util.cmplsig]), the set of whose template arguments is Sigs.
If a user-provided implementation of the algorithm that produced sndr is selected instead of the default, any completion signature that is in the set of types denoted by completion_signatures_of_t<Sndr, Env> and that is not part of Sigs shall correspond to error or stopped completion operations, unless otherwise specified.
33.9.2 Exposition-only entities [exec.snd.expos]
Subclause [exec.snd] makes use of the following exposition-only entities.
For a queryable object env,FWD-ENV(env) is an expression whose type satisfies queryablesuch that for a query object q and a pack of subexpressions as, the expression FWD-ENV(env).query(q, as...) is ill-formed if forwarding_query(q) is false; otherwise, it is expression-equivalent to env.query(q, as...).
For a query object q and a subexpression v,MAKE-ENV(q, v) is an expression envwhose type satisfies queryablesuch that the result of env.query(q) has a value equal to v ([concepts.equality]).
Unless otherwise stated, the object to which env.query(q) refers remains valid while env remains valid.
For two queryable objects env1 and env2, a query object q, and a pack of subexpressions as,JOIN-ENV(env1, env2) is an expression env3whose type satisfies queryablesuch that env3.query(q, as...) is expression-equivalent to:
- env1.query(q, as...) if that expression is well-formed,
- otherwise, env2.query(q, as...) if that expression is well-formed,
- otherwise, env3.query(q, as...) is ill-formed.
The results of FWD-ENV, MAKE-ENV, and _JOIN-ENV_can be context-dependent; i.e., they can evaluate to expressions with different types and value categories in different contexts for the same arguments.
For a scheduler sch,SCHED-ATTRS(sch) is an expression o1whose type satisfies queryablesuch that o1.query(get_completion_scheduler<Tag>) is an expression with the same type and value as schwhere Tag is one of set_value_t or set_stopped_t, and such that o1.query(get_domain) is expression-equivalent tosch.query(get_domain).
SCHED-ENV(sch) is an expression o2whose type satisfies queryablesuch that o2.query(get_scheduler) is a prvalue with the same type and value as sch, and such that o2.query(get_domain) is expression-equivalent tosch.query(get_domain).
For two subexpressions rcvr and expr,SET-VALUE(rcvr, expr) is expression-equivalent to(expr, set_value(std​::​move(rcvr)))if the type of expr is void; otherwise, set_value(std​::​move(rcvr), expr).
TRY-EVAL(rcvr, expr) is equivalent to:try { expr;} catch(...) { set_error(std::move(rcvr), current_exception());} if expr is potentially-throwing; otherwise, expr.
TRY-SET-VALUE(rcvr, expr) is_TRY-EVAL_(rcvr, SET-VALUE(rcvr, expr)) except that rcvr is evaluated only once.
template<class Default = default_domain, class Sndr> constexpr auto _completion-domain_(const Sndr& sndr) noexcept;
COMPL-DOMAIN(T) is the type of the expressionget_domain(get_completion_scheduler<T>(get_env(sndr))).
Effects: If all of the types_COMPL-DOMAIN_(set_value_t),COMPL-DOMAIN(set_error_t), and
COMPL-DOMAIN(set_stopped_t) are ill-formed,completion-domain<Default>(sndr) is a default-constructed prvalue of type Default.
Otherwise, if they all share a common type ([meta.trans.other]) (ignoring those types that are ill-formed), then completion-domain<Default>(sndr) is a default-constructed prvalue of that type.
Otherwise, completion-domain<Default>(sndr) is ill-formed.
template<class Tag, class Env, class Default> constexpr decltype(auto) _query-with-default_( Tag, const Env& env, Default&& value) noexcept(_see below_);
Let e be the expression Tag()(env)if that expression is well-formed; otherwise, it is static_cast<Default>(std​::​forward<Default>(value)).
Remarks: The expression in the noexcept clause is noexcept(e).
template<class Sndr> constexpr auto _get-domain-early_(const Sndr& sndr) noexcept;
Effects: Equivalent to:return Domain();where Domain is the decayed type of the first of the following expressions that is well-formed:
- get_domain(get_env(sndr))
- completion-domain(sndr)
- default_domain()
template<class Sndr, class Env> constexpr auto _get-domain-late_(const Sndr& sndr, const Env& env) noexcept;
Effects: Equivalent to:
- If sender-for<Sndr, continues_on_t> is true, thenreturn Domain();where Domain is the type of the following expression:[] { auto [_, sch, _] = sndr;return query-or-default(get_domain, sch, default_domain());}();
[Note 1:
The continues_on algorithm works in tandem with schedule_from ([exec.schedule.from]) to give scheduler authors a way to customize both how to transition onto (continues_on) and off of (schedule_from) a given execution context.
Thus, continues_on ignores the domain of the predecessor and uses the domain of the destination scheduler to select a customization, a property that is unique to continues_on.
That is why it is given special treatment here.
— _end note_] - Otherwise,return Domain();where Domain is the first of the following expressions that is well-formed and whose type is not void:
- get_domain(get_env(sndr))
- completion-domain<void>(sndr)
- get_domain(env)
- get_domain(get_scheduler(env))
- default_domain()
template<callable Fun> requires is_nothrow_move_constructible_v<Fun> struct emplace-from { Fun fun; using type = call-result-t<Fun>;constexpr operator type() && noexcept(nothrow-callable<Fun>) { return std::move(fun)();} constexpr type operator()() && noexcept(nothrow-callable<Fun>) { return std::move(fun)();} };
[Note 2:
emplace-from is used to emplace non-movable types into tuple, optional, variant, and similar types.
— _end note_]
struct on-stop-request { inplace_stop_source& stop-src; void operator()() noexcept { stop-src.request_stop(); } };
template<class T, class T, ..., class T> struct product-type { T t; T t; ... T t; template<size_t I, class Self> constexpr decltype(auto) get(this Self&& self) noexcept; template<class Self, class Fn> constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(see below);};
[Note 3:
product-type is presented here in pseudo-code form for the sake of exposition.
It can be approximated in standard C++ with a tuple-like implementation that takes care to keep the type an aggregate that can be used as the initializer of a structured binding declaration.
— _end note_]
[Note 4:
An expression of type product-type is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).
— _end note_]
template<size_t I, class Self> constexpr decltype(auto) _get_(this Self&& self) noexcept;
Effects: Equivalent to:auto& [...ts] = self;return std::forward_like<Self>(ts...[I]);
template<class Self, class Fn> constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(see below);
Constraints: The expression in the return statement below is well-formed.
Effects: Equivalent to:auto& [...ts] = self;return std::forward<Fn>(fn)(std::forward_like<Self>(ts)...);
Remarks: The expression in the noexcept clause is trueif the return statement above is not potentially throwing; otherwise, false.
template<class Tag, class Data = _see below_, class... Child> constexpr auto _make-sender_(Tag tag, Data&& data, Child&&... child);
Mandates: The following expressions are true:
- semiregular<Tag>
- movable-value<Data>
- (sender<Child> &&...)
Returns: A prvalue of type basic-sender<Tag, decay_t<Data>, decay_t<Child>...>that has been direct-list-initialized with the forwarded arguments, where basic-sender is the following exposition-only class template except as noted below.
namespace std::execution { template<class Tag> concept completion-tag = same_as<Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>;template<template<class...> class T, class... Args> concept valid-specialization = requires { typename T<Args...>; };struct default-impls { static constexpr auto get-attrs = see below; static constexpr auto get-env = see below; static constexpr auto get-state = see below; static constexpr auto start = see below; static constexpr auto complete = see below; };template<class Tag> struct impls-for : default-impls {}; template<class Sndr, class Rcvr> using state-type = decay_t<_call-result-t_< decltype(_impls-for_<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>>;template<class Index, class Sndr, class Rcvr> using env-type = call-result-t< decltype(_impls-for_<tag_of_t<Sndr>>::get-env), Index,state-type<Sndr, Rcvr>&, const Rcvr&>;template<class Sndr, size_t I = 0> using child-type = decltype(declval<Sndr>().template get<I+2>()); template<class Sndr> using indices-for = remove_reference_t<Sndr>::indices-for; template<class Sndr, class Rcvr> struct basic-state { basic-state(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below) : rcvr(std::move(rcvr)) , state(impls-for<tag_of_t<Sndr>>::get-state(std::forward<Sndr>(sndr), rcvr)) { } Rcvr rcvr; state-type<Sndr, Rcvr> state; };template<class Sndr, class Rcvr, class Index> requires valid-specialization<_env-type_, Index, Sndr, Rcvr> struct basic-receiver { using receiver_concept = receiver_t;using tag-t = tag_of_t<Sndr>; using state-t = state-type<Sndr, Rcvr>; static constexpr const auto& complete = impls-for<_tag-t_>::complete; template<class... Args> requires callable<decltype(_complete_), Index, _state-t_&, Rcvr&, set_value_t, Args...> void set_value(Args&&... args) && noexcept { complete(Index(), op->state, op->rcvr, set_value_t(), std::forward<Args>(args)...);} template<class Error> requires callable<decltype(_complete_), Index, _state-t_&, Rcvr&, set_error_t, Error> void set_error(Error&& err) && noexcept { complete(Index(), op->state, op->rcvr, set_error_t(), std::forward<Error>(err));} void set_stopped() && noexcept requires callable<decltype(_complete_), Index, _state-t_&, Rcvr&, set_stopped_t> { complete(Index(), op->state, op->rcvr, set_stopped_t());} auto get_env() const noexcept -> env-type<Index, Sndr, Rcvr> { return impls-for<tag-t>::get-env(Index(), op->state, op->rcvr);} basic-state<Sndr, Rcvr>* op; };constexpr auto connect-all = see below; template<class Sndr, class Rcvr> using connect-all-result = call-result-t< decltype(_connect-all_), _basic-state_<Sndr, Rcvr>*, Sndr, indices-for<Sndr>>;template<class Sndr, class Rcvr> requires valid-specialization<_state-type_, Sndr, Rcvr> && valid-specialization<_connect-all-result_, Sndr, Rcvr> struct basic-operation : basic-state<Sndr, Rcvr> { using operation_state_concept = operation_state_t;using tag-t = tag_of_t<Sndr>; connect-all-result<Sndr, Rcvr> inner-ops; basic-operation(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below) : basic-state<Sndr, Rcvr>(std::forward<Sndr>(sndr), std::move(rcvr)),inner-ops(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>())) {} void start() & noexcept { auto& [...ops] = inner-ops;impls-for<tag-t>::start(this->state, this->rcvr, ops...);} };template<class Sndr, class Env> using completion-signatures-for = see below; template<class Tag, class Data, class... Child> struct basic-sender : product-type<Tag, Data, Child...> { using sender_concept = sender_t;using indices-for = index_sequence_for<Child...>; decltype(auto) get_env() const noexcept { auto& [_, data, ...child] = *this;return impls-for<Tag>::get-attrs(data, child...);} template<decays-to<_basic-sender_> Self, receiver Rcvr> auto connect(this Self&& self, Rcvr rcvr) noexcept(see below) -> basic-operation<Self, Rcvr> { return {std::forward<Self>(self), std::move(rcvr)};} template<decays-to<_basic-sender_> Self, class Env> auto get_completion_signatures(this Self&& self, Env&& env) noexcept -> completion-signatures-for<Self, Env> { return {};} };}
The default template argument for the Data template parameter denotes an unspecified empty trivially copyable class type that models semiregular.
It is unspecified whether a specialization of _basic-sender_is an aggregate.
An expression of type basic-sender is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).
The expression in the noexcept clause of the constructor of basic-state isis_nothrow_move_constructible_v<Rcvr> && nothrow-callable<decltype(_impls-for_<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&> && (same_as<_state-type_<Sndr, Rcvr>, _get-state-result_> || is_nothrow_constructible_v<_state-type_<Sndr, Rcvr>, get-state-result_>) where get-state-result is_call-result-t<decltype(_impls-for_<tag_of_t<Sndr>>::get-state), Sndr, Rcvr&>.
The object connect-all is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr, size_t... Is>( _basic-state_<Sndr, Rcvr>* op, Sndr&& sndr, index_sequence<Is...>) noexcept(_see below_) -> decltype(auto) { auto& [_, data, ...child] = sndr;return _product-type_{connect( std::forward_like<Sndr>(child),_basic-receiver_<Sndr, Rcvr, integral_constant<size_t, Is>>{op})...};}
Constraints: The expression in the return statement is well-formed.
Remarks: The expression in the noexcept clause is trueif the return statement is not potentially throwing; otherwise, false.
The expression in the noexcept clause of the constructor of basic-operation is:is_nothrow_constructible_v<_basic-state_<Self, Rcvr>, Self, Rcvr> && noexcept(connect-all(this, std::forward<Sndr>(sndr), indices-for<Sndr>()))
The expression in the noexcept clause of the connect member function of basic-sender is:is_nothrow_constructible_v<_basic-operation_<Self, Rcvr>, Self, Rcvr>
The member _default-impls_​::​_get-attrs_is initialized with a callable object equivalent to the following lambda:[](const auto&, const auto&... child) noexcept -> decltype(auto) { if constexpr (sizeof...(child) == 1) return (FWD-ENV(get_env(child)), ...);else return env<>();}
The member _default-impls_​::​_get-env_is initialized with a callable object equivalent to the following lambda:[](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) { return FWD-ENV(get_env(rcvr));}
The member _default-impls_​::​_get-state_is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) { auto& [_, data, ...child] = sndr;return std::forward_like<Sndr>(data);}
The member _default-impls_​::​_start_is initialized with a callable object equivalent to the following lambda:[](auto&, auto&, auto&... ops) noexcept -> void { (execution::start(ops), ...);}
The member _default-impls_​::​_complete_is initialized with a callable object equivalent to the following lambda:[]<class Index, class Rcvr, class Tag, class... Args>( Index, auto& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires callable<Tag, Rcvr, Args...> { static_assert(Index::value == 0); Tag()(std::move(rcvr), std::forward<Args>(args)...);}
For a subexpression sndr let Sndr be decltype((sndr)).
Let rcvr be a receiver with an associated environment of type Envsuch that sender_in<Sndr, Env> is true.
completion-signatures-for<Sndr, Env> denotes a specialization of completion_signatures, the set of whose template arguments correspond to the set of completion operations that are potentially evaluated as a result of starting ([exec.async.ops]) the operation state that results from connecting sndr and rcvr.
When sender_in<Sndr, Env> is false, the type denoted by completion-signatures-for<Sndr, Env>, if any, is not a specialization of completion_signatures.
Recommended practice: When sender_in<Sndr, Env> is false, implementations are encouraged to use the type denoted by completion-signatures-for<Sndr, Env>to communicate to users why.
template<[sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") Env> constexpr auto _write-env_(Sndr&& sndr, Env&& env); // _exposition only_
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 envto the result of get_env(rcvr).
Let write-env-t be an exposition-only empty class type.
Returns: make-sender(write-env-t(), std::forward<Env>(env), std::forward<Sndr>(sndr))
Remarks: The exposition-only class template impls-for ([exec.snd.general]) is specialized for write-env-t as follows:template<> struct impls-for<_write-env-t_> : default-impls { static constexpr auto get-env = [](auto, const auto& state, const auto& rcvr) noexcept { return see below;};};
Invocation of_impls-for_<_write-env-t_>​::​_get-env_returns an object e such that
- decltype(e) models queryable and
- given a query object q, the expression e.query(q) is expression-equivalent to state.query(q) if that expression is valid, otherwise, e.query(q) is expression-equivalent to get_env(rcvr).query(q).
33.9.3 Sender concepts [exec.snd.concepts]
The sender concept defines the requirements for a sender type ([exec.async.ops]).
The sender_in concept defines the requirements for a sender type that can create asynchronous operations given an associated environment type.
The sender_to concept defines the requirements for a sender type that can connect with a specific receiver type.
The get_env customization point object is used to access a sender's associated attributes.
The connect customization point object is used to connect ([exec.async.ops]) a sender and a receiver to produce an operation state.
namespace std::execution { template<class Sigs> concept valid-completion-signatures = see below; template<class Sndr> concept is-sender = derived_from<typename Sndr::sender_concept, sender_t>;template<class Sndr> concept enable-sender = is-sender<Sndr> || is-awaitable<Sndr, _env-promise_<env<>>>; template<class Sndr> concept sender = bool(enable-sender<remove_cvref_t<Sndr>>) && requires (const remove_cvref_t<Sndr>& sndr) { { get_env(sndr) } -> queryable;} && move_constructible<remove_cvref_t<Sndr>> && constructible_from<remove_cvref_t<Sndr>, Sndr>;template<class Sndr, class Env = env<>> concept sender_in = sender<Sndr> && queryable<Env> && requires (Sndr&& sndr, Env&& env) { { get_completion_signatures(std::forward<Sndr>(sndr), std::forward<Env>(env)) } -> valid-completion-signatures;};template<class Sndr, class Rcvr> concept sender_to = sender_in<Sndr, env_of_t<Rcvr>> && receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t<Rcvr>>> && requires (Sndr&& sndr, Rcvr&& rcvr) { connect(std::forward<Sndr>(sndr), std::forward<Rcvr>(rcvr));};}
Given a subexpression sndr, let Sndr be decltype((sndr)) and let rcvr be a receiver with an associated environment whose type is Env.
A completion operation is a permissible completionfor Sndr and Envif its completion signature appears in the argument list of the specialization of completion_signatures denoted bycompletion_signatures_of_t<Sndr, Env>.
Sndr and Env model sender_in<Sndr, Env>if all the completion operations that are potentially evaluated by connecting sndr to rcvr and starting the resulting operation state are permissible completions for Sndr and Env.
A type models the exposition-only concept valid-completion-signaturesif it denotes a specialization of the completion_signatures class template.
The exposition-only conceptssender-of and sender-in-ofdefine the requirements for a sender type that completes with a given unique set of value result types.
namespace std::execution { template<class... As> using value-signature = set_value_t(As...); template<class Sndr, class Env, class... Values> concept sender-in-of = sender_in<Sndr, Env> && MATCHING-SIG( set_value_t(Values...), value_types_of_t<Sndr, Env, _value-signature_, type_identity_t>);template<class Sndr, class... Values> concept sender-of = sender-in-of<Sndr, env<>, Values...>;}
Let sndr be an expression such that decltype((sndr)) is Sndr.
The type tag_of_t<Sndr> is as follows:
- If the declarationauto&& [tag, data, ...children] = sndr;would be well-formed, tag_of_t<Sndr> is an alias for decltype(auto(tag)).
- Otherwise, tag_of_t<Sndr> is ill-formed.
Let sender-for be an exposition-only concept defined as follows:namespace std::execution { template<class Sndr, class Tag> concept sender-for = sender<Sndr> && same_as<tag_of_t<Sndr>, Tag>;}
For a type T,SET-VALUE-SIG(T) denotes the type set_value_t()if T is cv void; otherwise, it denotes the type set_value_t(T).
Library-provided sender types
- always expose an overload of a member connectthat accepts an rvalue sender and
- only expose an overload of a member connectthat accepts an lvalue sender if they model copy_constructible.
33.9.4 Awaitable helpers [exec.awaitable]
The sender concepts recognize awaitables as senders.
For [exec], an awaitable is an expression that would be well-formed as the operand of a co_await expression within a given context.
For a subexpression c, let GET-AWAITER(c, p) be expression-equivalent to the series of transformations and conversions applied to cas the operand of an await-expression in a coroutine, resulting in lvalue e as described by [expr.await], where p is an lvalue referring to the coroutine's promise, which has type Promise.
[Note 1:
This includes the invocation of the promise type's await_transform member if any, the invocation of the operator co_awaitpicked by overload resolution if any, and any necessary implicit conversions and materializations.
— _end note_]
Let is-awaitable be the following exposition-only concept:namespace std { template<class T> concept await-suspend-result = see below; template<class A, class Promise> concept is-awaiter = requires (A& a, coroutine_handle<Promise> h) { a.await_ready() ? 1 : 0;{ a.await_suspend(h) } -> await-suspend-result; a.await_resume();};template<class C, class Promise> concept is-awaitable = requires (C (*fc)() noexcept, Promise& p) { { GET-AWAITER(fc(), p) } -> is-awaiter<Promise>;};}
await-suspend-result<T> is trueif and only if one of the following is true:
- T is void, or
- T is bool, or
- T is a specialization of coroutine_handle.
For a subexpression csuch that decltype((c)) is type C, and an lvalue p of type Promise,await-result- type<C, Promise> denotes the type decltype(GET-AWAITER(c, p).await_resume()).
Let with-await-transform be the exposition-only class template:namespace std::execution { template<class T, class Promise> concept has-as-awaitable = requires (T&& t, Promise& p) { { std::forward<T>(t).as_awaitable(p) } -> is-awaitable<Promise&>;};template<class Derived> struct with-await-transform { template<class T> T&& await_transform(T&& value) noexcept { return std::forward<T>(value);} template<has-as-awaitable<Derived> T> decltype(auto) await_transform(T&& value) noexcept(noexcept(std::forward<T>(value).as_awaitable(declval<Derived&>()))) { return std::forward<T>(value).as_awaitable(static_cast<Derived&>(*this));} };}
Let env-promise be the exposition-only class template:namespace std::execution { template<class Env> struct env-promise : with-await-transform<_env-promise_<Env>> { unspecified get_return_object() noexcept;unspecified initial_suspend() noexcept;unspecified final_suspend() noexcept;void unhandled_exception() noexcept;void return_void() noexcept; coroutine_handle<> unhandled_stopped() noexcept;const Env& get_env() const noexcept;};}
[Note 2:
Specializations of env-promise are used only for the purpose of type computation; its members need not be defined.
— _end note_]
33.9.5 execution​::​default_domain [exec.domain.default]
namespace std::execution { struct default_domain { template<sender Sndr, queryable... Env> requires (sizeof...(Env) <= 1) static constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) noexcept(_see below_);template<sender Sndr, queryable Env> static constexpr queryable decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept;template<class Tag, sender Sndr, class... Args> static constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args) noexcept(see below);};}
template<[sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]")... Env> requires (sizeof...(Env) <= 1) constexpr [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) noexcept(_see below_);
Let e be the expressiontag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...) if that expression is well-formed; otherwise, std​::​forward<Sndr>(sndr).
Remarks: The exception specification is equivalent to noexcept(e).
template<[sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") Env> constexpr [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept;
Let e be the expressiontag_of_t<Sndr>().transform_env(std::forward<Sndr>(sndr), std::forward<Env>(env)) if that expression is well-formed; otherwise, static_cast<Env>(std​::​forward<Env>(env)).
Mandates: noexcept(e) is true.
template<class Tag, [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, class... Args> constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args) noexcept(_see below_);
Let e be the expression Tag().apply_sender(std::forward<Sndr>(sndr), std::forward<Args>(args)...)
Constraints: e is a well-formed expression.
Remarks: The exception specification is equivalent to noexcept(e).
33.9.6 execution​::​transform_sender [exec.snd.transform]
namespace std::execution { template<class Domain, [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]")... Env> requires (sizeof...(Env) <= 1) constexpr [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) noexcept(_see below_);}
Let transformed-sndr be the expressiondom.transform_sender(std::forward<Sndr>(sndr), env...) if that expression is well-formed; otherwise,default_domain().transform_sender(std::forward<Sndr>(sndr), env...)
Let final-sndr be the expression _transformed-sndr_if transformed-sndr and _sndr_have the same type ignoring cv-qualifiers; otherwise, it is the expression transform_sender(dom, transformed-sndr, env...).
Remarks: The exception specification is equivalent tonoexcept(final-sndr).
33.9.7 execution​::​transform_env [exec.snd.transform.env]
namespace std::execution { template<class Domain, [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") Env> constexpr [_queryable_](#concept:queryable "33.2.2 queryable concept [exec.queryable.concept]") decltype(auto) transform_env(Domain dom, Sndr&& sndr, Env&& env) noexcept;}
Let e be the expressiondom.transform_env(std::forward<Sndr>(sndr), std::forward<Env>(env)) if that expression is well-formed; otherwise,default_domain().transform_env(std::forward<Sndr>(sndr), std::forward<Env>(env))
Mandates: noexcept(e) is true.
33.9.8 execution​::​apply_sender [exec.snd.apply]
namespace std::execution { template<class Domain, class Tag, [sender](#concept:sender "33.9.3 Sender concepts [exec.snd.concepts]") Sndr, class... Args> constexpr decltype(auto) apply_sender(Domain dom, Tag, Sndr&& sndr, Args&&... args) noexcept(_see below_);}
Let e be the expressiondom.apply_sender(Tag(), std::forward<Sndr>(sndr), std::forward<Args>(args)...) if that expression is well-formed; otherwise,default_domain().apply_sender(Tag(), std::forward<Sndr>(sndr), std::forward<Args>(args)...)
Constraints: The expression e is well-formed.
Remarks: The exception specification is equivalent to noexcept(e).
33.9.9 execution​::​get_completion_signatures [exec.getcomplsigs]
get_completion_signatures is a customization point object.
Let sndr be an expression such that decltype((sndr)) is Sndr, and let env be an expression such that decltype((env)) is Env.
Let new_sndr be the expressiontransform_sender(decltype(get-domain-late(sndr, env)){}, sndr, env), and let NewSndr be decltype((new_sndr)).
Then get_completion_signatures(sndr, env) is expression-equivalent to(void(sndr), void(env), CS())except that void(sndr) and void(env) are indeterminately sequenced, where CS is:
- decltype(new_sndr.get_completion_signatures(env))if that type is well-formed,
- Otherwise, remove_cvref_t<NewSndr>​::​completion_signaturesif that type is well-formed,
- Otherwise, if is-awaitable<NewSndr, _env-promise_<Env>> is true, then:completion_signatures< _SET-VALUE-SIG_(_await-result-type_<NewSndr, _env-promise_<Env>>), set_error_t(exception_ptr), set_stopped_t()>
- Otherwise, CS is ill-formed.
Let rcvr be an rvalue whose type Rcvr models receiver, and let Sndr be the type of a sender such that sender_in<Sndr, env_of_t<Rcvr>> is true.
Let Sigs... be the template arguments of the completion_signatures specialization named by completion_signatures_of_t<Sndr, env_of_t<Rcvr>>.
Let CSO be a completion function.
If sender Sndr or its operation state cause the expression CSO(rcvr, args...)to be potentially evaluated ([basic.def.odr]) then there shall be a signature Sig in Sigs...such that_MATCHING-SIG_(decayed-typeof<CSO>(decltype(args)...), Sig) is true ([exec.general]).
33.9.10 execution​::​connect [exec.connect]
connect connects ([exec.async.ops]) a sender with a receiver.
The name connect denotes a customization point object.
For subexpressions sndr and rcvr, let Sndr be decltype((sndr)) andRcvr be decltype((rcvr)), let new_sndr be the expressiontransform_sender(decltype(get-domain-late(sndr, get_env(rcvr))){}, sndr, get_env(rcvr)) and let DS and DR bedecay_t<decltype((new_sndr))> and decay_t<Rcvr>, respectively.
Let connect-awaitable-promise be the following exposition-only class:
namespace std::execution { struct connect-awaitable-promise : with-await-transform<_connect-awaitable-promise_> { connect-awaitable-promise(DS&, DR& rcvr) noexcept : rcvr(rcvr) {} suspend_always initial_suspend() noexcept { return {}; } [[noreturn]] suspend_always final_suspend() noexcept { terminate(); } [[noreturn]] void unhandled_exception() noexcept { terminate(); } [[noreturn]] void return_void() noexcept { terminate(); } coroutine_handle<> unhandled_stopped() noexcept { set_stopped(std::move(rcvr));return noop_coroutine();} operation-state-task get_return_object() noexcept { return operation-state-task{ coroutine_handle<_connect-awaitable-promise_>::from_promise(*this)};} env_of_t<DR> get_env() const noexcept { return execution::get_env(rcvr);} private: DR& rcvr; };}
Let operation-state-task be the following exposition-only class:namespace std::execution { struct operation-state-task { using operation_state_concept = operation_state_t;using promise_type = connect-awaitable-promise;explicit operation-state-task(coroutine_handle<> h) noexcept : coro(h) {} operation-state-task(operation-state-task&&) = delete;~operation-state-task() { coro.destroy(); } void start() & noexcept { coro.resume();} private: coroutine_handle<> coro; };}
Let V name the type_await-result-type_<DS, _connect-awaitable-promise_>, let Sigs name the typecompletion_signatures< _SET-VALUE-SIG_(V), set_error_t(exception_ptr), set_stopped_t()> and let connect-awaitable be an exposition-only coroutine defined as follows:namespace std::execution { template<class Fun, class... Ts> auto suspend-complete(Fun fun, Ts&&... as) noexcept { auto fn = [&, fun]() noexcept { fun(std::forward<Ts>(as)...); };struct awaiter { decltype(fn) fn; static constexpr bool await_ready() noexcept { return false; } void await_suspend(coroutine_handle<>) noexcept { fn(); } [[noreturn]] void await_resume() noexcept { unreachable(); } };return awaiter{fn};} operation-state-task connect-awaitable(DS sndr, DR rcvr) requires receiver_of<DR, Sigs> { exception_ptr ep;try { if constexpr (same_as<V, void>) { co_await std::move(sndr);co_await suspend-complete(set_value, std::move(rcvr));} else { co_await suspend-complete(set_value, std::move(rcvr), co_await std::move(sndr));} } catch(...) { ep = current_exception();} co_await suspend-complete(set_error, std::move(rcvr), std::move(ep));} }
The expression connect(sndr, rcvr) is expression-equivalent to:
- new_sndr.connect(rcvr) if that expression is well-formed.
Mandates: The type of the expression above satisfies operation_state. - Otherwise, connect-awaitable(new_sndr, rcvr).
Mandates: sender<Sndr> && receiver<Rcvr> is true.
33.9.11 Sender factories [exec.factories]
33.9.11.1 execution​::​schedule [exec.schedule]
schedule obtains a schedule sender ([exec.async.ops]) from a scheduler.
The name schedule denotes a customization point object.
For a subexpression sch, the expression schedule(sch) is expression-equivalent tosch.schedule().
Mandates: The type of sch.schedule() satisfies sender.
If the expressionget_completion_scheduler<set_value_t>(get_env(sch.schedule())) == schis ill-formed or evaluates to false, the behavior of calling schedule(sch) is undefined.
33.9.11.2 execution​::​just, execution​::​just_error, execution​::​just_stopped [exec.just]
just, just_error, and just_stopped are sender factories whose asynchronous operations complete synchronously in their start operation with a value completion operation, an error completion operation, or a stopped completion operation, respectively.
The names just, just_error, and just_stopped denote customization point objects.
Let just-cpo be one ofjust, just_error, or just_stopped.
For a pack of subexpressions ts, let Ts be the pack of types decltype((ts)).
The expression just-cpo(ts...) is ill-formed if
- (movable-value<Ts> &&...) is false, or
- just-cpo is just_error andsizeof...(ts) == 1 is false, or
- just-cpo is just_stopped andsizeof...(ts) == 0 is false.
Otherwise, it is expression-equivalent to_make-sender_(just-cpo, product-type{ts...}).
For just, just_error, and just_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for just-cpo as follows:namespace std::execution { template<> struct impls-for<_decayed-typeof_<_just-cpo_>> : default-impls { static constexpr auto start = [](auto& state, auto& rcvr) noexcept -> void { auto& [...ts] = state;set-cpo(std::move(rcvr), std::move(ts)...);};};}
33.9.11.3 execution​::​read_env [exec.read.env]
read_env is a sender factory for a sender whose asynchronous operation completes synchronously in its start operation with a value completion result equal to a value read from the receiver's associated environment.
read_env is a customization point object.
For some query object q, the expression read_env(q) is expression-equivalent to_make-sender_(read_env, q).
The exposition-only class template impls-for ([exec.snd.general]) is specialized for read_env as follows:namespace std::execution { template<> struct impls-for<_decayed-typeof_<read_env>> : default-impls { static constexpr auto start = [](auto query, auto& rcvr) noexcept -> void { TRY-SET-VALUE(rcvr, query(get_env(rcvr)));};};}
33.9.12 Sender adaptors [exec.adapt]
33.9.12.1 General [exec.adapt.general]
Subclause [exec.adapt] specifies a set of sender adaptors.
The bitwise inclusive or operator is overloaded for the purpose of creating sender chains.
The adaptors also support function call syntax with equivalent semantics.
Unless otherwise specified:
- A sender adaptor is prohibited from causing observable effects, apart from moving and copying its arguments, before the returned sender is connected with a receiver using connect, and start is called on the resulting operation state.
- A parent sender ([exec.async.ops]) with a single child sender sndr has an associated attribute object equal to_FWD-ENV_(get_env(sndr)) ([exec.fwd.env]).
- A parent sender with more than one child sender has an associated attributes object equal to env<>{}.
- When a parent sender is connected to a receiver rcvr, any receiver used to connect a child sender has an associated environment equal to FWD-ENV(get_env(rcvr)).
- These requirements apply to any function that is selected by the implementation of the sender adaptor.
If a sender returned from a sender adaptor specified in [exec.adapt]is specified to include set_error_t(Err)among its set of completion signatures where decay_t<Err> denotes the type exception_ptr, but the implementation does not potentially evaluate an error completion operation with an exception_ptr argument, the implementation is allowed to omit the exception_ptr error completion signature from the set.
33.9.12.2 Closure objects [exec.adapt.obj]
A pipeable sender adaptor closure object is a function object that accepts one or more sender arguments and returns a sender.
For a pipeable sender adaptor closure object c and an expression sndrsuch that decltype((sndr)) models sender, the following expressions are equivalent and yield a sender:c(sndr)sndr | c
Given an additional pipeable sender adaptor closure object d, the expression c | d produces another pipeable sender adaptor closure object e:
e is a perfect forwarding call wrapper ([func.require]) with the following properties:
- Its target object is an object d2 of type decltype(auto(d))direct-non-list-initialized with d.
- It has one bound argument entity, an object c2 of type decltype(auto(c))direct-non-list-initialized with c.
- Its call pattern is d2(c2(arg)), where arg is the argument used in a function call expression of e.
The expression c | d is well-formed if and only if the initializations of the state entities ([func.def]) of eare all well-formed.
An object t of type T is a pipeable sender adaptor closure object if T models derived_from<sender_adaptor_closure<T>>,T has no other base classes of type sender_adaptor_closure<U> for any other type U, andT does not satisfy sender.
The template parameter D for sender_adaptor_closure can be an incomplete type.
Before any expression of type cv D appears as an operand to the | operator,D shall be complete and model derived_from<sender_adaptor_closure<D>>.
The behavior of an expression involving an object of type cv Das an operand to the | operator is undefined if overload resolution selects a program-defined operator| function.
A pipeable sender adaptor object is a customization point object that accepts a sender as its first argument and returns a sender.
If a pipeable sender adaptor object accepts only one argument, then it is a pipeable sender adaptor closure object.
If a pipeable sender adaptor object adaptor accepts more than one argument, then let sndr be an expression such that decltype((sndr)) models sender, let args... be arguments such that adaptor(sndr, args...) is a well-formed expression as specified below, and let BoundArgs be a pack that denotes decltype(auto(args))....
The expression adaptor(args...) produces a pipeable sender adaptor closure object fthat is a perfect forwarding call wrapper with the following properties:
- Its target object is a copy of adaptor.
- Its bound argument entities bound_args consist of objects of types BoundArgs... direct-non-list-initialized withstd​::​forward<decltype((args))>(args)..., respectively.
- Its call pattern is adaptor(rcvr, bound_args...), where rcvr is the argument used in a function call expression of f.
The expression adaptor(args...) is well-formed if and only if the initializations of the bound argument entities of the result, as specified above, are all well-formed.
33.9.12.3 execution​::​starts_on [exec.starts.on]
starts_on adapts an input sender into a sender that will start on an execution agent belonging to a particular scheduler's associated execution resource.
The name starts_on denotes a customization point object.
For subexpressions sch and sndr, if decltype((
sch)) does not satisfy scheduler, ordecltype((sndr)) does not satisfy sender,starts_on(sch, sndr) is ill-formed.
Otherwise, the expression starts_on(sch, sndr) is expression-equivalent to:transform_sender( query-or-default(get_domain, sch, default_domain()),make-sender(starts_on, sch, sndr)) except that sch is evaluated only once.
Let out_sndr and env be subexpressions such that OutSndr is decltype((out_sndr)).
If sender-for<OutSndr, starts_on_t> is false, then the expressions starts_on.transform_env(out_sndr, env) andstarts_on.transform_sender(out_sndr, env) are ill-formed; otherwise
- starts_on.transform_env(out_sndr, env) is equivalent to:auto&& [_, sch, _] = out_sndr;return JOIN-ENV(SCHED-ENV(sch), FWD-ENV(env));
- starts_on.transform_sender(out_sndr, env) is equivalent to:auto&& [_, sch, sndr] = out_sndr;return let_value( schedule(sch),[sndr = std::forward_like<OutSndr>(sndr)]() mutable noexcept(is_nothrow_move_constructible_v<decay_t<OutSndr>>) { return std::move(sndr);});
Let out_sndr be a subexpression denoting a sender returned from starts_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 Envsuch that sender_in<OutSndr, Env> 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 start sndron an execution agent of the associated execution resource of sch.
If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.
33.9.12.4 execution​::​continues_on [exec.continues.on]
continues_on adapts a sender into one that completes on the specified scheduler.
The name continues_on denotes a pipeable sender adaptor object.
For subexpressions sch and sndr, if decltype((sch)) does not satisfy scheduler, ordecltype((sndr)) does not satisfy sender,continues_on(sndr, sch) is ill-formed.
Otherwise, the expression continues_on(sndr, sch) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(continues_on, sch, sndr)) except that sndr is evaluated only once.
The exposition-only class template _impls-for_is specialized for continues_on_t as follows:namespace std::execution { template<> 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)));};};}
Let sndr and env be subexpressions such that Sndr is decltype((sndr)).
If 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:auto [_, data, child] = sndr;return schedule_from(std::move(data), std::move(child));
[Note 1:
This causes the continues_on(sndr, sch) sender to becomeschedule_from(sch, sndr) when it is connected with a receiver whose execution domain does not customize continues_on.
— _end note_]
Let out_sndr be a subexpression denoting a sender returned from continues_on(sndr, sch) 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 Envsuch that sender_in<OutSndr, Env> 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 start sndr on the current execution agent and execute completion operations on out_rcvron an execution agent of the execution resource associated with sch.
If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.
33.9.12.5 execution​::​schedule_from [exec.schedule.from]
schedule_from schedules work dependent on the completion of a sender onto a scheduler's associated execution resource.
[Note 1:
schedule_from is not meant to be used in user code; it is used in the implementation of continues_on.
— _end note_]
The name schedule_from denotes a customization point object.
For some subexpressions sch and sndr, let Sch be decltype((sch)) andSndr be decltype((sndr)).
If Sch does not satisfy scheduler, orSndr does not satisfy sender,schedule_from(sch, sndr) is ill-formed.
Otherwise, the expression schedule_from(sch, sndr) is expression-equivalent to:transform_sender( query-or-default(get_domain, sch, default_domain()),make-sender(schedule_from, sch, sndr)) except that sch is evaluated only once.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for schedule_from_t as follows:namespace std::execution { template<> struct impls-for<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;};}
The member impls-for<schedule_from_t>​::​_get-attrs_is initialized with a callable object equivalent to the following lambda:[](const auto& data, const auto& child) noexcept -> decltype(auto) { return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));}
The member impls-for<schedule_from_t>​::​_get-state_is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below) requires sender_in<_child-type_<Sndr>, env_of_t<Rcvr>> { auto& [_, sch, child] = sndr;using sched_t = decltype(auto(sch));using variant_t = see below;using receiver_t = see below;using operation_t = connect_result_t<schedule_result_t<sched_t>, receiver_t>;constexpr bool nothrow = noexcept(connect(schedule(sch), receiver_t{nullptr}));struct state-type { Rcvr& rcvr; variant_t async-result; operation_t op-state; explicit state-type(sched_t sch, Rcvr& rcvr) noexcept(nothrow) : rcvr(rcvr), op-state(connect(schedule(sch), receiver_t{this})) {} };return state-type{sch, rcvr};}
Objects of the local class state-type can be used to initialize a structured binding.
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...>.
Then variant_t denotes the type variant<monostate, _as-tuple_<Sigs>...>, except with duplicate types removed.
receiver_t is an alias for the following exposition-only class:namespace std::execution { struct receiver-type { using receiver_concept = receiver_t;state-type* state; void set_value() && noexcept { visit( [this]<class Tuple>(Tuple& result) noexcept -> void { if constexpr (<monostate, Tuple>) { auto& [tag, ...args] = result; tag(std::move(_state_->rcvr), std::move(args)...);} },_state_->async-result);} template<class Error> void set_error(Error&& err) && noexcept { execution::set_error(std::move(_state_->rcvr), std::forward<Error>(err));} void set_stopped() && noexcept { execution::set_stopped(std::move(_state_->rcvr));} decltype(auto) get_env() const noexcept { return FWD-ENV(execution::get_env(_state_->rcvr));} };}
The expression in the noexcept clause of the lambda is trueif the construction of the returned state-type object is not potentially throwing; otherwise, false.
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...>;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);};
Let out_sndr be a subexpression denoting a sender returned from schedule_from(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 Envsuch that sender_in<OutSndr, Env> 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 start sndr on the current execution agent and execute completion operations on out_rcvron an execution agent of the execution resource associated with sch.
If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.
33.9.12.6 execution​::​on [exec.on]
The on sender adaptor has two forms:
- on(sch, sndr), which starts a sender sndr on an execution agent belonging to a scheduler sch's associated execution resource and that, upon sndr's completion, transfers execution back to the execution resource on which the on sender was started.
- on(sndr, sch, closure), which upon completion of a sender sndr, transfers execution to an execution agent belonging to a scheduler sch's associated execution resource, then executes a sender adaptor closure closurewith the async results of the sender, and that then transfers execution back to the execution resource on which sndr completed.
The name on denotes a pipeable sender adaptor object.
For subexpressions sch and sndr,on(sch, sndr) is ill-formed if any of the following is true:
- decltype((sch)) does not satisfy scheduler, or
- decltype((sndr)) does not satisfy sender andsndr is not a pipeable sender adaptor closure object ([exec.adapt.obj]), or
- decltype((sndr)) satisfies sender andsndr is also a pipeable sender adaptor closure object.
Otherwise, if decltype((sndr)) satisfies sender, the expression on(sch, sndr) is expression-equivalent to:transform_sender( query-or-default(get_domain, sch, default_domain()),make-sender(on, sch, sndr)) except that sch is evaluated only once.
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.obj]),
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)) except that sndr is evaluated only once.
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 expressions on.transform_env(out_sndr, env) andon.transform_sender(out_sndr, env) are ill-formed.
Otherwise: Let not-a-scheduler be an unspecified empty class type, and let not-a-sender be the exposition-only type:struct not-a-sender { using sender_concept = sender_t;auto get_completion_signatures(auto&&) const { return see below;} };where the member function get_completion_signatures returns an object of a type that is not a specialization of the completion_signatures class template.
The expression on.transform_env(out_sndr, env)has effects equivalent to:auto&& [_, data, _] = out_sndr;if constexpr (scheduler<decltype(data)>) { return JOIN-ENV(SCHED-ENV(std::forward_like<OutSndr>(data)), FWD-ENV(std::forward<Env>(env)));} else { return std::forward<Env>(env);}
The expression on.transform_sender(out_sndr, env)has effects equivalent to:auto&& [_, data, child] = out_sndr;if constexpr (scheduler<decltype(data)>) { auto orig_sch = query-with-default(get_scheduler, env, not-a-scheduler());if constexpr (same_as<decltype(orig_sch), _not-a-scheduler_>) { return not-a-sender{};} else { return continues_on( starts_on(std::forward_like<OutSndr>(data), std::forward_like<OutSndr>(child)), std::move(orig_sch));} } else { auto& [sch, closure] = data;auto orig_sch = query-with-default( get_completion_scheduler<set_value_t>, get_env(child),query-with-default(get_scheduler, env, not-a-scheduler()));if constexpr (same_as<decltype(orig_sch), _not-a-scheduler_>) { return not-a-sender{};} else { return write-env( continues_on( std::forward_like<OutSndr>(closure)( continues_on( write-env(std::forward_like<OutSndr>(child), SCHED-ENV(orig_sch)), sch)), orig_sch),SCHED-ENV(sch));} }
Recommended practice: Implementations should use the return type of _not-a-sender_​::​get_completion_signaturesto inform users that their usage of on is incorrect because there is no available scheduler onto which to restore execution.
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 Envsuch that sender_in<OutSndr, Env> 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
- remember the current scheduler, get_scheduler(get_env(rcvr));
- start sndr on an execution agent belonging to sch's associated execution resource;
- upon sndr's completion, transfer execution back to the execution resource associated with the scheduler remembered in step 1; and
- 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.
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 Envsuch that sender_in<OutSndr, Env> 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
- remember the current scheduler, which is the first of the following expressions that is well-formed:
- get_completion_scheduler<set_value_t>(get_env(sndr))
- get_scheduler(get_env(rcvr));
- start sndr on the current execution agent;
- upon sndr's completion, transfer execution to an agent owned by sch's associated execution resource;
- 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; and
- upon completion of the operation started in the previous step, 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.
If any scheduling operation fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.
33.9.12.7 execution​::​then, execution​::​upon_error, execution​::​upon_stopped [exec.then]
then attaches an invocable as a continuation for an input sender's value completion operation.
upon_error and upon_stopped do the same for the error and stopped completion operations, respectively, sending the result of the invocable as a value completion.
The names then, upon_error, and upon_stoppeddenote pipeable sender adaptor objects.
Let the expression then-cpo be one ofthen, upon_error, or upon_stopped.
For subexpressions sndr and f, if decltype((sndr)) does not satisfy sender, ordecltype((f)) does not satisfy movable-value,then-cpo(sndr, f) is ill-formed.
Otherwise, the expression then-cpo(sndr, f) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(then-cpo, f, sndr)) except that sndr is evaluated only once.
For then, upon_error, and upon_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for then-cpo as follows:namespace std::execution { template<> struct impls-for<_decayed-typeof_<_then-cpo_>> : default-impls { static constexpr auto complete = []<class Tag, class... Args> (auto, auto& fn, auto& rcvr, Tag, Args&&... args) noexcept -> void { if constexpr (same_as<Tag, _decayed-typeof_<set-cpo>>) { TRY-SET-VALUE(rcvr, invoke(std::move(fn), std::forward<Args>(args)...));} else { Tag()(std::move(rcvr), std::forward<Args>(args)...);} };};}
The expression then-cpo(sndr, f) has undefined behavior unless it returns a sender out_sndr that
- invokes f or a copy of such with the value, error, or stopped result datums of sndrfor then, upon_error, and upon_stopped, respectively, using the result value of f as out_sndr's value completion, and
- forwards all other completion operations unchanged.
33.9.12.8 execution​::​let_value, execution​::​let_error, execution​::​let_stopped [exec.let]
let_value, let_error, and let_stopped transform a sender's value, error, and stopped completions, respectively, into a new child asynchronous operation by passing the sender's result datums to a user-specified callable, which returns a new sender that is connected and started.
For let_value, let_error, and let_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.
Let the expression let-cpo be one oflet_value, let_error, or let_stopped.
For a subexpression sndr, let let-env(sndr) be expression-equivalent to the first well-formed expression below:
- SCHED-ENV(get_completion_scheduler<_decayed-typeof_<_set-cpo_>>(get_env(sndr)))
- MAKE-ENV(get_domain, get_domain(get_env(sndr)))
- (void(sndr), env<>{})
The names let_value, let_error, and let_stopped denote pipeable sender adaptor objects.
For subexpressions sndr and f, let F be the decayed type of f.
If decltype((sndr)) does not satisfy sender or if decltype((f)) does not satisfy movable-value, the expression let-cpo(sndr, f) is ill-formed.
If F does not satisfy invocable, the expression let_stopped(sndr, f) is ill-formed.
Otherwise, the expression let-cpo(sndr, f) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr)) except that sndr is evaluated only once.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for let-cpo as follows:namespace std::execution { template<class State, class Rcvr, class... Args> void let-bind(State& state, Rcvr& rcvr, Args&&... args); template<> struct impls-for<_decayed-typeof_<_let-cpo_>> : default-impls { static constexpr auto get-state = see below;static constexpr auto complete = see below;};}
Let receiver2 denote the following exposition-only class template:namespace std::execution { template<class Rcvr, class Env> struct receiver2 { using receiver_concept = receiver_t;template<class... Args> void set_value(Args&&... args) && noexcept { execution::set_value(std::move(rcvr), std::forward<Args>(args)...);} template<class Error> void set_error(Error&& err) && noexcept { execution::set_error(std::move(rcvr), std::forward<Error>(err));} void set_stopped() && noexcept { execution::set_stopped(std::move(rcvr));} decltype(auto) get_env() const noexcept { return see below;} Rcvr& rcvr; Env env; };}
Invocation of the function _receiver2_​::​get_envreturns an object e such that
- decltype(e) models queryable and
- given a query object q, the expression e.query(q) is expression-equivalent to env.query(q) if that expression is valid, otherwise e.query(q) is expression-equivalent to get_env(rcvr).query(q).
impls-for<_decayed-typeof_<_let-cpo_>>​::​_get-state_is initialized with a callable object equivalent to the following:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below { auto& [_, fn, child] = sndr;using fn_t = decay_t<decltype(fn)>;using env_t = decltype(let-env(child));using args_variant_t = see below;using ops2_variant_t = see below;struct state-type { fn_t fn; env_t env; args_variant_t args; ops2_variant_t ops2; };return state-type{std::forward_like<Sndr>(fn), let-env(child), {}, {}};}
Let Sigs be a pack of the arguments to the completion_signatures specialization named bycompletion_signatures_of_t<_child-type_<Sndr>, env_of_t<Rcvr>>.
Let LetSigs be a pack of those types in Sigswith a return type of decayed-typeof<_set-cpo_>.
Let as-tuple be an alias template such that as-tuple<Tag(Args...)> denotes the type decayed-tuple<Args...>.
Then args_variant_t denotes the type variant<monostate, _as-tuple_<LetSigs>...>except with duplicate types removed.
Given a type Tag and a pack Args, let as-sndr2 be an alias template such that as-sndr2<Tag(Args...)> denotes the type call-result-t<Fn, decay_t<Args>&...>.
Then ops2_variant_t denotes the typevariant<monostate, connect_result_t<_as-sndr2_<LetSigs>, receiver2<Rcvr, Env>>...> except with duplicate types removed.
The requires-clause constraining the above lambda is satisfied if and only if the types args_variant_t and ops2_variant_t are well-formed.
The exposition-only function template _let-bind_has effects equivalent to:using args_t = decayed-tuple<Args...>;auto mkop2 = [&] { return connect( apply(std::move(state.fn), state.args.template emplace<args_t>(std::forward<Args>(args)...)),receiver2{rcvr, std::move(state.env)});}; start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
impls-for<_decayed-typeof_<let-cpo>>​::​_complete_is initialized with a callable object equivalent to the following:[]<class Tag, class... Args> (auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void { if constexpr (same_as<Tag, _decayed-typeof_<_set-cpo_>>) { TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward<Args>(args)...));} else { Tag()(std::move(rcvr), std::forward<Args>(args)...);} }
Let sndr and env be subexpressions, and let Sndr be decltype((sndr)).
Ifsender-for<Sndr, _decayed-typeof_<_let-cpo_>>is false, then the expression let-cpo.transform_env(sndr, env)is ill-formed.
Otherwise, it is equal to_JOIN-ENV_(let-env(sndr), FWD-ENV(env)).
Let the subexpression out_sndr denote the result of the invocation let-cpo(sndr, f) or an object equal to 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 ([exec.async.ops]) that, when started:
- invokes f when set-cpo is called with sndr's result datums,
- makes its completion dependent on the completion of a sender returned by f, and
- propagates the other completion operations sent by sndr.
33.9.12.9 execution​::​bulk [exec.bulk]
bulk runs a task repeatedly for every index in an index space.
The name bulk denotes a pipeable sender adaptor object.
For subexpressions sndr, shape, and f, let Shape be decltype(auto(shape)).
If
- decltype((sndr)) does not satisfy sender, or
- Shape does not satisfy integral, or
- decltype((f)) does not satisfy movable-value,
bulk(sndr, shape, f) is ill-formed.
Otherwise, the expression bulk(sndr, shape, f) is expression-equivalent to:
transform_sender(get-domain-early(sndr), make-sender(bulk, product-type{shape, f}, sndr)) except that sndr is evaluated only once.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for bulk_t as follows:namespace std::execution { template<> struct impls-for<bulk_t> : default-impls { static constexpr auto complete = see below;};}
The member impls-for<bulk_t>​::​_complete_is initialized with a callable object equivalent to the following lambda:[]<class Index, class State, class Rcvr, class Tag, class... Args> (Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires see below { if constexpr (same_as<Tag, set_value_t>) { auto& [shape, f] = state;constexpr bool nothrow = noexcept(f(auto(shape), args...));TRY-EVAL(rcvr, [&]() noexcept(nothrow) { for (decltype(auto(shape)) i = 0; i < shape; ++i) { f(auto(i), args...);} Tag()(std::move(rcvr), std::forward<Args>(args)...);}());} else { Tag()(std::move(rcvr), std::forward<Args>(args)...);} }
The expression in the requires-clause of the lambda above is true if and only if Tag denotes a type other than set_value_t or if the expression f(auto(shape), args...) is well-formed.
Let the subexpression out_sndr denote the result of the invocation bulk(sndr, shape, f) or an object equal to 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 ([exec.async.ops]) that, when started,
- on a value completion operation, invokes f(i, args...)for every i of type Shape in [0, shape), where args is a pack of lvalue subexpressions referring to the value completion result datums of the input sender, and
- propagates all completion operations sent by sndr.
33.9.12.10 execution​::​split [exec.split]
split adapts an arbitrary sender into a sender that can be connected multiple times.
Let split-env be the type of an environment such that, given an instance env, the expression get_stop_token(env) is well-formed and has type inplace_stop_token.
The name split denotes a pipeable sender adaptor object.
For a subexpression sndr, let Sndr be decltype((sndr)).
If sender_in<Sndr, _split-env_> is false,split(sndr) is ill-formed.
Otherwise, the expression split(sndr) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(split, {}, sndr)) except that sndr is evaluated only once.
[Note 1:
The default implementation of transform_senderwill have the effect of connecting the sender to a receiver.
It will return a sender with a different tag type.
— _end note_]
Let local-state denote the following exposition-only class template:
namespace std::execution { struct local-state-base { virtual ~local-state-base() = default;virtual void notify() noexcept = 0; };template<class Sndr, class Rcvr> struct local-state : local-state-base { using on-stop-callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, _on-stop-request_>;local-state(Sndr&& sndr, Rcvr& rcvr) noexcept;~local-state();void notify() noexcept override;private: optional<_on-stop-callback_> on_stop; shared-state<Sndr>* sh_state; Rcvr* rcvr; };}
_local-state_(Sndr&& sndr, Rcvr& rcvr) noexcept;
Effects: Equivalent to:auto& [_, data, _] = sndr;this->sh_state = data.sh_state.get();this->_sh_state_->inc-ref();this->rcvr = addressof(rcvr);
Effects: Equivalent to:sh_state->dec-ref();
void _notify_() noexcept override;
Effects: Equivalent to:on_stop.reset(); visit( [this](const auto& tupl) noexcept -> void { apply( [this](auto tag, const auto&... args) noexcept -> void { tag(std::move(*rcvr), args...);}, tupl);},_sh_state_->result);
Let split-receiver denote the following exposition-only class template:namespace std::execution { template<class Sndr> struct split-receiver { using receiver_concept = receiver_t;template<class Tag, class... Args> void complete(Tag, Args&&... args) noexcept { using tuple_t = decayed-tuple<Tag, Args...>;try { _sh_state_->result.template emplace<tuple_t>(Tag(), std::forward<Args>(args)...);} catch (...) { using tuple_t = tuple<set_error_t, exception_ptr>;_sh_state_->result.template emplace<tuple_t>(set_error, current_exception());} _sh_state_->notify();} template<class... Args> void set_value(Args&&... args) && noexcept { complete(execution::set_value, std::forward<Args>(args)...);} template<class Error> void set_error(Error&& err) && noexcept { complete(execution::set_error, std::forward<Error>(err));} void set_stopped() && noexcept { complete(execution::set_stopped);} struct env { shared-state<Sndr>* sh-state; inplace_stop_token query(get_stop_token_t) const noexcept { return _sh-state_->stop_src.get_token();} };env get_env() const noexcept { return env{sh_state};} shared-state<Sndr>* sh_state; };}
Let shared-state denote the following exposition-only class template:namespace std::execution { template<class Sndr> struct shared-state { using variant-type = see below; using state-list-type = see below; explicit shared-state(Sndr&& sndr);void start-op() noexcept; void notify() noexcept; void inc-ref() noexcept; void dec-ref() noexcept; inplace_stop_source stop_src{}; variant-type result{}; state-list-type waiting_states; atomic<bool> completed{false}; atomic<size_t> ref_count{1}; connect_result_t<Sndr, _split-receiver_<Sndr>> op_state; };}
Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<Sndr>.
For type Tag and pack Args, let as-tuple be an alias template such that as-tuple<Tag(Args...)> denotes the type decayed-tuple<Tag, Args...>.
Then variant-type denotes the typevariant<tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple<Sigs>...> but with duplicate types removed.
Let state-list-type be a type that stores a list of pointers to local-state-base objects and that permits atomic insertion.
explicit _shared-state_(Sndr&& sndr);
Effects: Initializes op_state with the result ofconnect(std​::​forward<Sndr>(sndr), split-receiver{this}).
Postconditions: waiting_states is empty, and completed is false.
void _start-op_() noexcept;
Effects: Evaluates inc-ref().
If stop_src.stop_requested() is true, evaluates notify(); otherwise, evaluates start(op_state).
Effects: Atomically does the following:
- Sets completed to true, and
- Exchanges waiting_states with an empty list, storing the old value in a local prior_states.
Then, for each pointer p in prior_states, evaluates p->notify().
Finally, evaluates dec-ref().
Effects: Increments ref_count.
Effects: Decrements ref_count.
If the new value of ref_count is 0, calls delete this.
Synchronization: If an evaluation of dec-ref() does not decrement the ref_count to 0 then synchronizes with the evaluation of dec-ref()that decrements ref_count to 0.
Let split-impl-tag be an empty exposition-only class type.
Given an expression sndr, the expression split.transform_sender(sndr) is equivalent to:auto&& [tag, _, child] = sndr;auto* sh_state = new shared-state{std::forward_like<decltype((sndr))>(child)};return make-sender(split-impl-tag(), shared-wrapper{sh_state, tag});where shared-wrapper is an exposition-only class that manages the reference count of the shared-state object pointed to by sh_state.
shared-wrapper models copyablewith move operations nulling out the moved-from object, copy operations incrementing the reference count by calling sh_state->inc-ref(), and assignment operations performing a copy-and-swap operation.
The destructor has no effect if sh_state is null; otherwise, it decrements the reference count by evaluating sh_state->dec-ref().
The exposition-only class template impls-for ([exec.snd.general]) is specialized for split-impl-tag as follows:namespace std::execution { template<> struct impls-for<_split-impl-tag_> : default-impls { static constexpr auto get-state = see below;static constexpr auto start = see below;};}
The member_impls-for_<_split-impl-tag_>​::​_get-state_is initialized with a callable object equivalent to the following lambda expression:[]<class Sndr>(Sndr&& sndr, auto& rcvr) noexcept { return local-state{std::forward<Sndr>(sndr), rcvr};}
The member_impls-for_<_split-impl-tag_>​::​_start_is initialized with a callable object that has a function call operator equivalent to the following:template<class Sndr, class Rcvr> void operator()(local-state<Sndr, Rcvr>& state, Rcvr& rcvr) const noexcept;
Effects: If state._sh_state_->completed is true, evaluates state.notify() and returns.
Otherwise, does the following in order:
- Evaluatesstate.on_stop.emplace( get_stop_token(get_env(rcvr)),on-stop-request{state._sh_state_->stop_src});
- Then atomically does the following:
- Reads the value c ofstate._sh_state_->completed, and
- Inserts addressof(state) intostate._sh_state_->_waiting_states_if c is false.
- If c is true, calls state.notify() and returns.
- Otherwise, if addressof(state) is the first item added tostate._sh_state_->waiting_states, evaluates state._sh_state_->start-op().
33.9.12.11 execution​::​when_all [exec.when.all]
when_all and when_all_with_variantboth adapt multiple input senders into a sender that completes when all input senders have completed.
when_all only accepts senders with a single value completion signature and on success concatenates all the input senders' value result datums into its own value completion operation.
when_all_with_variant(sndrs...) is semantically equivalent to when_all(into_variant(sndrs)...), where sndrs is a pack of subexpressions whose types model sender.
The names when_all and when_all_with_variant denote customization point objects.
Let sndrs be a pack of subexpressions, let Sndrs be a pack of the types decltype((sndrs))..., and let CD be the type common_type_t<decltype(_get-domain-early_(sndrs))...>.
The expressions when_all(sndrs...) andwhen_all_with_variant(sndrs...) are ill-formed if any of the following is true:
The expression when_all(sndrs...) is expression-equivalent to:transform_sender(CD(), make-sender(when_all, {}, sndrs...))
The exposition-only class template impls-for ([exec.snd.general]) is specialized for when_all_t as follows:namespace std::execution { template<> struct impls-for<when_all_t> : default-impls { static constexpr auto get-attrs = see below;static constexpr auto get-env = see below;static constexpr auto get-state = see below;static constexpr auto start = see below;static constexpr auto complete = see below;};}
The member impls-for<when_all_t>​::​_get-attrs_is initialized with a callable object equivalent to the following lambda expression:[](auto&&, auto&&... child) noexcept { if constexpr (same_as<CD, default_domain>) { return env<>();} else { return MAKE-ENV(get_domain, CD());} }
The member impls-for<when_all_t>​::​_get-env_is initialized with a callable object equivalent to the following lambda expression:[]<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept { return see below;}
Returns an object e such that
- decltype(e) models queryable, and
- e.query(get_stop_token) is expression-equivalent tostate.stop-src.get_token(), and
- given a query object q with type other than cv stop_token_t,e.query(q) is expression-equivalent to get_env(rcvr).query(q).
The member impls-for<when_all_t>​::​_get-state_is initialized with a callable object equivalent to the following lambda expression:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(e) -> decltype(e) { return e;} where e is the expressionstd::forward<Sndr>(sndr).apply(make-state<Rcvr>()) and where make-state is the following exposition-only class template:template<class Sndr, class Env> concept max-1-sender-in = sender_in<Sndr, Env> && (tuple_size_v<value_types_of_t<Sndr, Env, tuple, tuple>> <= 1);enum class _disposition_ { _started_, _error_, _stopped_ }; template<class Rcvr> struct make-state { template<max-1-sender-in<env_of_t<Rcvr>>... Sndrs> auto operator()(auto, auto, Sndrs&&... sndrs) const { using values_tuple = see below;using errors_variant = see below;using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, _on-stop-request_>;struct state-type { void arrive(Rcvr& rcvr) noexcept { if (0 == --count) { complete(rcvr);} } void complete(Rcvr& rcvr) noexcept; atomic<size_t> count{sizeof...(sndrs)}; inplace_stop_source stop_src{}; atomic<_disposition_> disp{disposition::started}; errors_variant errors{}; values_tuple values{}; optional<stop_callback> on_stop{nullopt}; };return state-type{};} };
Let copy-fail be exception_ptrif decay-copying any of the child senders' result datums can potentially throw; otherwise, none-such, where none-such is an unspecified empty class type.
The alias values_tuple denotes the typetuple<value_types_of_t<Sndrs, env_of_t<Rcvr>, decayed-tuple, optional>...> if that type is well-formed; otherwise, tuple<>.
The alias errors_variant denotes the type variant<_none-such_, _copy-fail_, Es...>with duplicate types removed, where Es is the pack of the decayed types of all the child senders' possible error result datums.
The membervoid state-type_​::​_complete(Rcvr& rcvr) noexceptbehaves as follows:
- If disp is equal to disposition_​::​_started, evaluates:auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); };auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); };on_stop.reset(); apply( [&](auto&... opts) noexcept { apply(set, tuple_cat(tie(*opts)...));}, values);
- Otherwise, if disp is equal to disposition_​::​_error, evaluates:on_stop.reset(); visit( [&]<class Error>(Error& error) noexcept { if constexpr (
<Error, _none-such_>) { set_error(std::move(rcvr), std::move(error));} }, errors);
- Otherwise, evaluates:on_stop.reset(); set_stopped(std::move(rcvr));
The member impls-for<when_all_t>​::​_start_is initialized with a callable object equivalent to the following lambda expression:[]<class State, class Rcvr, class... Ops>( State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void { state.on_stop.emplace( get_stop_token(get_env(rcvr)),on-stop-request{state.stop_src});if (state.stop_src.stop_requested()) { state._on_stop._reset(); set_stopped(std::move(rcvr));} else { (start(ops), ...);} }
The member _impls-for<when_all_t>​::​complete_is initialized with a callable object equivalent to the following lambda expression:[]<class Index, class State, class Rcvr, class Set, class... Args>( this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void { if constexpr (same_as<Set, set_error_t>) { if (disposition::error != state.disp.exchange(disposition::error)) { state.stop_src.request_stop();TRY-EMPLACE-ERROR(state.errors, std::forward<Args>(args)...);} } else if constexpr (same_as<Set, set_stopped_t>) { auto expected = disposition::started;if (state.disp.compare_exchange_strong(expected, disposition::stopped)) { state.stop_src.request_stop();} } else if constexpr (<decltype(State::values), tuple<>>) { if (state.disp == disposition::started) { auto& opt = getIndex::value\(state.values);TRY-EMPLACE-VALUE(complete, opt, std::forward<Args>(args)...);} } state.arrive(rcvr);} where TRY-EMPLACE-ERROR(v, e), for subexpressions v and e, is equivalent to:try { v.template emplace<decltype(auto(e))>(e);} catch (...) { v.template emplace<exception_ptr>(current_exception());} if the expression decltype(auto(e))(e) is potentially throwing; otherwise, v.template emplace<decltype(auto(e))>(e); and where TRY-EMPLACE-VALUE(c, o, as...), for subexpressions c, o, and pack of subexpressions as, is equivalent to:try { o.emplace(as...);} catch (...) { c(Index(), state, rcvr, set_error, current_exception());return;} if the expression decayed-tuple<decltype(as)...>{as...}is potentially throwing; otherwise, o.emplace(as...).
The expression when_all_with_variant(sndrs...)is expression-equivalent to:transform_sender(CD(), make-sender(when_all_with_variant, {}, sndrs...));
Given subexpressions sndr and env, ifsender-for<decltype((sndr)), when_all_with_variant_t>is false, then the expression when_all_with_variant.transform_sender(sndr, env)is ill-formed; otherwise, it is equivalent to:auto&& [_, _, ...child] = sndr;return when_all(into_variant(std::forward_like<decltype((sndr))>(child))...);
[Note 1:
This causes the when_all_with_variant(sndrs...) sender to become when_all(into_variant(sndrs)...)when it is connected with a receiver whose execution domain does not customize when_all_with_variant.
— _end note_]
33.9.12.12 execution​::​into_variant [exec.into.variant]
into_variant adapts a sender with multiple value completion signatures into a sender with just one value completion signature consisting of a variant of tuples.
The name into_variant denotes a pipeable sender adaptor object.
For a subexpression sndr, let Sndr be decltype((sndr)).
If Sndr does not satisfy sender,into_variant(sndr) is ill-formed.
Otherwise, the expression into_variant(sndr)is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(into_variant, {}, sndr)) except that sndr is only evaluated once.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for into_variant as follows:namespace std::execution { template<> struct impls-for<into_variant_t> : default-impls { static constexpr auto get-state = see below;static constexpr auto complete = see below;};}
The member impls-for<into_variant_t>​::​_get-state_is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> type_identity<value_types_of_t<_child-type_<Sndr>, env_of_t<Rcvr>>> { return {};}
The member impls-for<into_variant_t>​::​_complete_is initialized with a callable object equivalent to the following lambda:[]<class State, class Rcvr, class Tag, class... Args>( auto, State, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void { if constexpr (same_as<Tag, set_value_t>) { using variant_type = typename State::type;TRY-SET-VALUE(rcvr, variant_type(decayed-tuple<Args...>{std::forward<Args>(args)...}));} else { Tag()(std::move(rcvr), std::forward<Args>(args)...);} }
33.9.12.13 execution​::​stopped_as_optional [exec.stopped.opt]
stopped_as_optional maps a sender's stopped completion operation into a value completion operation as a disengaged optional.
The sender's value completion operation is also converted into an optional.
The result is a sender that never completes with stopped, reporting cancellation by completing with a disengaged optional.
The name stopped_as_optional denotes a pipeable sender adaptor object.
For a subexpression sndr, let Sndr be decltype((sndr)).
The expression stopped_as_optional(sndr) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(stopped_as_optional, {}, sndr)) except that sndr is only evaluated once.
Let sndr and env be subexpressions such that Sndr is decltype((sndr)) andEnv is decltype((env)).
If sender-for<Sndr, stopped_as_optional_t>is false, or if the type single-sender-value-type<Sndr, Env>is ill-formed or void, then the expression stopped_as_optional.transform_sender(sndr, env)is ill-formed; otherwise, it is equivalent to:auto&& [_, _, child] = sndr;using V = single-sender-value-type<Sndr, Env>;return let_stopped( then(std::forward_like<Sndr>(child),[]<class... Ts>(Ts&&... ts) noexcept(is_nothrow_constructible_v<V, Ts...>) { return optional<V>(in_place, std::forward<Ts>(ts)...);}),[]() noexcept { return just(optional<V>()); });
33.9.12.14 execution​::​stopped_as_error [exec.stopped.err]
stopped_as_error maps an input sender's stopped completion operation into an error completion operation as a custom error type.
The result is a sender that never completes with stopped, reporting cancellation by completing with an error.
The name stopped_as_error denotes a pipeable sender adaptor object.
For some subexpressions sndr and err, let Sndr be decltype((sndr)) and let Err be decltype((err)).
If the type Sndr does not satisfy sender or if the type Err does not satisfy movable-value,stopped_as_error(sndr, err) is ill-formed.
Otherwise, the expression stopped_as_error(sndr, err)is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(stopped_as_error, err, sndr)) except that sndr is only evaluated once.
Let sndr and env be subexpressions such that Sndr is decltype((sndr)) andEnv is decltype((env)).
If sender-for<Sndr, stopped_as_error_t> is false, then the expression stopped_as_error.transform_sender(sndr, env)is ill-formed; otherwise, it is equivalent to:auto&& [_, err, child] = sndr;using E = decltype(auto(err));return let_stopped( std::forward_like<Sndr>(child),[err = std::forward_like<Sndr>(err)]() mutable noexcept(is_nothrow_move_constructible_v<E>) { return just_error(std::move(err));});
33.9.13 Sender consumers [exec.consumers]
33.9.13.1 this_thread​::​sync_wait [exec.sync.wait]
this_thread​::​sync_wait and this_thread​::​sync_wait_with_variantare used to block the current thread of execution until the specified sender completes and to return its async result.
sync_wait mandates that the input sender has exactly one value completion signature.
Let sync-wait-env be the following exposition-only class type:namespace std::this_thread { struct sync-wait-env { execution::run_loop* loop; auto query(execution::get_scheduler_t) const noexcept { return _loop_->get_scheduler();} auto query(execution::get_delegation_scheduler_t) const noexcept { return _loop_->get_scheduler();} };}
Let sync-wait-result-type and_sync-wait-with-variant-result-type_be exposition-only alias templates defined as follows:namespace std::this_thread { template<execution::sender_in<_sync-wait-env_> Sndr> using sync-wait-result-type = optional<execution::value_types_of_t<Sndr, _sync-wait-env_, _decayed-tuple_, type_identity_t>>;template<execution::sender_in<_sync-wait-env_> Sndr> using sync-wait-with-variant-result-type = optional<execution::value_types_of_t<Sndr, _sync-wait-env_>>;}
The name this_thread​::​sync_wait denotes a customization point object.
For a subexpression sndr, let Sndr be decltype((sndr)).
If sender_in<Sndr, _sync-wait-env_>is false, the expression this_thread​::​sync_wait(sndr) is ill-formed.
Otherwise, it is expression-equivalent to the following, except that sndr is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait, sndr) Mandates:
- The type sync-wait-result-type<Sndr> is well-formed.
- same_as<decltype(e), _sync-wait-result-type_<Sndr>>is true, where e is the apply_sender expression above.
Let sync-wait-state and _sync-wait-receiver_be the following exposition-only class templates:namespace std::this_thread { template<class Sndr> struct sync-wait-state { execution::run_loop loop; exception_ptr error; sync-wait-result-type<Sndr> result; };template<class Sndr> struct sync-wait-receiver { using receiver_concept = execution::receiver_t;sync-wait-state<Sndr>* state; template<class... Args> void set_value(Args&&... args) && noexcept;template<class Error> void set_error(Error&& err) && noexcept;void set_stopped() && noexcept;sync-wait-env get_env() const noexcept { return {&_state_->loop}; } };}
template<class... Args> void set_value(Args&&... args) && noexcept;
Effects: Equivalent to:try { _state_->result.emplace(std::forward<Args>(args)...);} catch (...) { _state_->error = current_exception();} _state_->loop.finish();
template<class Error> void set_error(Error&& err) && noexcept;
Effects: Equivalent to:_state_->error = AS-EXCEPT-PTR(std::forward<Error>(err)); _state_->loop.finish();
void set_stopped() && noexcept;
Effects: Equivalent to _state_->loop.finish().
For a subexpression sndr, let Sndr be decltype((sndr)).
If sender_to<Sndr, _sync-wait-receiver_<Sndr>>is false, the expression sync_wait.apply_sender(sndr) is ill-formed; otherwise, it is equivalent to:sync-wait-state<Sndr> state;auto op = connect(sndr, sync-wait-receiver<Sndr>{&state}); start(op); state.loop.run();if (state.error) { rethrow_exception(std::move(state.error));} return std::move(state.result);
The behavior of this_thread​::​sync_wait(sndr) is undefined unless:
- It blocks the current thread of execution ([defns.block]) with forward progress guarantee delegation ([intro.progress]) until the specified sender completes.
[Note 1:
The default implementation of sync_wait achieves forward progress guarantee delegation by providing a run_loop scheduler via the get_delegation_scheduler query on the sync-wait-receiver's environment.
The run_loop is driven by the current thread of execution.
— _end note_] - It returns the specified sender's async results as follows:
33.9.13.2 this_thread​::​sync_wait_with_variant [exec.sync.wait.var]
The name this_thread​::​sync_wait_with_variant denotes a customization point object.
For a subexpression sndr, let Sndr be decltype(into_variant(sndr)).
If sender_in<Sndr, _sync-wait-env_>is false,this_thread​::​sync_wait_with_variant(sndr) is ill-formed.
Otherwise, it is expression-equivalent to the following, except sndr is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait_with_variant, sndr) Mandates:
- The type sync-wait-with-variant-result-type<Sndr>is well-formed.
- same_as<decltype(e), _sync-wait-with-variant-result-type_<Sndr>>is true, where e is the apply_sender expression above.
If callable<sync_wait_t, Sndr> is false, the expression sync_wait_with_variant.apply_sender(sndr) is ill-formed.
Otherwise, it is equivalent to:using result_type = sync-wait-with-variant-result-type<Sndr>;if (auto opt_value = sync_wait(into_variant(sndr))) { return result_type(std::move(get<0>(*opt_value)));} return result_type(nullopt);
The behavior of this_thread​::​sync_wait_with_variant(sndr)is undefined unless:
- It blocks the current thread of execution ([defns.block]) with forward progress guarantee delegation ([intro.progress]) until the specified sender completes.
[Note 1:
The default implementation of sync_wait_with_variant achieves forward progress guarantee delegation by relying on the forward progress guarantee delegation provided by sync_wait.
— _end note_] - It returns the specified sender's async results as follows:
33.10 Sender/receiver utilities [exec.util]
33.10.1 execution​::​completion_signatures [exec.util.cmplsig]
completion_signatures is a type that encodes a set of completion signatures ([exec.async.ops]).
[Example 1: struct my_sender { using sender_concept = sender_t;using completion_signatures = execution::completion_signatures< set_value_t(), set_value_t(int, float), set_error_t(exception_ptr), set_error_t(error_code), set_stopped_t()>;};
Declares my_sender to be a sender that can complete by calling one of the following for a receiver expression rcvr:
- set_value(rcvr)
- set_value(rcvr, int{...}, float{...})
- set_error(rcvr, exception_ptr{...})
- set_error(rcvr, error_code{...})
- set_stopped(rcvr)
— _end example_]
This subclause makes use of the following exposition-only entities:template<class Fn> concept completion-signature = see below;
A type Fn satisfies completion-signatureif and only if it is a function type with one of the following forms:
- set_value_t(Vs...), where Vs is a pack of object or reference types.
- set_error_t(Err), where Err is an object or reference type.
template<bool> struct indirect-meta-apply { template<template<class...> class T, class... As> using meta-apply = T<As...>; };template<class...> concept always-true = true; template<class Tag,valid-completion-signatures Completions,template<class...> class Tuple,template<class...> class Variant> using gather-signatures = see below;
Let Fns be a pack of the arguments of the completion_signatures specialization named by Completions, let TagFns be a pack of the function types in Fnswhose return types are Tag, and let be a pack of the function argument types in the n-th type in TagFns.
Then, given two variadic templates Tuple and Variant, the type gather-signatures<Tag, Completions, Tuple, Variant>names the type_META-APPLY_(Variant, META-APPLY(Tuple, Ts...),META-APPLY(Tuple, Ts...), …,META-APPLY(Tuple, Ts...)) where m is the size of the pack TagFns andMETA-APPLY(T, As...) is equivalent to:typename indirect-meta-apply<_always-true_<As...>>::template meta-apply<T, As...>
[Note 1:
The purpose of META-APPLY is to make it valid to use non-variadic templates as Variant and Tuple arguments to gather-signatures.
— _end note_]
namespace std::execution { template<completion-signature... Fns> struct completion_signatures {};template<class Sndr, class Env = env<>,template<class...> class Tuple = decayed-tuple,template<class...> class Variant = _variant-or-empty_> requires sender_in<Sndr, Env> using value_types_of_t = gather-signatures<set_value_t, completion_signatures_of_t<Sndr, Env>, Tuple, Variant>;template<class Sndr, class Env = env<>,template<class...> class Variant = _variant-or-empty_> requires sender_in<Sndr, Env> using error_types_of_t = gather-signatures<set_error_t, completion_signatures_of_t<Sndr, Env>, type_identity_t, Variant>;template<class Sndr, class Env = env<>> requires sender_in<Sndr, Env> constexpr bool sends_stopped = <_type-list_<>,gather-signatures<set_stopped_t, completion_signatures_of_t<Sndr, Env>,type-list, _type-list_>>;}
33.10.2 execution​::​transform_completion_signatures [exec.util.cmplsig.trans]
transform_completion_signatures is an alias template used to transform one set of completion signatures into another.
It takes a set of completion signatures and several other template arguments that apply modifications to each completion signature in the set to generate a new specialization of completion_signatures.
[Example 1:
Given a sender Sndr and an environment Env, adapt the completion signatures of Sndr by lvalue-ref qualifying the values, adding an additional exception_ptr error completion if it is not already there, and leaving the other completion signatures alone.
template<class... Args> using my_set_value_t = completion_signatures< set_value_t(add_lvalue_reference_t<Args>...)>;using my_completion_signatures = transform_completion_signatures< completion_signatures_of_t<Sndr, Env>, completion_signatures<set_error_t(exception_ptr)>, my_set_value_t>; — _end example_]
This subclause makes use of the following exposition-only entities:template<class... As> using default-set-value = completion_signatures<set_value_t(As...)>;template<class Err> using default-set-error = completion_signatures<set_error_t(Err)>;
namespace std::execution { template<valid-completion-signatures InputSignatures,valid-completion-signatures AdditionalSignatures = completion_signatures<>,template<class...> class SetValue = default-set-value,template<class> class SetError = default-set-error,valid-completion-signatures SetStopped = completion_signatures<set_stopped_t()>> using transform_completion_signatures = completion_signatures<_see below_>;}
SetValue shall name an alias template such that for any pack of types As, the type SetValue<As...> is either ill-formed or elsevalid-completion-signatures<SetValue<As...>> is satisfied.
SetError shall name an alias template such that for any type Err,SetError<Err> is either ill-formed or elsevalid-completion-signatures<SetError<Err>> is satisfied.
Let Vs be a pack of the types in the type-list named by_gather-signatures_<set_value_t, InputSignatures, SetValue, _type-list_>.
Let Es be a pack of the types in the type-list named by_gather-signatures_<set_error_t, InputSignatures, type_identity_t, error-list_>, where error-list is an alias template such that error-list<Ts...> is_type-list<SetError<Ts>...>.
Let Ss name the type completion_signatures<> if_gather-signatures_<set_stopped_t, InputSignatures, _type-list_, _type-list_>is an alias for the type type-list<>; otherwise, SetStopped.
If any of the above types are ill-formed, thentransform_completion_signatures<InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped> is ill-formed.
Otherwise,transform_completion_signatures<InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped> is the type completion_signatures<Sigs...>where Sigs... is the unique set of types in all the template arguments of all the completion_signatures specializations in the setAdditionalSignatures, Vs..., Es..., Ss.
33.11 Queryable utilities [exec.envs]
33.11.1 Class template prop [exec.prop]
namespace std::execution { template<class QueryTag, class ValueType> struct prop { QueryTag query_; ValueType value_; constexpr const ValueType& query(QueryTag) const noexcept { return value_;} };template<class QueryTag, class ValueType> prop(QueryTag, ValueType) -> prop<QueryTag, unwrap_reference_t<ValueType>>;}
Class template prop is for building a queryable object from a query object and a value.
Mandates: callable<QueryTag, _prop-like_<ValueType>>is modeled, where prop-like is the following exposition-only class template:template<class ValueType> struct prop-like { const ValueType& query(auto) const noexcept;};
[Example 1: template<sender Sndr>sender auto parameterize_work(Sndr sndr) { auto e = prop(get_allocator, my_alloc{});return write_env(sndr, e);} — _end example_]
Specializations of prop are not assignable.
33.11.2 Class template env [exec.env]
namespace std::execution { template<queryable... Envs> struct env { Envs ; Envs ; â‹® Envs ; template<class QueryTag> constexpr decltype(auto) query(QueryTag q) const noexcept(see below);};template<class... Envs> env(Envs...) -> env<unwrap_reference_t<Envs>...>;}
The class template env is used to construct a queryable object from several queryable objects.
Query invocations on the resulting object are resolved by attempting to query each subobject in lexical order.
Specializations of env are not assignable.
[Example 1: template<sender Sndr>sender auto parameterize_work(Sndr sndr) { auto e = env{prop(get_allocator, my_alloc{}), prop(get_scheduler, my_sched{})};return write_env(sndr, e);} — _end example_]
template<class QueryTag> constexpr decltype(auto) query(QueryTag q) const noexcept(_see below_);
Let has-query be the following exposition-only concept:template<class Env, class QueryTag> concept has-query = requires (const Env& env) { env.query(QueryTag());};
Let fe be the first element of, , …, such that the expression fe.query(q) is well-formed.
Constraints: (has-query<Envs, QueryTag> || ...) is true.
Effects: Equivalent to: return fe.query(q);
Remarks: The expression in the noexcept clause is equivalent to noexcept(fe.query(q)).
33.12 Execution contexts [exec.ctx]
33.12.1 execution​::​run_loop [exec.run.loop]
33.12.1.1 General [exec.run.loop.general]
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 that calls run.
A run_loop instance has an associated countthat corresponds to the number of work items that are in its queue.
Additionally, a run_loop instance has an associated state that can be one ofstarting, running, finishing, or finished.
Concurrent invocations of the member functions of run_loopother than run and its destructor do not introduce data races.
The member functions_pop-front_, push-back, and finishexecute atomically.
Recommended practice: Implementations should use an intrusive queue of operation states to hold the work units to make scheduling allocation-free.
namespace std::execution { class run_loop { class run-loop-scheduler; class run-loop-sender; struct run-loop-opstate-base { virtual void execute() = 0; run_loop* loop; run-loop-opstate-base* next; };template<class Rcvr> using run-loop-opstate = unspecified; run-loop-opstate-base* pop-front(); void push-back(run-loop-opstate-base*); public: run_loop() noexcept; run_loop(run_loop&&) = delete;~run_loop();run-loop-scheduler get_scheduler();void run();void finish();};}
33.12.1.2 Associated types [exec.run.loop.types]
class _run-loop-scheduler_;
run-loop-scheduler is an unspecified type that models scheduler.
Instances of run-loop-scheduler remain valid until the end of the lifetime of the run_loop instance from which they were obtained.
Two instances of run-loop-scheduler compare equal if and only if they were obtained from the same run_loop instance.
Let sch be an expression of type run-loop-scheduler.
The expression schedule(sch)has type run-loop- sender and is not potentially-throwing if sch is not potentially-throwing.
run-loop-sender is an exposition-only type that satisfies sender.
For any type Env,completion_signatures_of_t<_run-loop-sender_, Env> iscompletion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
An instance of run-loop-sender remains valid until the end of the lifetime of its associated run_loop instance.
Let sndr be an expression of type run-loop-sender, let rcvr be an expression such that receiver_of<decltype((_rcvr_)), CS> is truewhere 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(void(sndr), auto(rcvr)) is potentially-throwing.
- The expression get_completion_scheduler<C>(get_env(sndr))is 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<class Rcvr> struct _run-loop-opstate_;
run-loop-opstate<Rcvr>inherits privately and unambiguously from run-loop-opstate-base.
Let o be a non-const lvalue of type run-loop-opstate<Rcvr>, and let REC(o) be a non-const lvalue reference to an instance of type Rcvrthat was initialized with the expression _rcvr_passed to the invocation of connect that returned o.
Then:
- The object to which REC(o) refers remains valid for the lifetime of the object to which o refers.
- The type run-loop-opstate<Rcvr> overrides_run-loop-opstate-base_​::​execute()such that o.execute() is equivalent to:if (get_stop_token(REC(o)).stop_requested()) { set_stopped(std::move(REC(o)));} else { set_value(std::move(REC(o)));}
- The expression start(o) is equivalent to:try { o._loop_->push-back(addressof(o));} catch(...) { set_error(std::move(REC(o)), current_exception());}
33.12.1.3 Constructor and destructor [exec.run.loop.ctor]
Postconditions: count is 0 and state is starting.
Effects: If count is not 0 or if state is running, invokes terminate ([except.terminate]).
Otherwise, has no effects.
33.12.1.4 Member functions [exec.run.loop.members]
_run-loop-opstate-base_* _pop-front_();
Effects: Blocks ([defns.block]) until one of the following conditions is true:
- count is 0 and state is finishing, in which case pop-front sets state to _finished_and returns nullptr; or
- count is greater than 0, in which case an item is removed from the front of the queue,count is decremented by 1, and the removed item is returned.
void _push-back_(_run-loop-opstate-base_* item);
Effects: Adds item to the back of the queue and increments count by 1.
Synchronization: This operation synchronizes with the pop-front operation that obtains item.
_run-loop-scheduler_ get_scheduler();
Returns: An instance of _run-loop-scheduler_that can be used to schedule work onto this run_loop instance.
Preconditions: state is either starting or finishing.
Effects: If state is starting, sets the state to running, otherwise leaves state unchanged.
Then, equivalent to:while (auto* op = pop-front()) { op->execute();}
Remarks: When state changes, it does so without introducing data races.
Preconditions: state is either starting or running.
Effects: Changes state to finishing.
Synchronization: finish synchronizes with the pop-front operation that returns nullptr.
33.13 Coroutine utilities [exec.coro.util]
33.13.1 execution​::​as_awaitable [exec.as.awaitable]
as_awaitable transforms an object into one that is awaitable within a particular coroutine.
Subclause [exec.coro.util] 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<Promise>> && sender_to<Sndr, _awaitable-receiver_> && requires (Promise& p) { { p.unhandled_stopped() } -> convertible_to<coroutine_handle<>>;};template<class Sndr, class Promise> class sender-awaitable; }
The type sender-awaitable<Sndr, Promise> is equivalent to:
namespace std::execution { template<class Sndr, class Promise> class sender-awaitable { struct unit {}; using value-type = single-sender-value-type<Sndr, env_of_t<Promise>>;using result-type = conditional_t<is_void_v<_value-type_>, unit, _value-type_>;struct awaitable-receiver; variant<monostate, _result-type_, exception_ptr> result{}; connect_result_t<Sndr, _awaitable-receiver_> state; public: sender-awaitable(Sndr&& sndr, Promise& p);static constexpr bool await_ready() noexcept { return false; } void await_suspend(coroutine_handle<Promise>) noexcept { start(state); } value-type await_resume();};}
awaitable-receiver is equivalent to:struct awaitable-receiver { using receiver_concept = receiver_t; variant<monostate, _result-type_, exception_ptr>* result-ptr; coroutine_handle<Promise> continuation; };
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 subexpressions, and let err be an expression of type Err.
Then:
- If constructible_from<_result-type_, decltype((vs))...>is satisfied, the expression set_value(
rcvr, vs...) is equivalent to:try { rcvr._result-ptr_->template emplace<1>(vs...);} catch(...) { rcvr._result-ptr_->template emplace<2>(current_exception());}rcvr.continuation.resume();
Otherwise, set_value(rcvr, vs...) is ill-formed. - The expression set_error(rcvr, err) is equivalent to:rcvr._result-ptr_->template emplace<2>(AS-EXCEPT-PTR(err)); rcvr.continuation.resume();
- The expression set_stopped(rcvr) is equivalent to:static_cast<coroutine_handle<>>(rcvr.continuation.promise().unhandled_stopped()).resume();
- For any expression tagwhose 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...)
_sender-awaitable_(Sndr&& sndr, Promise& p);
Effects: Initializes state withconnect(std::forward<Sndr>(sndr),awaitable-receiver{addressof(result), coroutine_handle<Promise>::from_promise(p)})
_value-type_ await_resume();
Effects: Equivalent to:if (result.index() == 2) rethrow_exception(get<2>(result));if constexpr (!is_void_v<_value-type_>) return std::forward<_value-type_>(get<1>(result));
as_awaitable is a customization point object.
For subexpressions expr and pwhere p is an lvalue,Expr names the type decltype((expr)) andPromise names the type decay_t<decltype((p))>,as_awaitable(expr, p) is expression-equivalent to, except that the evaluations of expr and pare indeterminately sequenced:
- 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. - Otherwise, (void(p), expr)if is-awaitable<Expr, U> is true, where U is an unspecified class type that is not Promise and that lacks a member named await_transform.
Preconditions: is-awaitable<Expr, Promise> is true and the expression co_await exprin a coroutine with promise type U is expression-equivalent to the same expression in a coroutine with promise type Promise. - Otherwise, sender-awaitable{expr, p}if awaitable-sender<Expr, Promise> is true.
- Otherwise, (void(p), expr).
33.13.2 execution​::​with_awaitable_senders [exec.with.awaitable.senders]
with_awaitable_senders, when used as the base class of a coroutine promise type, makes senders awaitable in that coroutine type.
In addition, it provides a default implementation of unhandled_stoppedsuch that if a sender completes by calling set_stopped, it is treated as if an uncatchable "stopped" exception were thrown from the await-expression.
[Note 1:
The coroutine is never resumed, and the unhandled_stopped of the coroutine caller's promise type is called.
— _end note_]
namespace std::execution { template<class-type Promise> struct with_awaitable_senders { template<class OtherPromise> requires (<OtherPromise, void>) void set_continuation(coroutine_handle<OtherPromise> h) noexcept; coroutine_handle<> continuation() const noexcept { return continuation; } coroutine_handle<> unhandled_stopped() noexcept { return stopped-handler(continuation.address());} template<class Value> see below await_transform(Value&& value);private: [[noreturn]] static coroutine_handle<> default-unhandled-stopped(void*) noexcept { terminate();} coroutine_handle<> continuation{}; coroutine_handle<> (*stopped-handler)(void*) noexcept = &default-unhandled-stopped;};}
template<class OtherPromise> requires (<OtherPromise, void>) void set_continuation(coroutine_handle<OtherPromise> h) noexcept;
Effects: Equivalent to:continuation = h;if constexpr ( requires(OtherPromise& other) { other.unhandled_stopped(); } ) { stopped-handler = [](void* p) noexcept -> coroutine_handle<> { return coroutine_handle<OtherPromise>::from_address(p) .promise().unhandled_stopped();};} else { stopped-handler = &default-unhandled-stopped;}
template<class Value> _call-result-t_<as_awaitable_t, Value, Promise&> await_transform(Value&& value);
Effects: Equivalent to:return as_awaitable(std::forward<Value>(value), static_cast<Promise&>(*this));