diff --git a/source/config.tex b/source/config.tex index 11a7c28fe9..50adcc3a99 100644 --- a/source/config.tex +++ b/source/config.tex @@ -10,4 +10,4 @@ %% Library chapters \newcommand{\firstlibchapter}{support} -\newcommand{\lastlibchapter}{thread} +\newcommand{\lastlibchapter}{exec} diff --git a/source/exceptions.tex b/source/exceptions.tex index 8473cd1c64..67e052c476 100644 --- a/source/exceptions.tex +++ b/source/exceptions.tex @@ -1084,7 +1084,27 @@ \item% when a call to a \tcode{wait()}, \tcode{wait_until()}, or \tcode{wait_for()} function on a condition variable\iref{thread.condition.condvar,thread.condition.condvarany} -fails to meet a postcondition. +fails to meet a postcondition, or + +\item% +when a callback invocation exits via an exception +when requesting stop on +a \tcode{std::stop_source} or +a \tcode{std::in\-place_stop_source}\iref{stopsource.mem,stopsource.inplace.mem}, +or in the constructor of +\tcode{std::stop_callback} or +\tcode{std::inplace_stop_callback}\iref{stopcallback.cons,stopcallback.inplace.cons} +when a callback invocation exits via an exception, or + +\item% +when a \tcode{run_loop} object is destroyed +that is still in the \tcode{running} state\iref{exec.run.loop}, or + +\item% +when \tcode{unhandled_stopped} is called on +a \tcode{with_awaitable_senders} object\iref{exec.with.awaitable.senders} +whose continuation is not a handle to a coroutine +whose promise type has an \tcode{unhandled_stopped} member function. \end{itemize} diff --git a/source/exec.tex b/source/exec.tex new file mode 100644 index 0000000000..1bd683d8fe --- /dev/null +++ b/source/exec.tex @@ -0,0 +1,5465 @@ +%!TEX root = std.tex +\rSec0[exec]{Execution control library} + +\rSec1[exec.general]{General} + +\pnum +This Clause describes components +supporting execution of function objects\iref{function.objects}. + +\pnum +The following subclauses describe +the requirements, concepts, and components +for execution control primitives as summarized in \tref{exec.summary}. + +\begin{libsumtab}{Execution control library summary}{exec.summary} +\ref{exec.sched} & Schedulers & \tcode{} \\ +\ref{exec.recv} & Receivers & \\ +\ref{exec.opstate} & Operation states & \\ +\ref{exec.snd} & Senders & \\ +\end{libsumtab} + +\pnum +\tref{exec.pos} shows +the types of customization point objects\iref{customization.point.object} +used in the execution control library. + +\begin{floattable}{Types of customization point objects in the execution control library}{exec.pos}{lx{0.23\hsize}x{0.45\hsize}} +\topline +\lhdr{Customization point} & \chdr{Purpose} & \rhdr{Examples} \\ +\lhdr{object type} & & \\ +\capsep +core & + provide core execution functionality, and connection between core components & + e.g., \tcode{connect}, \tcode{start} \\ +completion functions & + called by senders to announce the completion of the work (success, error, or cancellation) & + \tcode{set_value}, \tcode{set_error}, \tcode{set_stopped} \\ +senders & + allow the specialization of the provided sender algorithms & + \begin{itemize} + \item sender factories (e.g., \tcode{schedule}, \tcode{just}, \tcode{read_env}) + \item sender adaptors (e.g., \tcode{continues_on}, \tcode{then}, \tcode{let_value}) + \item sender consumers (e.g., \tcode{sync_wait}) + \end{itemize} + \\ +queries & + allow querying different properties of objects & + \begin{itemize} + \item general queries (e.g., \tcode{get_allocator}, \tcode{get_stop_token}) + \item environment queries (e.g., \tcode{get_scheduler}, \tcode{get_delegation_scheduler}) + \item scheduler queries (e.g., \tcode{get_forward_progress_guarantee}) + \item sender attribute queries (e.g., \tcode{get_completion_scheduler}) + \end{itemize} + \\ +\end{floattable} + +\pnum +This clause makes use of the following exposition-only entities. + +\pnum +For a subexpression \tcode{expr}, +let \tcode{\exposid{MANDATE-NOTHROW}(expr)} be +expression-equivalent to \tcode{expr}. + +\mandates +\tcode{noexcept(expr)} is \tcode{true}. + +\pnum +\begin{codeblock} +namespace std { + template + concept @\defexposconcept{movable-value}@ = // \expos + @\libconcept{move_constructible}@> && + @\libconcept{constructible_from}@, T> && + (!is_array_v>); +} +\end{codeblock} + +\pnum +For function types \tcode{F1} and \tcode{F2} denoting +\tcode{R1(Args1...)} and \tcode{R2(Args2...)}, respectively, +\tcode{MATCHING-SIG(F1, F2)} is \tcode{true} if and only if +\tcode{\libconcept{same_as}} +is \tcode{true}. + +\pnum +For a subexpression \tcode{err}, +let \tcode{Err} be \tcode{decltype((err))} and +let \tcode{\exposid{AS-EXCEPT-PTR}(err)} be: +\begin{itemize} +\item +\tcode{err} if \tcode{decay_t} denotes the type \tcode{exception_ptr}. + +\mandates +\tcode{err != exception_ptr()} is \tcode{true}. +\item +Otherwise, +\tcode{make_exception_ptr(system_error(err))} +if \tcode{decay_t} denotes the type \tcode{error_code}. +\item +Otherwise, \tcode{make_exception_ptr(err)}. +\end{itemize} + +\rSec1[exec.queryable]{Queries and queryables} + +\rSec2[exec.queryable.general]{General} + +\pnum +A \defnadj{queryable}{object} is +a read-only collection of key/value pair +where each key is a customization point object known as a \defn{query object}. +A \defn{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. + +\pnum +Let \tcode{q} be a query object, +let \tcode{args} be a (possibly empty) pack of subexpressions, +let \tcode{env} be a subexpression +that refers to a queryable object \tcode{o} of type \tcode{O}, and +let \tcode{cenv} be a subexpression referring to \tcode{o} +such that \tcode{decltype((cenv))} is \tcode{const O\&}. +The expression \tcode{q(env, args...)} is equal to\iref{concepts.equality} +the expression \tcode{q(cenv, args...)}. + +\pnum +The type of a query expression cannot be \tcode{void}. + +\pnum +The expression \tcode{q(env, args...)} is +equality-preserving\iref{concepts.equality} and +does not modify the query object or the arguments. + +\pnum +If the expression \tcode{env.query(q, args...)} is well-formed, +then it is expression-equivalent to \tcode{q(env, args...)}. + +\pnum +Unless otherwise specified, +the result of a query is valid as long as the queryable object is valid. + +\rSec2[exec.queryable.concept]{\tcode{queryable} concept} + +\begin{codeblock} +namespace std { + template + concept @\defexposconcept{queryable}@ = @\libconcept{destructible}@; // \expos +} +\end{codeblock} + +\pnum +The exposition-only \exposconcept{queryable} concept specifies +the constraints on the types of queryable objects. + +\pnum +Let \tcode{env} be an object of type \tcode{Env}. +The type \tcode{Env} models queryable +if for each callable object \tcode{q} and a pack of subexpressions \tcode{args}, +if \tcode{requires \{ q(env, args...) \}} is \tcode{true} then +\tcode{q(env, args...)} meets any semantic requirements imposed by \tcode{q}. + +\rSec1[exec.async.ops]{Asynchronous operations} + +\pnum +An \defnadj{execution}{resource} is a program entity that manages +a (possibly dynamic) set of execution agents\iref{thread.req.lockable.general}, +which it uses to execute parallel work on behalf of callers. +\begin{example} +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. + +\pnum +An \defnadj{asynchronous}{operation} is +a distinct unit of program execution that +\begin{itemize} +\item +is explicitly created; +\item +can be explicitly started once at most; +\item +once started, eventually completes exactly once +with a (possibly empty) set of result datums and +in exactly one of three \defnx{dispositions}{disposition}: +success, failure, or cancellation; +\begin{itemize} +\item +A successful completion, also known as a \defnadj{value}{completion}, +can have an arbitrary number of result datums. +\item +A failure completion, also known as an \defnadj{error}{completion}, +has a single result datum. +\item +A cancellation completion, also known as a \defnadj{stopped completion}, +has no result datum. +\end{itemize} +An asynchronous operation's \defnadj{async}{result} +is its disposition and its (possibly empty) set of result datums. +\item +can complete on a different execution resource +than the execution resource on which it started; and +\item +can create and start other asynchronous operations +called \defnadj{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 \defnadj{parent}{operation} is the asynchronous operation +that created a particular child operation. +\end{itemize} +\begin{note} +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} + +\pnum +An asynchronous operation has associated state +known as its \defnadj{operation}{state}. + +\pnum +An asynchronous operation has an associated environment. +An \defn{environment} is a queryable object\iref{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's operation state owns the operation's environment. + +\pnum +An asynchronous operation has an associated receiver. +A \defn{receiver} is an aggregation of three handlers +for the three asynchronous completion dispositions: +\begin{itemize} +\item a value completion handler for a value completion, +\item an error completion handler for an error completion, and +\item a stopped completion handler for a stopped completion. +\end{itemize} +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. + +\pnum +For each completion disposition, there is a \defnadj{completion}{function}. +A completion function is +a customization point object\iref{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 \defnadj{completion}{tag} +that is the unqualified type of the completion function. +A valid invocation of a completion function is called +a \defnadj{completion}{operation}. + +\pnum +The \defn{lifetime of an asynchronous operation}, +also known as the operation's \defn{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. + +\pnum +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. + +\pnum +A \defn{sender} is a factory for one or more asynchronous operations. +\defnx{Connecting}{connect} 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. + +\pnum +A \defn{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 \defn{schedule-expression} obtains such a sender from a scheduler. +A \defn{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. + +\pnum +An asynchronous operation has one or more associated completion schedulers +for each of its possible dispositions. +A \defn{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. + +\pnum +A sender has an associated queryable object\iref{exec.queryable} +known as its \defnx{attributes}{attribute} +that 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. + +\pnum +A \defn{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\iref{basic.def.odr}. +For a completion function \tcode{set}, +receiver \tcode{rcvr}, and +pack of arguments \tcode{args}, +let \tcode{c} be the completion operation \tcode{set(rcvr, args...)}, and +let \tcode{F} be +the function type \tcode{decltype(auto(set))(decltype((args))...)}. +A completion signature \tcode{Sig} is associated with \tcode{c} +if and only if +\tcode{\exposid{MATCHING-SIG}(Sig, F)} is true\iref{exec.general}). +Together, a sender type and an environment type \tcode{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 \tcode{Env}. +The type of the receiver does not affect +an asynchronous operation's completion signatures, +only the type of the receiver's environment. + +\pnum +A sender algorithm is a function that takes and/or returns a sender. +There are three categories of sender algorithms: +\begin{itemize} +\item +A \defn{sender factory} is a function +that takes non-senders as arguments and that returns a sender. +\item +A \defn{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. +\item +A \defn{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. +\end{itemize} + +\rSec1[execution.syn]{Header \tcode{} synopsis} + +\indexheader{execution}% +\begin{codeblock} +namespace std { + // \ref{execpol.type}, execution policy type trait + template struct is_execution_policy; + template constexpr bool @\libglobal{is_execution_policy_v}@ = is_execution_policy::value; +} + +namespace std::execution { + // \ref{execpol.seq}, sequenced execution policy + class sequenced_policy; + + // \ref{execpol.par}, parallel execution policy + class parallel_policy; + + // \ref{execpol.parunseq}, parallel and unsequenced execution policy + class parallel_unsequenced_policy; + + // \ref{execpol.unseq}, unsequenced execution policy + class unsequenced_policy; + + // \ref{execpol.objects}, execution policy objects + inline constexpr sequenced_policy seq{ @\unspec@ }; + inline constexpr parallel_policy par{ @\unspec@ }; + inline constexpr parallel_unsequenced_policy par_unseq{ @\unspec@ }; + inline constexpr unsequenced_policy unseq{ @\unspec@ }; +} + +namespace std { + // \ref{exec.general}, helper concepts + template + concept @\exposconceptnc{movable-value}@ = @\seebelownc@; // \expos + + template + concept @\defexposconceptnc{decays-to}@ = @\libconcept{same_as}@, To>; // \expos + + template + concept @\defexposconceptnc{class-type}@ = @\exposconceptnc{decays-to}@ && is_class_v; // \expos + + // \ref{exec.queryable}, queryable objects + template + concept @\exposconceptnc{queryable}@ = @\seebelownc@; // \expos + + // \ref{exec.queries}, queries + struct forwarding_query_t { @\unspec@ }; + struct get_allocator_t { @\unspec@ }; + struct get_stop_token_t { @\unspec@ }; + + inline constexpr forwarding_query_t forwarding_query{}; + inline constexpr get_allocator_t get_allocator{}; + inline constexpr get_stop_token_t get_stop_token{}; + + template + using stop_token_of_t = remove_cvref_t()))>; + + template + concept @\defexposconceptnc{forwarding-query}@ = forwarding_query(T{}); // \expos +} + +namespace std::execution { + // \ref{exec.queries}, queries + struct get_domain_t { @\unspec@ }; + struct get_scheduler_t { @\unspec@ }; + struct get_delegation_scheduler_t { @\unspec@ }; + struct get_forward_progress_guarantee_t { @\unspec@ }; + template + struct get_completion_scheduler_t { @\unspec@ }; + + inline constexpr get_domain_t get_domain{}; + inline constexpr get_scheduler_t get_scheduler{}; + inline constexpr get_delegation_scheduler_t get_delegation_scheduler{}; + enum class forward_progress_guarantee; + inline constexpr get_forward_progress_guarantee_t get_forward_progress_guarantee{}; + template + inline constexpr get_completion_scheduler_t get_completion_scheduler{}; + + struct empty_env {}; + struct get_env_t { @\unspec@ }; + inline constexpr get_env_t get_env{}; + + template + using env_of_t = decltype(get_env(declval())); + + // \ref{exec.domain.default}, execution domains + struct default_domain; + + // \ref{exec.sched}, schedulers + struct scheduler_t {}; + + template + concept @\libconcept{scheduler}@ = @\seebelow@; + + // \ref{exec.recv}, receivers + struct receiver_t {}; + + template + concept @\libconcept{receiver}@ = @\seebelow@; + + template + concept @\libconcept{receiver_of}@ = @\seebelow@; + + struct set_value_t { @\unspec@ }; + struct set_error_t { @\unspec@ }; + struct set_stopped_t { @\unspec@ }; + + inline constexpr set_value_t set_value{}; + inline constexpr set_error_t set_error{}; + inline constexpr set_stopped_t set_stopped{}; + + // \ref{exec.opstate}, operation states + struct operation_state_t {}; + + template + concept @\libconcept{operation_state}@ = @\seebelow@; + + struct start_t; + inline constexpr start_t start{}; + + // \ref{exec.snd}, senders + struct sender_t {}; + + template + concept @\libconcept{sender}@ = @\seebelow@; + + template + concept @\libconcept{sender_in}@ = @\seebelow@; + + template + concept @\libconcept{sender_to}@ = @\seebelow@; + + template + struct @\exposidnc{type-list}@; // \expos + + // \ref{exec.getcomplsigs}, completion signatures + struct get_completion_signatures_t; + inline constexpr get_completion_signatures_t get_completion_signatures {}; + + template + requires @\libconcept{sender_in}@ + using completion_signatures_of_t = @\exposid{call-result-t}@; + + template + using @\exposidnc{decayed-tuple}@ = tuple...>; // \expos + + template + using @\exposidnc{variant-or-empty}@ = @\seebelownc@; // \expos + + template class Tuple = @\exposid{decayed-tuple}@, + template class Variant = @\exposid{variant-or-empty}@> + requires sender_in + using value_types_of_t = @\seebelow@; + + template class Variant = @\exposid{variant-or-empty}@> + requires @\libconcept{sender_in}@ + using error_types_of_t = @\seebelow@; + + template + requires @\libconcept{sender_in}@ + inline constexpr bool sends_stopped = @\seebelow@; + + template + using @\exposidnc{single-sender-value-type}@ = @\seebelownc@; // \expos + + template + concept @\exposconcept{single-sender}@ = @\seebelow@; // \expos + + template + using tag_of_t = @\seebelow@; + + // \ref{exec.snd.transform}, sender transformations + template + requires (sizeof...(Env) <= 1) + constexpr @\libconcept{sender}@ decltype(auto) transform_sender( + Domain dom, Sndr&& sndr, const Env&... env) noexcept(@\seebelow@); + + // \ref{exec.snd.transform.env}, environment transformations + template + constexpr @\exposconcept{queryable}@ decltype(auto) transform_env( + Domain dom, Sndr&& sndr, Env&& env) noexcept; + + // \ref{exec.snd.apply}, sender algorithm application + template + constexpr decltype(auto) apply_sender( + Domain dom, Tag, Sndr&& sndr, Args&&... args) noexcept(@\seebelow@); + + // \ref{exec.connect}, the connect sender algorithm + struct connect_t; + inline constexpr connect_t connect{}; + + template + using connect_result_t = + decltype(connect(declval(), declval())); + + // \ref{exec.factories}, sender factories + struct just_t { @\unspec@ }; + struct just_error_t { @\unspec@ }; + struct just_stopped_t { @\unspec@ }; + struct schedule_t { @\unspec@ }; + + inline constexpr just_t just{}; + inline constexpr just_error_t just_error{}; + inline constexpr just_stopped_t just_stopped{}; + inline constexpr schedule_t schedule{}; + inline constexpr unspecified read{}; + + template + using schedule_result_t = decltype(schedule(declval())); + + // \ref{exec.adapt}, sender adaptors + template<@\exposconcept{class-type}@ D> + struct sender_adaptor_closure { }; + + struct starts_on_t { @\unspec@ }; + struct continues_on_t { @\unspec@ }; + struct on_t { @\unspec@ }; + struct schedule_from_t { @\unspec@ }; + struct then_t { @\unspec@ }; + struct upon_error_t { @\unspec@ }; + struct upon_stopped_t { @\unspec@ }; + struct let_value_t { @\unspec@ }; + struct let_error_t { @\unspec@ }; + struct let_stopped_t { @\unspec@ }; + struct bulk_t { @\unspec@ }; + struct split_t { @\unspec@ }; + struct when_all_t { @\unspec@ }; + struct when_all_with_variant_t { @\unspec@ }; + struct into_variant_t { @\unspec@ }; + struct stopped_as_optional_t { @\unspec@ }; + struct stopped_as_error_t { @\unspec@ }; + + inline constexpr starts_on_t starts_on{}; + inline constexpr continues_on_t continues_on{}; + inline constexpr on_t on{}; + inline constexpr schedule_from_t schedule_from{}; + inline constexpr then_t then{}; + inline constexpr upon_error_t upon_error{}; + inline constexpr upon_stopped_t upon_stopped{}; + inline constexpr let_value_t let_value{}; + inline constexpr let_error_t let_error{}; + inline constexpr let_stopped_t let_stopped{}; + inline constexpr bulk_t bulk{}; + inline constexpr split_t split{}; + inline constexpr when_all_t when_all{}; + inline constexpr when_all_with_variant_t when_all_with_variant{}; + inline constexpr into_variant_t into_variant{}; + inline constexpr stopped_as_optional_t stopped_as_optional{}; + inline constexpr stopped_as_error_t stopped_as_error{}; + + // \ref{exec.util}, sender and receiver utilities + // \ref{exec.util.cmplsig} + template + concept @\exposconceptnc{completion-signature}@ = @\seebelownc@; // \expos + + template<@\exposconcept{completion-signature}@... Fns> + struct completion_signatures {}; + + template + concept @\exposconceptnc{valid-completion-signatures}@ = @\seebelownc@; // \expos + + // \ref{exec.util.cmplsig.trans} + template< + @\exposconcept{valid-completion-signatures}@ InputSignatures, + @\exposconcept{valid-completion-signatures}@ AdditionalSignatures = completion_signatures<>, + template class SetValue = @\seebelow@, + template class SetError = @\seebelow@, + @\exposconcept{valid-completion-signatures}@ SetStopped = completion_signatures> + using transform_completion_signatures = completion_signatures<@\seebelow@>; + + template< + @\libconcept{sender}@ Sndr, + class Env = empty_env, + @\exposconcept{valid-completion-signatures}@ AdditionalSignatures = completion_signatures<>, + template class SetValue = @\seebelow@, + template class SetError = @\seebelow@, + @\exposconcept{valid-completion-signatures}@ SetStopped = completion_signatures> + requires @\libconcept{sender_in}@ + using transform_completion_signatures_of = + transform_completion_signatures< + completion_signatures_of_t, + AdditionalSignatures, SetValue, SetError, SetStopped>; + + // \ref{exec.run.loop}, run_loop + class run_loop; +} + +namespace std::this_thread { + // \ref{exec.consumers}, consumers + struct sync_wait_t { @\unspec@ }; + struct sync_wait_with_variant_t { @\unspec@ }; + + inline constexpr sync_wait_t sync_wait{}; + inline constexpr sync_wait_with_variant_t sync_wait_with_variant{}; +} + +namespace std::execution { + // \ref{exec.as.awaitable} + struct as_awaitable_t { @\unspec@ }; + inline constexpr as_awaitable_t as_awaitable{}; + + // \ref{exec.with.awaitable.senders} + template<@\exposconcept{class-type}@ Promise> + struct with_awaitable_senders; +} +\end{codeblock} + +\pnum +The exposition-only type \tcode{\exposid{variant-or-empty}} +is defined as follows: +\begin{itemize} +\item +If \tcode{sizeof...(Ts)} is greater than zero, +\tcode{\exposid{variant-or-empty}} denotes \tcode{variant} +where \tcode{Us...} is the pack \tcode{decay_t...} +with duplicate types removed. +\item +Otherwise, \tcode{\exposid{variant-or-empty}} denotes +the exposition-only class type: +\begin{codeblock} +namespace std::execution { + struct @\exposidnc{empty-variant}@ { // \expos + @\exposidnc{empty-variant}@() = delete; + }; +} +\end{codeblock} +\end{itemize} + +\pnum +For types \tcode{Sndr} and \tcode{Env}, +\tcode{\exposid{single-sender-value-type}} is an alias for: +\begin{itemize} +\item +\tcode{value_types_of_t} +if that type is well-formed, +\item +Otherwise, \tcode{void} +if \tcode{value_types_of_t} is +\tcode{variant>} or \tcode{vari\-ant<>}, +\item +Otherwise, \tcode{value_types_of_t} +if that type is well-formed, +\item +Otherwise, \tcode{single-sender-value-type} is ill-formed. +\end{itemize} + +\pnum +The exposition-only concept \exposconcept{single-sender} is defined as follows: +\begin{codeblock} +namespace std::execution { + template + concept @\defexposconcept{single-sender}@ = @\libconcept{sender_in}@ && + requires { + typename @\exposid{single-sender-value-type}@; + }; +} +\end{codeblock} + +\rSec1[exec.queries]{Queries} + +\rSec2[exec.fwd.env]{\tcode{forwarding_query}} + +\pnum +\tcode{forwarding_query} asks a query object +whether it should be forwarded through queryable adaptors. + +\pnum +The name \tcode{forwarding_query} denotes a query object. +For some query object \tcode{q} of type \tcode{Q}, +\tcode{forwarding_query(q)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{\exposid{MANDATE-NOTHROW}(q.query(forwarding_query))} +if that expression is well-formed. + +\mandates +The expression above has type \tcode{bool} and +is a core constant expression if \tcode{q} is a core constant expression. +\item +Otherwise, true if \tcode{derived_from} is \tcode{true}. +\item +Otherwise, \tcode{false}. +\end{itemize} + +\rSec2[exec.get.allocator]{\tcode{get_allocator}} + +\pnum +\tcode{get_allocator} asks a queryable object for its associated allocator. + +\pnum +The name \tcode{get_allocator} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_allocator(env)} is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(as_const(env).query(get_allocator))}. + +\mandates +If the expression above is well-formed, +its type satisfies +\exposconcept{simple-allocator}\iref{allocator.requirements.general}. + +\pnum +\tcode{forwarding_query(get_allocator)} is a core constant expression and +has value \tcode{true}. + +\rSec2[exec.get.stop.token]{\tcode{get_stop_token}} + +\pnum +\tcode{get_stop_token} asks a queryable object for an associated stop token. + +\pnum +The name \tcode{get_stop_token} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_stop_token(env)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{\exposid{MANDATE-NOTHROW}(as_const(env).query(get_stop_token))} +if that expression is well-formed. + +\mandates +The type of the expression above satisfies \libconcept{stoppable_token}. + +\item +Otherwise, \tcode{never_stop_token\{\}}. +\end{itemize} + +\pnum +\tcode{forwarding_query(get_stop_token)} is a core constant expression and +has value \tcode{true}. + +\rSec2[exec.get.env]{\tcode{execution::get_env}} + +\pnum +\tcode{execution::get_env} is a customization point object. +For a subexpression \tcode{o}, +\tcode{execution::get_env(o)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{\exposid{MANDATE-NOTHROW}(as_const(o).get_env())} +if that expression is well-formed. + +\mandates +The type of the expression above satisfies +\exposconcept{queryable}\iref{exec.queryable}. +\item +Otherwise, \tcode{empty_env\{\}}. +\end{itemize} + +\pnum +The value of \tcode{get_env(o)} shall be valid while \tcode{o} is valid. + +\pnum +\begin{note} +When passed a sender object, +\tcode{get_env} returns the sender's associated attributes. +When passed a receiver, +\tcode{get_env} returns the receiver's associated execution environment. +\end{note} + +\rSec2[exec.get.domain]{\tcode{execution::get_domain}} + +\pnum +\tcode{get_domain} asks a queryable object +for its associated execution domain tag. + +\pnum +The name \tcode{get_domain} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_domain(env)} is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(as_const(env).query(get_domain))}. + +\pnum +\tcode{forwarding_query(execution::get_domain)} is +a core constant expression and has value \tcode{true}. + +\rSec2[exec.get.scheduler]{\tcode{execution::get_scheduler}} + +\pnum +\tcode{get_scheduler} asks a queryable object for its associated scheduler. + +\pnum +The name \tcode{get_scheduler} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_scheduler(env)} is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(as_const(env).query(get_scheduler))}. + +\mandates +If the expression above is well-formed, +its type satisfies \libconcept{scheduler}. + +\pnum +\tcode{forwarding_query(execution::get_scheduler)} is +a core constant expression and has value \tcode{true}. + +\rSec2[exec.get.delegation.scheduler]{\tcode{execution::get_delegation_scheduler}} + +\pnum +\tcode{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\iref{intro.progress}. + +\pnum +The name \tcode{get_delegation_scheduler} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_delegation_scheduler(env)} is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(as_const(env).query(get_delegation_scheduler))}. + +\mandates +If the expression above is well-formed, +its type satisfies \libconcept{scheduler}. + +\pnum +\tcode{forwarding_query(execution::get_delegation_scheduler)} is +a core constant expression and has value \tcode{true}. + +\rSec2[exec.get.fwd.progress]{\tcode{execution::get_forward_progress_guarantee}} + +\begin{codeblock} +namespace std::execution { + enum class forward_progress_guarantee { + concurrent, + parallel, + weakly_parallel + }; +} +\end{codeblock} + +\pnum +\tcode{get_forward_progress_guarantee} asks a scheduler about +the forward progress guarantee of execution agents +created by that scheduler's associated execution resource\iref{intro.progress}. + +\pnum +The name \tcode{get_forward_progress_guarantee} denotes a query object. +For a subexpression \tcode{sch}, let \tcode{Sch} be \tcode{decltype((sch))}. +If \tcode{Sch} does not satisfy \libconcept{scheduler}, +\tcode{get_forward_progress_guarantee} is ill-formed. +Otherwise, +\tcode{get_forward_progress_guarantee(sch)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{\exposid{MANDATE-NOTHROW}(as_const(sch).query(get_forward_progress_guarantee))}, +if that expression is well-formed. + +\mandates +The type of the expression above is \tcode{forward_progress_guarantee}. +\item +Otherwise, \tcode{forward_progress_guarantee::weakly_parallel}. +\end{itemize} + +\pnum +If \tcode{get_forward_progress_guarantee(sch)} for some scheduler \tcode{sch} +returns \tcode{forward_progress_guaran\-tee::concurrent}, +all execution agents created by that scheduler's associated execution resource +shall provide the concurrent forward progress guarantee. +If it returns \tcode{forward_progress_guarantee::parallel}, +all such execution agents +shall provide at least the parallel forward progress guarantee. + +\rSec2[exec.get.compl.sched]{\tcode{execution::get_completion_scheduler}} + +\pnum +\tcode{get_completion_scheduler<\exposid{completion-tag>}} obtains +the completion scheduler associated with a completion tag +from a sender's attributes. + +\pnum +The name \tcode{get_completion_scheduler} denotes a query object template. +For a subexpression \tcode{q}, +the expression \tcode{get_completion_scheduler<\exposid{completion-tag}>(q)} +is ill-formed if \exposid{completion-tag} is not one of +\tcode{set_value_t}, \tcode{set_error_t}, or \tcode{set_stopped_t}. +Otherwise, \tcode{get_completion_scheduler<\exposid{completion-tag}>(q)} +is expression-equivalent to +\begin{codeblock} +@\exposid{MANDATE-NOTHROW}@(as_const(q).query(get_completion_scheduler<@\exposid{completion-tag}@>)) +\end{codeblock} +\mandates +If the expression above is well-formed, +its type satisfies \libconcept{scheduler}. + +\pnum +Let \exposid{completion-fn} be a completion function\iref{exec.async.ops}; +let \exposid{completion-tag} be +the associated completion tag of \exposid{completion-fn}; +let \tcode{args} be a pack of subexpressions; and +let \tcode{sndr} be a subexpression +such that \tcode{\libconcept{sender}} is \tcode{true} and +\tcode{get_completion_scheduler<\exposid{completion-tag}>(get_env(sndr))} +is well-formed and denotes a scheduler \tcode{sch}. +If an asynchronous operation +created by connecting \tcode{sndr} with a receiver \tcode{rcvr} +causes the evaluation of \tcode{\exposid{completion-fn}(rcvr, args...)}, +the behavior is undefined +unless the evaluation happens on an execution agent +that belongs to \tcode{sch}'s associated execution resource. + +\pnum +The expression +\tcode{forwarding_query(get_completion_scheduler<\exposid{completion-tag}>)} +is a core constant expression and has value \tcode{true}. + +\rSec1[exec.sched]{Schedulers} + +\pnum +The \libconcept{scheduler} concept defines +the requirements of a scheduler type\iref{exec.async.ops}. +\tcode{schedule} is a customization point object +that accepts a scheduler. +A valid invocation of \tcode{schedule} is a schedule-expression. +\begin{codeblock} +namespace std::execution { + template + concept @\deflibconcept{scheduler}@ = + @\libconcept{derived_from}@::scheduler_concept, scheduler_t> && + @\exposconcept{queryable}@ && + requires(Sch&& sch) { + { schedule(std::forward(sch)) } -> @\libconcept{sender}@; + { auto(get_completion_scheduler( + get_env(schedule(std::forward(sch))))) } + -> @\libconcept{same_as}@>; + } && + @\libconcept{equality_comparable}@> && + @\libconcept{copy_constructible}@>; +} +\end{codeblock} + +\pnum +Let \tcode{Sch} be the type of a scheduler and +let \tcode{Env} be the type of an execution environment +for which \tcode{\libconcept{sender_in}, Env>} +is satisfied. +Then \tcode{\exposconcept{sender-in-of}, Env>} +shall be modeled. + +\pnum +None of a scheduler's +copy constructor, +destructor, +equality comparison, or +\tcode{swap} member functions +shall exit via an exception. +None of these member functions, +nor a scheduler type's \tcode{schedule} function, +shall introduce data races +as a result of potentially concurrent\iref{intro.races} invocations +of those functions from different threads. + +\pnum +For any two values \tcode{sch1} and \tcode{sch2} +of some scheduler type \tcode{Sch}, +\tcode{sch1 == sch2} shall return \tcode{true} +only if both \tcode{sch1} and \tcode{sch2} share +the same associated execution resource. + +\pnum +For a given scheduler expression \tcode{sch}, +the expression +\tcode{get_completion_scheduler(get_env(schedule(sch)))} +shall compare equal to \tcode{sch}. + +\pnum +For a given scheduler expression \tcode{sch}, +if the expression \tcode{get_domain(sch)} is well-formed, +then the expression \tcode{get_domain(get_env(schedule(sch)))} +is also well-formed and has the same type. + +\pnum +A scheduler type's destructor shall not block +pending completion of any receivers +connected to the sender objects returned from \tcode{schedule}. +\begin{note} +The ability to wait for completion of submitted function objects +can be provided by the associated execution resource of the scheduler. +\end{note} + +\rSec1[exec.recv]{Receivers} + +\rSec2[exec.recv.concepts]{Receiver concepts} + +\pnum +A receiver represents the continuation of an asynchronous operation. +The \libconcept{receiver} concept defines +the requirements for a receiver type\iref{exec.async.ops}. +The \libconcept{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 \tcode{get_env} customization point object is used to access +a receiver's associated environment. +\begin{codeblock} +namespace std::execution { + template + concept @\deflibconcept{receiver}@ = + @\libconcept{derived_from}@::receiver_concept, receiver_t> && + requires(const remove_cvref_t& rcvr) { + { get_env(rcvr) } -> @\exposconcept{queryable}@; + } && + @\libconcept{move_constructible}@> && // rvalues are movable, and + @\libconcept{constructible_from}@, Rcvr>; // lvalues are copyable + + template + concept @\defexposconcept{valid-completion-for}@ = + requires (Signature* sig) { + [](Tag(*)(Args...)) + requires @\exposconcept{callable}@, Args...> + {}(sig); + }; + + template + concept @\defexposconcept{has-completions}@ = + requires (Completions* completions) { + []<@\exposconcept{valid-completion-for}@...Sigs>(completion_signatures*) + {}(completions); + }; + + template + concept @\deflibconcept{receiver_of}@ = + @\libconcept{receiver}@ && @\exposconcept{has-completions}@; +} +\end{codeblock} + +\pnum +Class types that are marked \tcode{final} do not model the receiver concept. + +\pnum +Let \tcode{rcvr} be a receiver and +let \tcode{op_state} be an operation state associated with +an asynchronous operation created by connecting \tcode{rcvr} with a sender. +Let \tcode{token} be a stop token equal to +\tcode{get_stop_token(get_env(rcvr))}. +\tcode{token} shall remain valid +for the duration of the asynchronous operation's lifetime\iref{exec.async.ops}. +\begin{note} +This means that, unless it knows about further guarantees +provided by the type of \tcode{rcvr}, +the implementation of \tcode{op_state} cannot use \tcode{token} +after 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} + +\rSec2[exec.set.value]{\tcode{execution::set_value}} + +\pnum +\tcode{set_value} is a value completion function\iref{exec.async.ops}. +Its associated completion tag is \tcode{set_value_t}. +The expression \tcode{set_value(rcvr, vs...)} +for a subexpression \tcode{rcvr} and +pack of subexpressions \tcode{vs} is ill-formed +if \tcode{rcvr} is an lvalue or an rvalue of const type. +Otherwise, it is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(rcvr.set_value(vs...))}. + +\rSec2[exec.set.error]{\tcode{execution::set_error}} + +\pnum +\tcode{set_error} is an error completion function\iref{exec.async.ops}. +Its associated completion tag is \tcode{set_error_t}. +The expression \tcode{set_error(rcvr, err)} +for some \tcode{subexpressions} \tcode{rcvr} and \tcode{err} is ill-formed +if \tcode{rcvr} is an lvalue or an rvalue of const type. +Otherwise, it is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(rcvr.set_error(err))}. + +\rSec2[exec.set.stopped]{\tcode{execution::set_stopped}} + +\pnum +\tcode{set_stopped} is a stopped completion function\iref{exec.async.ops}. +Its associated completion tag is \tcode{set_stopped_t}. +The expression \tcode{set_stopped(rcvr)} +for a subexpression \tcode{rcvr} is ill-formed +if \tcode{rcvr} is an lvalue or an rvalue of const type. +Otherwise, it is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(rcvr.set_stopped())}. + +\rSec1[exec.opstate]{Operation states} + +\rSec2[exec.opstate.general]{General} + +\pnum +The \libconcept{operation_state} concept defines +the requirements of an operation state type\iref{exec.async.ops}. +\begin{codeblock} +namespace std::execution { + template + concept @\deflibconcept{operation_state}@ = + @\libconcept{derived_from}@ && + is_object_v && + requires (O& o) { + { start(o) } noexcept; + }; +} +\end{codeblock} + +\pnum +If an \libconcept{operation_state} object is destroyed +during the lifetime of its asynchronous operation\iref{exec.async.ops}, +the behavior is undefined. +\begin{note} +The \libconcept{operation_state} concept does not impose requirements +on any operations other than destruction and \tcode{start}, +including copy and move operations. +Invoking any such operation on an object +whose type models \libconcept{operation_state} can lead to undefined behavior. +\end{note} + +\pnum +The program is ill-formed +if it performs a copy or move construction or assigment operation on +an operation state object created by connecting a library-provided sender. + +\rSec2[exec.opstate.start]{\tcode{execution::start}} + +\pnum +The name \tcode{start} denotes a customization point object +that starts\iref{exec.async.ops} +the asynchronous operation associated with the operation state object. +For a subexpression \tcode{op}, +the expression \tcode{start(op)} is ill-formed +if \tcode{op} is an rvalue. +Otherwise, it is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(op.start())}. + +\pnum +If \tcode{op.start()} does not start\iref{exec.async.ops} +the asynchronous operation associated with the operation state \tcode{op}, +the behavior of calling \tcode{start(op)} is undefined. + +\rSec1[exec.snd]{Senders} + +\rSec2[exec.snd.general]{General} + +\pnum +For the purposes of this subclause, +a sender is an object +whose type satisfies the sender concept\iref{exec.async.ops}. + +\pnum +Subclauses \ref{exec.factories} and \ref{exec.adapt} define +customizable algorithms that return senders +Each algorithm has a default implementation. +Let \tcode{sndr} be the result of an invocation of such an algorithm or +an object equal to the result\iref{concepts.equality}, and +let \tcode{Sndr} be \tcode{decltype((sndr))}. +Let \tcode{rcvr} be a receiver of type \tcode{Rcvr} +with associated environment \tcode{env} of type \tcode{Env} +such that \tcode{\libconcept{sender_to}} is \tcode{true}. +For the default implementation of the algorithm that produced \tcode{sndr}, +connecting \tcode{sndr} to \tcode{rcvr} and +starting the resulting operation state\iref{exec.async.ops} +necessarily results in the potential evaluation\iref{basic.def.odr} of +a set of completion operations +whose first argument is a subexpression equal to \tcode{rcvr}. +Let \tcode{Sigs} be a pack of completion signatures corresponding to +this set of completion operations. +Then the type of the expression \tcode{get_completion_signatures(sndr, env)} is +a specialization of +the class template \tcode{completion_signatures}\iref{exec.util.cmplsig}, +the set of whose template arguments is \tcode{Sigs}. +If a user-provided implementation of the algorithm +that produced \tcode{sndr} is selected instead of the default, +any completion signature +that is in the set of types +denoted by \tcode{completion_signatures_of_t} and +that is not part of \tcode{Sigs} shall correspond to +error or stopped completion operations, +unless otherwise specified. + +\rSec2[exec.snd.expos]{Exposition-only entities} + +\pnum +Subclause \ref{exec.snd} makes use of the following exposition-only entities. + +\pnum +For a queryable object \tcode{env}, +\tcode{\exposid{FWD-ENV}(env)} is an expression +whose type satisfies \exposconcept{queryable} +such that for a query object \tcode{q} and +a pack of subexpressions \tcode{as}, +the expression \tcode{\exposid{FWD-ENV}(env).query(q, as...)} is ill-formed +if \tcode{forwarding_query(q)} is \tcode{false}; +otherwise, it is expression-equivalent to \tcode{env.query(q, as...)}. + +\pnum +For a query object \tcode{q} and \tcode{a} subexpression \tcode{v}, +\tcode{\exposid{MAKE-ENV}(q, v)} is an expression \tcode{env} +whose type satisfies \exposconcept{queryable} +such that the result of \tcode{env.query(q)} has +a value equal to \tcode{v}\iref{concepts.equality}. +Unless otherwise stated, +the object to which \tcode{env.query(q)} refers remains valid +while \tcode{env} remains valid. + +\pnum +For two queryable objects \tcode{env1} and \tcode{env2}, +a query object \tcode{q}, and +a pack of subexpressions \tcode{as}, +\tcode{\exposid{JOIN-ENV}(env1, env2)} is an expression \tcode{env3} +whose type satisfies \exposconcept{queryable} +such that \tcode{env3.query(q, as...)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{env1.query(q, as...)} if that expression is well-formed, +\item +otherwise, \tcode{env2.query(q, as...)} if that expression is well-formed, +\item +otherwise, \tcode{env3.query(q, as...)} is ill-formed. +\end{itemize} + +\pnum +The results of \exposid{FWD-ENV}, \exposid{MAKE-ENV}, and \exposid{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. + +\pnum +For a scheduler \tcode{sch}, +\tcode{\exposid{SCHED-ATTRS}(sch)} is an expression \tcode{o1} +whose type satisfies \exposconcept{queryable} +such that \tcode{o1.query(get_completion_scheduler)} is +an expression with the same type and value as \tcode{sch} +where \tcode{Tag} is one of \tcode{set_value_t} or \tcode{set_stopped_t}, and +such that \tcode{o1.query(get_domain)} is expression-equivalent to +\tcode{sch.query(get_domain)}. +\tcode{\exposid{SCHED-ENV}(sch)} is an expression \tcode{o2} +whose type satisfies \exposconcept{queryable} +such that \tcode{o1.query(get_scheduler)} is a prvalue +with the same type and value as \tcode{sch}, and +such that \tcode{o2.query(get_domain)} is expression-equivalent to +\tcode{sch.query(get_domain)}. + +\pnum +For two subexpressions \tcode{rcvr} and \tcode{expr}, +\tcode{\exposid{SET-VALUE}(rcvr, expr)} is expression-equivalent to +\tcode{(expr, set_value(std::move(rcvr)))} +if the type of \tcode{expr} is \tcode{void}; +otherwise, \tcode{set_value(std::move(rcvr), expr)}. +\tcode{\exposid{TRY-EVAL}(rcvr, expr)} is equivalent to: +\begin{codeblock} +try { + expr; +} catch(...) { + set_error(std::move(rcvr), current_exception()); +} +\end{codeblock} +if \tcode{expr} is potentially-throwing; otherwise, \tcode{expr}. +\tcode{\exposid{TRY-SET-VALUE}(rcvr, expr)} is +\begin{codeblock} +@\exposid{TRY-EVAL}@(rcvr, @\exposid{SET-VALUE}@(rcvr, expr)) +\end{codeblock} +except that \tcode{rcvr} is evaluated only once. + +\begin{itemdecl} +template + constexpr auto @\exposid{completion-domain}@(const Sndr& sndr) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\tcode{\exposid{COMPL-DOMAIN}(T)} is the type of the expression +\tcode{get_domain(get_completion_scheduler(get_env(sndr)))}. + +\pnum +\effects +If all of the types +\tcode{COMPL-DOMAIN(set_value_t)}, +\tcode{COMPL-DOMAIN(set_error_t)}, and +\tcode{COMPL-DO\-MAIN(set_stopped_t)} are ill-formed, +\tcode{completion-domain(sndr)} is +a default-constructed prvalue of type \tcode{Default}. +Otherwise, if they all share a common type\iref{meta.trans.other} +(ignoring those types that are ill-formed), +then \tcode{\exposid{completion-domain}(sndr)} is +a default-constructed prvalue of that type. +Otherwise, \tcode{\exposid{completion-domain}(sndr)} is ill-formed. +\end{itemdescr} + +\begin{itemdecl} +template + constexpr decltype(auto) @\exposid{query-with-default}@( + Tag, const Env& env, Default&& value) noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{e} be the expression \tcode{Tag()(env)} +if that expression is well-formed; +otherwise, it is \tcode{static_cast(std::forward(value))}. + +\pnum +\returns +\tcode{e}. + +\pnum +\remarks +The expression in the noexcept clause is \tcode{noexcept(e)}. +\end{itemdescr} + +\begin{itemdecl} +template + constexpr auto @\exposid{get-domain-early}@(const Sndr& sndr) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +return Domain(); +\end{codeblock} +where \tcode{Domain} is +the decayed type of the first of the following expressions that is well-formed: +\begin{itemize} +\item \tcode{get_domain(get_env(sndr))} +\item \tcode{\exposid{completion-domain}(sndr)} +\item \tcode{default_domain()} +\end{itemize} +\end{itemdescr} + +\begin{itemdecl} +template + constexpr auto @\exposid{get-domain-late}@(const Sndr& sndr, const Env& env) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{itemize} +\item +If \tcode{\exposconcept{sender-for}} is \tcode{true}, then +\begin{codeblock} +return Domain(); +\end{codeblock} +where \tcode{Domain} is the type of the following expression: +\begin{codeblock} +[] { + auto [_, sch, _] = sndr; + return @\exposid{query-or-default}@(get_domain, sch, default_domain()); +}(); +\end{codeblock} +\begin{note} +The \tcode{continues_on} algorithm works +in tandem with \tcode{schedule_from}\iref{exec.schedule.from} +to give scheduler authors a way to customize both +how to transition onto (\tcode{continues_on}) and off of (\tcode{schedule_from}) +a given execution context. +Thus, \tcode{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 \tcode{continues_on}. +That is why it is given special treatment here. +\end{note} +\item +Otherwise, +\begin{codeblock} +return Domain(); +\end{codeblock} +where \tcode{Domain} is the first of the following expressions +that is well-formed and whose type is not \tcode{void}: +\begin{itemize} +\item \tcode{get_domain(get_env(sndr))} +\item \tcode{\exposid{completion-domain}(sndr)} +\item \tcode{get_domain(env)} +\item \tcode{get_domain(get_scheduler(env))} +\item \tcode{default_domain()} +\end{itemize} +\end{itemize} +\end{itemdescr} + +\pnum +\begin{codeblock} +template<@\exposconcept{callable}@ Fun> + requires is_nothrow_move_constructible_v +struct @\exposid{emplace-from}@ { + Fun @\exposid{fun}@; // \expos + using type = @\exposid{call-result-t}@; + + constexpr operator type() && noexcept(@\exposid{nothrow-callable}@) { + return std::move(fun)(); + } + + constexpr type operator()() && noexcept(@\exposid{nothrow-callable}@) { + return std::move(fun)(); + } +}; +\end{codeblock} +\begin{note} +\exposid{emplace-from} is used to emplace non-movable types +into \tcode{tuple}, \tcode{optional}, \tcode{variant}, and similar types. +\end{note} + +\pnum +\begin{codeblock} +struct @\exposid{on-stop-request}@ { + inplace_stop_source& @\exposid{stop-src}@; // \expos + void operator()() noexcept { @\exposid{stop-src}@.request_stop(); } +}; +\end{codeblock} + +\pnum +\begin{codeblock} +template +struct @\exposid{product-type}@ { // \expos + T@$_0$@ t@$_0$@; // \expos + T@$_1$@ t@$_1$@; // \expos + @...@ + T@$_n$@ t@$_n$@; // \expos + + template + constexpr decltype(auto) @\exposid{get}@(this Self&& self) noexcept; // \expos + + template + constexpr decltype(auto) @\exposid{apply}@(this Self&& self, Fn&& fn) // \expos + noexcept(@\seebelow@); +}; +\end{codeblock} + +\pnum +\begin{note} +\exposid{product-type} is presented here in pseudo-code form +for the sake of exposition. +It can be approximated in standard \Cpp{} 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} +\begin{note} +An expression of type \exposid{product-type} is usable as +the initializer of a structured binding declaration\iref{dcl.struct.bind}. +\end{note} + +\begin{itemdecl} +template +constexpr decltype(auto) @\exposid{get}@(this Self&& self) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +auto& [...ts] = self; +return std::forward_like(ts...[I]); +\end{codeblock} +\end{itemdescr} + +\begin{codeblock} +template +constexpr decltype(auto) @\exposid{apply}@(this Self&& self, Fn&& fn) noexcept(@\seebelow@); +\end{codeblock} + +\begin{itemdescr} +\pnum +\constraints +The expression in the \tcode{return} statement below is well-formed. + +\pnum +\effects +Equivalent to: +\begin{codeblock} +auto& [...ts] = self; +return std::forward(fn)(std::forward_like(ts)...); +\end{codeblock} + +\pnum +\remarks +The expression in the \tcode{noexcept} clause is \tcode{true} +if the \tcode{return} statement above is not potentially throwing; +otherwise, \tcode{false}. +\end{itemdescr} + +\begin{itemdecl} +template + constexpr auto make-sender(Tag tag, Data&& data, Child&&... child); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\mandates +The following expressions are \tcode{true}: +\begin{itemize} +\item \tcode{\libconcept{semiregular}} +\item \tcode{\exposconcept{movable-value}} +\item \tcode{(\libconcept{sender} \&\&...)} +\end{itemize} + +\pnum +\returns +A prvalue of +type \tcode{\exposid{basic-sender}, decay_t...>} +that has been direct-list-initialized with the forwarded arguments, +where \exposid{basic-sender} is the following exposition-only class template except as noted below. +\end{itemdescr} + +\begin{codeblock} +namespace std::execution { + template + concept @\defexposconcept{completion-tag}@ = // \expos + @\libconcept{same_as}@ || @\libconcept{same_as}@ || @\libconcept{same_as}@; + + template class T, class... Args> + concept @\defexposconcept{valid-specialization}@ = // \expos + requires { typename T; }; + + struct @\exposid{default-impls}@ { // \expos + static constexpr auto @\exposid{get-attrs}@ = @\seebelow@; // \expos + static constexpr auto @\exposid{get-env}@ = @\seebelow@; // \expos + static constexpr auto @\exposid{get-state}@ = @\seebelow@; // \expos + static constexpr auto @\exposid{start}@ = @\seebelow@; // \expos + static constexpr auto @\exposid{complete}@ = @\seebelow@; // \expos + }; + + template + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ {}; // \expos + + template // \expos + using @\exposid{state-type}@ = decay_t<@\exposid{call-result-t}@< + decltype(@\exposid{impls-for}@>::@\exposid{get-state}@), Sndr, Rcvr&>>; + + template // \expos + using @\exposid{env-type}@ = @\exposid{call-result-t}@< + decltype(@\exposid{impls-for}@>::@\exposid{get-env}@), Index, + @\exposid{state-type}@&, const Rcvr&>; + + template + using @\exposid{child-type}@ = decltype(declval().template @\exposid{get}@()); // \expos + + template + using @\exposid{indices-for}@ = remove_reference_t::@\exposid{indices-for}@; // \expos + + template + struct @\exposid{basic-state}@ { // \expos + @\exposid{basic-state}@(Sndr&& sndr, Rcvr&& rcvr) noexcept(@\seebelow@) + : rcvr(std::move(rcvr)) + , state(@\exposid{impls-for}@>::@\exposid{get-state}@(std::forward(sndr), rcvr)) { } + + Rcvr @\exposid{rcvr}@; // \expos + @\exposid{state-type}@ @\exposid{state}@; // \expos + }; + + template + requires @\exposid{valid-specialization}@<@\exposid{env-type}@, Index, Sndr, Rcvr> + struct @\exposid{basic-receiver}@ { // \expos + using receiver_concept = receiver_t; + + using @\exposid{tag-t}@ = tag_of_t; // \expos + using @\exposid{state-t}@ = @\exposid{state-type}@; // \expos + static constexpr const auto& @\exposid{complete}@ = @\exposid{impls-for}@::@\exposid{complete}@; // \expos + + template + requires @\exposconcept{callable}@ + void set_value(Args&&... args) && noexcept { + @\exposid{complete}@(Index(), op->@\exposid{state}@, op->@\exposid{rcvr}@, set_value_t(), std::forward(args)...); + } + + template + requires @\exposconcept{callable}@ + void set_error(Error&& err) && noexcept { + @\exposid{complete}@(Index(), op->@\exposid{state}@, op->@\exposid{rcvr}@, set_error_t(), std::forward(err)); + } + + void set_stopped() && noexcept + requires @\exposconcept{callable}@ { + @\exposid{complete}@(Index(), op->@\exposid{state}@, op->@\exposid{rcvr}@, set_stopped_t()); + } + + auto get_env() const noexcept -> @\exposid{env-type}@ { + return @\exposid{impls-for}@::@\exposid{get-env}@(Index(), op->@\exposid{state}@, op->@\exposid{rcvr}@); + } + + @\exposid{basic-state}@* @\exposid{op}@; // \expos + }; + + constexpr auto @\exposid{connect-all}@ = @\seebelow@; // \expos + + template + using @\exposid{connect-all-result}@ = @\exposid{call-result-t}@< // \expos + decltype(@\exposid{connect-all}@), @\exposid{basic-state}@*, Sndr, @\exposid{indices-for}@>; + + template + requires @\exposconcept{valid-specialization}@<@\exposid{state-type}@, Sndr, Rcvr> && + @\exposconcept{valid-specialization}@<@\exposid{connect-all-result}@, Sndr, Rcvr> + struct @\exposid{basic-operation}@ : @\exposid{basic-state}@ { // \expos + using operation_state_concept = operation_state_t; + using @\exposid{tag-t}@ = tag_of_t; // \expos + + @\exposid{connect-all-result}@ @\exposid{inner-ops}@; // \expos + + @\exposid{basic-operation}@(Sndr&& sndr, Rcvr&& rcvr) noexcept(@\seebelow@) // \expos + : @\exposid{basic-state}@(std::forward(sndr), std::move(rcvr)), + @\exposid{inner-ops}@(@\exposid{connect-all}@(this, std::forward(sndr), @\exposid{indices-for}@())) + {} + + void start() & noexcept { + auto& [...ops] = @\exposid{inner-ops}@; + @\exposid{impls-for}@::@\exposid{start}@(this->@\exposid{state}@, this->@\exposid{rcvr}@, ops...); + } + }; + + template + using @\exposid{completion-signatures-for}@ = @\seebelow@; // \expos + + template + struct @\exposid{basic-sender}@ : @\exposid{product-type}@ { // \expos + using sender_concept = sender_t; + using @\exposid{indices-for}@ = index_sequence_for; // \expos + + decltype(auto) get_env() const noexcept { + auto& [_, data, ...child] = *this; + return @\exposid{impls-for}@::@\exposid{get-attrs}@(data, child...); + } + + template<@\exposconcept{decays-to}@<@\exposid{basic-sender}@> Self, receiver Rcvr> + auto connect(this Self&& self, Rcvr rcvr) noexcept(@\seebelow@) + -> @\exposid{basic-operation}@ { + return {std::forward(self), std::move(rcvr)}; + } + + template<@\exposconcept{decays-to}@<@\exposid{basic-sender}@> Self, class Env> + auto get_completion_signatures(this Self&& self, Env&& env) noexcept + -> @\exposid{completion-signatures-for}@ { + return {}; + } + }; +} +\end{codeblock} + +\pnum +The default template argument for the \tcode{Data} template parameter +denotes an unspecified empty trivially copyable class type +that models \libconcept{semiregular}. + +\pnum +It is unspecified whether a specialization of \exposid{basic-sender} +is an aggregate. + +\pnum +An expression of type \exposid{basic-sender} is usable as +the initializer of a structured binding declaration\iref{dcl.struct.bind}. + +\pnum +The expression in the \tcode{noexcept} clause of +the constructor of \exposid{basic-state} is: +\begin{codeblock} +is_nothrow_move_constructible_v && +@\exposid{nothrow-callable}@>::@\exposid{get-state}@), Sndr, Rcvr&> +\end{codeblock} + +\pnum +The object \exposid{connect-all} is initialized with +a callable object equivalent to the following lambda: +\begin{itemdecl} +[]( + @\exposid{basic-state}@* op, Sndr&& sndr, index_sequence) noexcept(@\seebelow@) + -> decltype(auto) { + auto& [_, data, ...child] = sndr; + return @\exposid{product-type}@{connect( + std::forward_like(child), + @\exposid{basic-receiver}@>{op})...}; + } +\end{itemdecl} + +\begin{itemdescr} +\pnum +\constraints +The expression in the \tcode{return} statement is well-formed. + +\pnum +\remarks +The expression in the \tcode{noexcept} clause is \tcode{true} +if the \tcode{return} statement is not potentially throwing; +otherwise, \tcode{false}. +\end{itemdescr} + +\pnum +The expression in the \tcode{noexcept} clause of +the constructor of \exposid{basic-operation} is: +\begin{codeblock} +is_nothrow_constructible_v<@\exposid{basic-state}@, Self, Rcvr> && +noexcept(@\exposid{connect-all}@(this, std::forward(sndr), @\exposid{indices-for}@())) +\end{codeblock} + +\pnum +The expression in the \tcode{noexcept} clause of +the \tcode{connect} member function of \exposid{basic-sender} is: +\begin{codeblock} +is_nothrow_constructible_v<@\exposid{basic-operation}@, Self, Rcvr> +\end{codeblock} + +\pnum +The member \tcode{\exposid{default-impls}::\exposid{get-attrs}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](const auto&, const auto&... child) noexcept -> decltype(auto) { + if constexpr (sizeof...(child) == 1) + return (@\exposid{FWD-ENV}@(get_env(child)), ...); + else + return empty_env(); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{default-impls}::\exposid{get-env}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) { + return @\exposid{FWD-ENV}@(get_env(rcvr)); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{default-impls}::\exposid{get-state}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) { + auto& [_, data, ...child] = sndr; + return std::forward_like(data); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{default-impls}::\exposid{start}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](auto&, auto&, auto&... ops) noexcept -> void { + (execution::start(ops), ...); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{default-impls}::\exposid{complete}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[]( + Index, auto& state, Rcvr& rcvr, Tag, Args&&... args) noexcept + -> void requires @\exposconcept{callable}@ { + static_assert(Index::value == 0); + Tag()(std::move(rcvr), std::forward(args)...); +} +\end{codeblock} + +\pnum +For a subexpression \tcode{sndr} let \tcode{Sndr} be \tcode{decltype((sndr))}. +Let \tcode{rcvr} be a receiver +with an associated environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +\tcode{\exposid{completion-signatures-for}} denotes +a specialization of \tcode{completion_signatures}, +the set of whose template arguments correspond to +the set of completion operations that are potentially evaluated +as a result of starting\iref{exec.async.ops} +the operation state that results from connecting \tcode{sndr} and \tcode{rcvr}. +When \tcode{\libconcept{sender_in}} is \exposid{false}, +the type denoted by \tcode{\exposid{completion-signatures-for}}, +if any, is not a specialization of \tcode{completion_signatures}. + +\recommended +When \tcode{\libconcept{sender_in}} is \tcode{false}, +implementations are encouraged to use the type +denoted by \tcode{\exposid{completion-signatures-for}} +to communicate to users why. + +\begin{itemdecl} +template<@\libconcept{sender}@ Sndr, @\exposconcept{queryable}@ Env> + constexpr auto @\exposid{write-env}@(Sndr&& sndr, Env&& env); // \expos +\end{itemdecl} + +\begin{itemdescr} +\pnum +\exposid{write-env} is an exposition-only sender adaptor that, +when connected with a receiver \tcode{rcvr}, +connects the adapted sender with a receiver +whose execution environment is the result of +joining the \exposconcept{queryable} argument \tcode{env} +to the result of \tcode{get_env(rcvr)}. + +\pnum +Let \exposid{write-env-t} be an exposition-only empty class type. + +\pnum +\returns +\begin{codeblock} +@\exposid{make-sender}@(@\exposid{write-env-t}@(), std::forward(env), std::forward(sndr)) +\end{codeblock} + +\pnum +\remarks +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{write-env-t} as follows: +\begin{codeblock} +template<> +struct @\exposid{impls-for}@<@\exposid{write-env-t}@> : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-env}@ = + [](auto, const auto& state, const auto& rcvr) noexcept { + return @\exposid{JOIN-ENV}@(state, get_env(rcvr)); + }; +}; +\end{codeblock} +\end{itemdescr} + +\rSec2[exec.snd.concepts]{Sender concepts} + +\pnum +The \libconcept{sender} concept defines +the requirements for a sender type\iref{exec.async.ops}. +The \libconcept{sender_in} concept defines +the requirements for a sender type +that can create asynchronous operations given an associated environment type. +The \libconcept{sender_to} concept defines +the requirements for a sender type +that can connect with a specific receiver type. +The \tcode{get_env} customization point object is used to access +a sender's associated attributes. +The connect customization point object is used to connect\iref{exec.async.ops} +a sender and a receiver to produce an operation state. + +\begin{codeblock} +namespace std::execution { + template + concept @\exposconcept{valid-completion-signatures}@ = @\seebelow@; // \expos + + template + concept @\defexposconcept{is-sender}@ = // \expos + derived_from; + + template + concept @\defexposconcept{enable-sender}@ = // \expos + @\exposconcept{is-sender}@ || + @\exposconcept{is-awaitable}@>; // \ref{exec.awaitable} + + template + concept @\deflibconcept{sender}@ = + bool(@\exposconcept{enable-sender}@>) && + requires (const remove_cvref_t& sndr) { + { get_env(sndr) } -> @\exposconcept{queryable}@; + } && + @\libconcept{move_constructible}@> && + @\libconcept{constructible_from}@, Sndr>; + + template + concept @\deflibconcept{sender_in}@ = + @\libconcept{sender}@ && + @\exposconcept{queryable}@ && + requires (Sndr&& sndr, Env&& env) { + { get_completion_signatures(std::forward(sndr), std::forward(env)) } + -> @\exposconcept{valid-completion-signatures}@; + }; + + template + concept @\deflibconcept{sender_to}@ = + @\libconcept{sender_in}@> && + @\libconcept{receiver_of}@>> && + requires (Sndr&& sndr, Rcvr&& rcvr) { + connect(std::forward(sndr), std::forward(rcvr)); + }; +} +\end{codeblock} + +\pnum +Given a subexpression \tcode{sndr}, +let \tcode{Sndr} be \tcode{decltype((sndr))} and +let \tcode{rcvr} be a receiver +with an associated environment whose type is \tcode{Env}. +A completion operation is a \defnadj{permissible}{completion} +for \tcode{Sndr} and \tcode{Env} +if its completion signature appears in the argument list of the specialization of \tcode{completion_signatures} denoted by +\tcode{completion_signatures_of_t}. +\tcode{Sndr} and \tcode{Env} model \tcode{\libconcept{sender_in}} +if all the completion operations +that are potentially evaluated by connecting \tcode{sndr} to \tcode{rcvr} and +starting the resulting operation state +are permissible completions for \tcode{Sndr} and \tcode{Env}. + +\pnum +A type models +the exposition-only concept \defexposconcept{valid-completion-signatures} +if it denotes a specialization of +the \tcode{completion_signatures} class template. + +\pnum +The exposition-only concepts +\exposconcept{sender-of} and \exposconcept{sender-in-of} +define the requirements for a sender type +that completes with a given unique set of value result types. +\begin{codeblock} +namespace std::execution { + template + using @\exposid{value-signature}@ = set_value_t(As...); // \expos + + template + concept @\defexposconcept{sender-in-of}@ = + @\libconcept{sender_in}@ && + @\exposid{MATCHING-SIG}@( // see \ref{exec.general} + set_value_t(Values...), + value_types_of_t); + + template + concept @\defexposconcept{sender-of}@ = @\exposconcept{sender-in-of}@; +} +\end{codeblock} + +\pnum +Let \tcode{sndr} be an expression +such that \tcode{decltype((sndr))} is \tcode{Sndr}. +The type \tcode{tag_of_t} is as follows: +\begin{itemize} +\item +If the declaration +\begin{codeblock} +auto&& [tag, data, ...children] = sndr; +\end{codeblock} +would be well-formed, \tcode{tag_of_t} is +an alias for \tcode{decltype(auto(tag))}. +\item +Otherwise, \tcode{tag_of_t} is ill-formed. +\end{itemize} + +\pnum +Let \exposconcept{sender-for} be an exposition-only concept defined as follows: +\begin{codeblock} +namespace std::execution { + template + concept @\defexposconcept{sender-for}@ = + @\libconcept{sender}@ && + @\libconcept{same_as}@, Tag>; +} +\end{codeblock} + +\pnum +For a type \tcode{T}, +\tcode{\exposid{SET-VALUE-SIG}(T)} denotes the type \tcode{set_value_t()} +if \tcode{T} is \cv{} \tcode{void}; +otherwise, it denotes the type \tcode{set_value_t(T)}. + +\pnum +Library-provided sender types +\begin{itemize} +\item +always expose an overload of a member \tcode{connect} +that accepts an rvalue sender and +\item +only expose an overload of a member \tcode{connect} +that accepts an lvalue sender if they model \tcode{copy_constructible}. +\end{itemize} + +\rSec2[exec.awaitable]{Awaitable helpers} + +\pnum +The sender concepts recognize awaitables as senders. +For \ref{exec}, an \defn{awaitable} is an expression +that would be well-formed as the operand of a \tcode{co_await} expression +within a given context. + +\pnum +For a subexpression \tcode{c}, +let \tcode{\exposid{GET-AWAITER}(c, p)} be expression-equivalent to +the series of transformations and conversions applied to \tcode{c} +as the operand of an \grammarterm{await-expression} in a coroutine, +resulting in lvalue \tcode{e} as described by \ref{expr.await}, +where \tcode{p} is an lvalue referring to the coroutine's promise, +which has type \tcode{Promise}. +\begin{note} +This includes the invocation of +the promise type's \tcode{await_transform} member if any, +the invocation of the \tcode{operator co_await} +picked by overload resolution if any, and +any necessary implicit conversions and materializations. +\end{note} + +\pnum +Let \exposconcept{is-awaitable} be the following exposition-only concept: +\begin{codeblock} +namespace std { + template + concept @\exposconcept{await-suspend-result}@ = @\seebelow@; // \expos + + template + concept @\defexposconcept{is-awaiter}@ = // \expos + requires (A& a, coroutine_handle h) { + a.await_ready() ? 1 : 0; + { a.await_suspend(h) } -> @\exposconcept{await-suspend-result}@; + a.await_resume(); + }; + + template + concept @\defexposconcept{is-awaitable}@ = // \expos + requires (C (*fc)() noexcept, Promise& p) { + { @\exposid{GET-AWAITER}@(fc(), p) } -> @\exposconcept{is-awaiter}@; + }; +} +\end{codeblock} + +\tcode{\defexposconcept{await-suspend-result}} is \tcode{true} +if and only if one of the following is \tcode{true}: +\begin{itemize} +\item \tcode{T} is \tcode{void}, or +\item \tcode{T} is \tcode{bool}, or +\item \tcode{T} is a specialization of \tcode{coroutine_handle}. +\end{itemize} + +\pnum +For a subexpression \tcode{c} +such that \tcode{decltype((c))} is type \tcode{C}, and +an lvalue \tcode{p} of type \tcode{Promise}, +\tcode{\exposid{await-result-\newline type}} denotes +the type \tcode{decltype(\exposid{GET-AWAITER}(c, p).await_resume())}. + +\pnum +Let \exposid{with-await-transform} be the exposition-only class template: +\begin{codeblock} +namespace std::execution { + template + concept @\defexposconcept{has-as-awaitable}@ = // \expos + requires (T&& t, Promise& p) { + { std::forward(t).as_awaitable(p) } -> @\exposconcept{is-awaitable}@; + }; + + template + struct @\exposid{with-await-transform}@ { // \expos + template + T&& await_transform(T&& value) noexcept { + return std::forward(value); + } + + template<@\exposconcept{has-as-awaitable}@ T> + decltype(auto) await_transform(T&& value) + noexcept(noexcept(std::forward(value).as_awaitable(declval()))) { + return std::forward(value).as_awaitable(static_cast(*this)); + } + }; +} +\end{codeblock} + +\pnum +Let \exposid{env-promise} be the exposition-only class template: +\begin{codeblock} +namespace std::execution { + template + struct @\exposid{env-promise}@ : @\exposid{with-await-transform}@<@\exposid{env-promise}@> { // \expos + @\unspec@ get_return_object() noexcept; + @\unspec@ initial_suspend() noexcept; + @\unspec@ final_suspend() noexcept; + void unhandled_exception() noexcept; + void return_void() noexcept; + coroutine_handle<> unhandled_stopped() noexcept; + + const Env& get_env() const noexcept; + }; +} +\end{codeblock} +\begin{note} +of \exposid{env-promise} are used only for the purpose of type computation; +its members need not be defined. +\end{note} + +\rSec2[exec.domain.default]{\tcode{execution::default_domain}} + +\pnum +\begin{codeblock} +namespace std::execution { + struct default_domain { + template<@\libconcept{sender}@ Sndr, @\exposconcept{queryable}@... Env> + requires (sizeof...(Env) <= 1) + static constexpr @\libconcept{sender}@ decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) + noexcept(@\seebelow@); + + template<@\libconcept{sender}@ Sndr, @\exposconcept{queryable}@ Env> + static constexpr @\exposconcept{queryable}@ decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept; + + template + static constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args) + noexcept(@\seebelow@); + }; +} +\end{codeblock} + +\begin{itemdecl} +template<@\libconcept{sender}@ Sndr, @\exposconcept{queryable}@... Env> + requires (sizeof...(Env) <= 1) +constexpr @\libconcept{sender}@ decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) + noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{e} be the expression +\begin{codeblock} +tag_of_t().transform_sender(std::forward(sndr), env...) +\end{codeblock} +if that expression is well-formed; +otherwise, \tcode{std::forward(sndr)}. + +\pnum +\returns +\tcode{e}. + +\remarks +The exception specification is equivalent to \tcode{noexcept(e)}. +\end{itemdescr} + +\begin{itemdecl} +template<@\libconcept{sender}@ Sndr, @\exposconcept{queryable}@ Env> + constexpr @\exposconcept{queryable}@ decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{e} be the expression +\begin{codeblock} +tag_of_t().transform_env(std::forward(sndr), std::forward(env)) +\end{codeblock} +if that expression is well-formed; +otherwise, \tcode{static_cast(std::forward(env))}. + +\pnum +\mandates +\tcode{noexcept(e)} is \tcode{true}. + +\pnum +\returns +\tcode{e}. +\end{itemdescr} + +\begin{itemdecl} +template +constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args) + noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{e} be the expression +\begin{codeblock} + Tag().apply_sender(std::forward(sndr), std::forward(args)...) +\end{codeblock} + +\pnum +\constraints +\tcode{e} is a well-formed expression. + +\pnum +\returns +\tcode{e}. + +\pnum +\remarks +The exception specification is equivalent to \tcode{noexcept(e)}. +\end{itemdescr} + +\rSec2[exec.snd.transform]{\tcode{execution::transform_sender}} + +\begin{itemdecl} +namespace std::execution { + template + requires (sizeof...(Env) <= 1) + constexpr @\libconcept{sender}@ decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) + noexcept(@\seebelow@); +} +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \exposid{transformed-sndr} be the expression +\begin{codeblock} +dom.transform_sender(std::forward(sndr), env...) +\end{codeblock} +if that expression is well-formed; otherwise, +\begin{codeblock} +default_domain().transform_sender(std::forward(sndr), env...) +\end{codeblock} +Let \exposid{final-sndr} be the expression \exposid{transformed-sndr} +if \exposid{transformed-sndr} and \exposid{sndr} +have the same type ignoring cv-qualifiers; +otherwise, it is +the expression \tcode{transform_sender(dom, transformed-sndr, env...)}. + +\pnum +\returns +\exposid{final-sndr}. + +\pnum +\remarks +The exception specification is equivalent to +\tcode{noexcept(\exposid{final-sndr})}. +\end{itemdescr} + +\rSec2[exec.snd.transform.env]{\tcode{execution::transform_env}} + +\begin{itemdecl} +namespace std::execution { + template + constexpr @\exposconcept{queryable}@ decltype(auto) transform_env(Domain dom, Sndr&& sndr, Env&& env) noexcept; +} +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \tcode{e} be the expression +\begin{codeblock} +dom.transform_env(std::forward(sndr), std::forward(env)) +\end{codeblock} +if that expression is well-formed; otherwise, +\begin{codeblock} +default_domain().transform_env(std::forward(sndr), std::forward(env)) +\end{codeblock} + +\pnum +\mandates +\tcode{noexcept(e)} is \tcode{true}. + +\pnum +\returns +\tcode{e}. +\end{itemdescr} + +\rSec2[exec.snd.apply]{\tcode{execution::apply_sender}} + +\begin{itemdecl} +namespace std::execution { + template + constexpr decltype(auto) apply_sender(Domain dom, Tag, Sndr&& sndr, Args&&... args) + noexcept(see below); +} +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let $e$ be the expression +\begin{codeblock} +dom.apply_sender(Tag(), std::forward(sndr), std::forward(args)...) +\end{codeblock} +if that expression is well-formed; otherwise, +\begin{codeblock} +default_domain().apply_sender(Tag(), std::forward(sndr), std::forward(args)...) +\end{codeblock} + +\pnum +\constraints +The expression $e$ is well-formed. + +\pnum +\returns +$e$. + +\pnum +\remarks +The exception specification is equivalent to \tcode{noexcept($e$)}. +\end{itemdescr} + +\rSec2[exec.getcomplsigs]{\tcode{execution::get_completion_signatures}} + +\pnum +\tcode{get_completion_signatures} is a customization point object. +Let \tcode{sndr} be an expression +such that \tcode{decltype((sndr))} is \tcode{Sndr}, and +let \tcode{env} be an expression +such that \tcode{decltype((env))} is \tcode{Env}. +Let \tcode{new_sndr} be the expression +\tcode{transform_sender(decltype(\exposid{get-domain-late}(sndr, env)){}, sndr, env)}, and +let \tcode{NewSndr} be \tcode{decltype((new_sndr))}. +Then \tcode{get_completion_signatures(sndr, env)} is expression-equivalent to +\tcode{(void(sndr), void(env), CS())} +except that \tcode{void(sndr)} and \tcode{void(env)} are +indeterminately sequenced, +where \tcode{CS} is: +\begin{itemize} +\item +\tcode{decltype(new_sndr.get_completion_signatures(env))} +if that type is well-formed, + +\item +Otherwise, \tcode{remove_cvref_t::completion_signatures} +if that type is well-formed, + +\item +Otherwise, +if \tcode{\exposid{is-awaitable}>} is \tcode{true}, +then: +\begin{codeblock} +completion_signatures< + @\exposid{SET-VALUE-SIG}@(@\exposid{await-result-type}@>), // \iref{exec.snd.concepts} + set_error_t(exception_ptr), + set_stopped_t()> +\end{codeblock} + +\item +Otherwise, \tcode{CS} is ill-formed. +\end{itemize} + +\pnum +Let \tcode{rcvr} be an rvalue +whose type \tcode{Rcvr} models \libconcept{receiver}, and +let \tcode{Sndr} be the type of a sender +such that \tcode{\libconcept{sender_in}>} is \tcode{true}. +Let \tcode{Sigs...} be the template arguments of +the \tcode{completion_signatures} specialization +named by \tcode{completion_signatures_of_t>}. +Let \tcode{CSO} be a completion function. +If sender \tcode{Sndr} or its operation state cause +the expression \tcode{CSO(rcvr, args...)} +to be potentially evaluated\iref{basic.def.odr} +then there shall be a signature \tcode{Sig} in \tcode{Sigs...} +such that +\begin{codeblock} +@\exposid{MATCHING-SIG}@(@\exposid{decayed-typeof}@(decltype(args)...), Sig) +\end{codeblock} +is \tcode{true}\iref{exec.general}. + +\rSec2[exec.connect]{\tcode{execution::connect}} + +\pnum +\tcode{connect} connects\iref{exec.async.ops} a sender with a receiver. + +\pnum +The name \tcode{connect} denotes a customization point object. +For subexpressions \tcode{sndr} and \tcode{rcvr}, +let \tcode{Sndr} be \tcode{decltype((sndr))} and +\tcode{Rcvr} be \tcode{decltype((rcvr))}, +let \tcode{new_sndr} be the expression +\begin{codeblock} +transform_sender(decltype(@\exposid{get-domain-late}@(sndr, get_env(rcvr))){}, sndr, get_env(rcvr)) +\end{codeblock} +and let \tcode{DS} and \tcode{DR} be +\tcode{decay_t} and \tcode{decay_t}, respectively. + +\pnum +Let \exposid{connect-awaitable-promise} be the following exposition-only class: + +\begin{codeblock} +namespace std::execution { + struct @\exposid{connect-awaitable-promise}@ : @\exposid{with-await-transform}@<@\exposid{connect-awaitable-promise}@> { + + @\exposid{connect-awaitable-promise}@(DS&, DR& rcvr) noexcept : @\exposid{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(@\exposid{rcvr}@)); + return noop_coroutine(); + } + + @\exposid{operation-state-task}@ get_return_object() noexcept { + return @\exposid{operation-state-task}@{ + coroutine_handle<@\exposid{connect-awaitable-promise}@>::from_promise(*this)}; + } + + env_of_t get_env() const noexcept { + return execution::get_env(@\exposid{rcvr}@); + } + + private: + DR& @\exposid{rcvr}@; // \expos + }; +} +\end{codeblock} + +\pnum +Let \exposid{operation-state-task} be the following exposition-only class: +\begin{codeblock} +namespace std::execution { + struct @\exposid{operation-state-task}@ { + using operation_state_concept = operation_state_t; + using promise_type = @\exposid{connect-awaitable-promise}@; + + explicit @\exposid{operation-state-task}@(coroutine_handle<> h) noexcept : coro(h) {} + @\exposid{operation-state-task}@(@\exposid{operation-state-task}@&& o) noexcept + : @\exposid{coro}@(exchange(o.@\exposid{coro}@, {})) {} + ~@\exposid{operation-state-task}@() { if (@\exposid{coro}@) @\exposid{coro}@.destroy(); } + + void start() & noexcept { + @\exposid{coro}@.resume(); + } + + private: + coroutine_handle<> @\exposid{coro}@; // \expos + }; +} +\end{codeblock} + +\pnum +Let \tcode{V} name the type +\tcode{\exposid{await-result-type}}, +let \tcode{Sigs} name the type +\begin{codeblock} +completion_signatures< + @\exposid{SET-VALUE-SIG}@(V), // see \iref{exec.snd.concepts} + set_error_t(exception_ptr), + set_stopped_t()> +\end{codeblock} +and let \exposid{connect-awaitable} be an exposition-only coroutine +defined as follows: +\begin{codeblock} +namespace std::execution { + template + auto @\exposid{suspend-complete}@(Fun fun, Ts&&... as) noexcept { // \expos + auto fn = [&, fun]() noexcept { fun(std::forward(as)...); }; + + struct awaiter { + decltype(@\exposid{fn}@) @\exposid{fn}@; // \expos + + static constexpr bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept { @\exposid{fn}@(); } + [[noreturn]] void await_resume() noexcept { unreachable(); } + }; + return awaiter{@\exposid{fn}@}; + } + + @\exposid{operation-state-task}@ @\exposid{connect-awaitable}@(DS sndr, DR rcvr) requires @\libconcept{receiver_of}@ { + exception_ptr ep; + try { + if constexpr (@\libconcept{same_as}@) { + co_await std::move(sndr); + co_await @\exposid{suspend-complete}@(set_value, std::move(rcvr)); + } else { + co_await @\exposid{suspend-complete}@(set_value, std::move(rcvr), co_await std::move(sndr)); + } + } catch(...) { + ep = current_exception(); + } + co_await @\exposid{suspend-complete}@(set_error, std::move(rcvr), std::move(ep)); + } +} +\end{codeblock} + +\pnum +The expression \tcode{connect(sndr, rcvr)} is expression-equivalent to: +\begin{itemize} +\item +\tcode{new_sndr.connect(rcvr)} if that expression is well-formed. + +\mandates +The type of the expression above satisfies \libconcept{operation_state}. + +\item +Otherwise, \tcode{\exposid{connect-awaitable}(new_sndr, rcvr)}. +\end{itemize} + +\mandates +\tcode{\libconcept{sender} \&\& \libconcept{receiver}} is \tcode{true}. + +\rSec2[exec.factories]{Sender factories} + +\rSec3[exec.schedule]{\tcode{execution::schedule}} + +\pnum +\tcode{schedule} obtains a schedule sender\iref{exec.async.ops} +from a scheduler. + +\pnum +The name \tcode{schedule} denotes a customization point object. +For a subexpression \tcode{sch}, +the expression \tcode{schedule(sch)} is expression-equivalent to +\tcode{sch.schedule()}. + +\pnum +\mandates +The type of \tcode{sch.schedule()} satisfies \libconcept{sender}. + +\pnum +If the expression +\begin{codeblock} +get_completion_scheduler(get_env(sch.schedule())) == sch +\end{codeblock} +is ill-formed or evaluates to \tcode{false}, +the behavior of calling \tcode{schedule(sch)} is undefined. + +\rSec3[exec.just]{\tcode{execution::just}, \tcode{execution::just_error}, \tcode{execution::just_stopped}} + +\pnum +\tcode{just}, \tcode{just_error}, and \tcode{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. + +\pnum +The names \tcode{just}, \tcode{just_error}, and \tcode{just_stopped} denote +customization point objects. +Let \exposid{just-cpo} be one of +\tcode{just}, \tcode{just_error}, or \tcode{just_stopped}. +For a pack of subexpressions \tcode{ts}, +let \tcode{Ts} be the pack of types \tcode{decltype((ts))}. +The expression \tcode{\exposid{just-cpo}(ts...)} is ill-formed if +\begin{itemize} +\item +\tcode{(\exposid{movable-value} \&\&...)} is \tcode{false}, or +\item +\exposid{just-cpo} is \tcode{just_error} and +\tcode{sizeof...(ts) == 1} is \tcode{false}, or +\item +\exposid{just-cpo} is \tcode{just_stopped} and +\tcode{sizeof...(ts) == 0} is \tcode{false}. +\end{itemize} + +Otherwise, it is expression-equivalent to +\tcode{\exposid{make-sender}(\exposid{just-cpo}, \exposid{product-type}{ts...})}. + +For \tcode{just}, \tcode{just_error}, and \tcode{just_stopped}, +let \exposid{set-cpo} be +\tcode{set_value}, \tcode{set_error}, and \tcode{set_stopped}, respectively. +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{just-cpo} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@<@\exposid{decayed-typeof}@<@\exposid{just-cpo}@>> : @\exposid{default-impls}@ { + static constexpr auto @\exposid{start}@ = + [](auto& state, auto& rcvr) noexcept -> void { + auto& [...ts] = state; + @\exposid{set-cpo}@(std::move(rcvr), std::move(ts)...); + }; + }; +} +\end{codeblock} + +\rSec3[exec.read.env]{\tcode{execution::read_env}} + +\pnum +\tcode{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. + +\pnum +\tcode{read_env} is a customization point object. +For some query object \tcode{q}, +the expression \tcode{read_env(q)} is expression-equivalent to +\tcode{\exposid{make-sender}(read_env, q)}. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{read_env} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@<@\exposid{decayed-typeof}@> : @\exposid{default-impls}@ { + static constexpr auto start = + [](auto query, auto& rcvr) noexcept -> void { + @\exposid{TRY-SET-VALUE}@(rcvr, query(get_env(rcvr))); + }; + }; +} +\end{codeblock} + +\rSec2[exec.adapt]{Sender adaptors} + +\rSec3[exec.adapt.general]{General} + +\pnum +Subclause \ref{exec.adapt} specifies a set of sender adaptors. + +\pnum +The bitwise inclusive \logop{or} operator is overloaded +for the purpose of creating sender chains. +The adaptors also support function call syntax with equivalent semantics. + +\pnum +Unless otherwise specified: +\begin{itemize} +\item +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 \tcode{connect}, +and \tcode{start} is called on the resulting operation state. +\item +A parent sender\iref{exec.async.ops} with a single child sender \tcode{sndr} has +an associated attribute object equal to +\tcode{\exposid{FWD-ENV}(get_env(sndr))}\iref{exec.fwd.env}. +\item +A parent sender with more than one child sender has +an associated attributes object equal to \tcode{empty_env\{\}}. +\item +When a parent sender is connected to a receiver \tcode{rcvr}, +any receiver used to connect a child sender has +an associated environment equal to \tcode{\exposid{FWD-ENV}(get_env(rcvr))}. +\item +These requirements apply to any function +that is selected by the implementation of the sender adaptor. +\end{itemize} + +\pnum +If a sender returned from a sender adaptor specified in \ref{exec.adapt} +is specified to include \tcode{set_error_t(Err)} +among its set of completion signatures +where \tcode{decay_t} denotes the type \tcode{exception_ptr}, +but the implementation does not potentially evaluate +an error completion operation with an \tcode{exception_ptr} argument, +the implementation is allowed to omit +the \tcode{exception_ptr} error completion signature from the set. + +\rSec3[exec.adapt.obj]{Closure objects} + +\pnum +A \defnadj{pipeable}{sender adaptor closure object} is a function object +that accepts one or more \tcode{sender} arguments and returns a \tcode{sender}. +For a pipeable sender adaptor closure object \tcode{c} and +an expression \tcode{sndr} +such that \tcode{decltype((sndr))} models \tcode{sender}, +the following expressions are equivalent and yield a \tcode{sender}: +\begin{codeblock} +c(sndr) +sndr | c +\end{codeblock} +Given an additional pipeable sender adaptor closure object \tcode{d}, +the expression \tcode{c | d} produces +another pipeable sender adaptor closure object \tcode{e}: + +\tcode{e} is a perfect forwarding call wrapper\iref{func.require} +with the following properties: +\begin{itemize} +\item +Its target object is an object \tcode{d2} of type \tcode{decltype(auto(d))} +direct-non-list-initialized with \tcode{d}. +\item +It has one bound argument entity, +an object \tcode{c2} of type \tcode{decltype(auto(c))} +direct-non-list-initialized with \tcode{c}. +\item +Its call pattern is \tcode{d2(c2(arg))}, +where arg is the argument used in a function call expression of \tcode{e}. +\end{itemize} +The expression \tcode{c | d} is well-formed if and only if +the initializations of the state entities\iref{func.def} of \tcode{e} +are all well-formed. + +\pnum +An object \tcode{t} of type \tcode{T} is +a pipeable sender adaptor closure object +if \tcode{T} models \tcode{\libconcept{derived_from}>}, +\tcode{T} has no other base classes +of type \tcode{sender_adaptor_closure} for any other type \tcode{U}, and +\tcode{T} does not satisfy \tcode{sender}. + +\pnum +The template parameter \tcode{D} for sender_adaptor_closure can be +an incomplete type. +Before any expression of type \cv{} \tcode{D} appears as +an operand to the \tcode{|} operator, +\tcode{D} shall be complete and +model \tcode{\libconcept{derived_from}>}. +The behavior of an expression involving an object of type \cv{} \tcode{D} +as an operand to the \tcode{|} operator is undefined +if overload resolution selects a program-defined \tcode{operator|} function. + +\pnum +A \defnadj{pipeable}{sender adaptor object} is a customization point object +that accepts a \tcode{sender} as its first argument and +returns a \tcode{sender}. +If a pipeable sender adaptor object accepts only one argument, +then it is a pipeable sender adaptor closure object. + +\pnum +If a pipeable sender adaptor object adaptor accepts more than one argument, +then let \tcode{sndr} be an expression +such that \tcode{decltype((sndr))} models \libconcept{sender}, +let \tcode{args...} be arguments +such that \tcode{adaptor(sndr, args...)} is a well-formed expression +as specified below, and +let \tcode{BoundArgs} be a pack that denotes \tcode{decltype(auto(args))...}. +The expression \tcode{adaptor(args...)} produces +a pipeable sender adaptor closure object \tcode{f} +that is a perfect forwarding call wrapper with the following properties: +\begin{itemize} +\item +Its target object is a copy of adaptor. +\item +Its bound argument entities \tcode{bound_args} consist of +objects of types \tcode{BoundArgs...} direct-non-list-initialized with +\tcode{std::forward(args)...}, respectively. +\item +Its call pattern is \tcode{adaptor(rcvr, bound_args...)}, +where \tcode{rcvr} is +the argument used in a function call expression of \tcode{f}. +\end{itemize} +The expression \tcode{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. + +\rSec3[exec.starts.on]{\tcode{execution::starts_on}} + +\pnum +\tcode{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. + +\pnum +The name \tcode{starts_on} denotes a customization point object. +For subexpressions \tcode{sch} and \tcode{sndr}, +if \tcode{decltype((\newline sch))} does not satisfy \libconcept{scheduler}, or +\tcode{decltype((sndr))} does not satisfy \libconcept{sender}, +\tcode{starts_on(sch, sndr)} is ill-formed. + +\pnum +Otherwise, +the expression \tcode{starts_on(sch, sndr)} is expression-equivalent to: +\begin{codeblock} +transform_sender( + @\exposid{query-or-default}@(get_domain, sch, default_domain()), + @\exposid{make-sender}@(starts_on, sch, sndr)) +\end{codeblock} +except that \tcode{sch} is evaluated only once. + +\pnum +Let \tcode{out_sndr} and \tcode{env} be subexpressions +such that \tcode{OutSndr} is \tcode{decltype((out_sndr))}. +If \tcode{\exposconcept{sender-for}} is \tcode{false}, +then the expressions \tcode{starts_on.transform_env(out_sndr, env)} and\linebreak +\tcode{starts_on.transform_sender(out_sndr, env)} are ill-formed; otherwise +\begin{itemize} +\item +\tcode{starts_on.transform_env(out_sndr, env)} is equivalent to: +\begin{codeblock} +auto&& [_, sch, _] = out_sndr; +return @\exposid{JOIN-ENV}@(@\exposid{SCHED-ENV}@(sch), @\exposid{FWD-ENV}@(env)); +\end{codeblock} +\item +\tcode{starts_on.transform_sender(out_sndr, env)} is equivalent to: +\begin{codeblock} +auto&& [_, sch, sndr] = out_sndr; +return let_value( + schedule(sch), + [sndr = std::forward_like(sndr)]() mutable + noexcept(is_nothrow_move_constructible_v>) { + return std::move(sndr); + }); +\end{codeblock} +\end{itemize} + +\pnum +Let \tcode{out_sndr} be a subexpression denoting +a sender returned from \tcode{starts_on(sch, sndr)} or one equal to such, and +let \tcode{OutSndr} be the type \tcode{decltype((out_sndr))}. +Let \tcode{out_rcvr} be a subexpression denoting a receiver +that has an environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +Let \tcode{op} be an lvalue referring to the operation state +that results from connecting \tcode{out_sndr} with \tcode{out_rcvr}. +Calling \tcode{start(op)} shall start \tcode{sndr} +on an execution agent of the associated execution resource of \tcode{sch}. +If scheduling onto \tcode{sch} fails, +an error completion on \tcode{out_rcvr} shall be executed +on an unspecified execution agent. + +\rSec3[exec.continues.on]{\tcode{execution::continues_on}} + +\pnum +\tcode{continues_on} adapts a sender into one +that completes on the specified scheduler. + +\pnum +The name \tcode{continues_on} denotes a pipeable sender adaptor object. +For subexpressions \tcode{sch} and \tcode{sndr}, +if \tcode{decltype((sch))} does not satisfy \libconcept{scheduler}, or +\tcode{decltype((sndr))} does not satisfy \libconcept{sender}, +\tcode{continues_on(sndr, sch)} is ill-formed. + +\pnum +Otherwise, +the expression \tcode{continues_on(sndr, sch)} is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(continues_on, sch, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. + +\pnum +The exposition-only class template \exposid{impls-for} +is specialized for \tcode{continues_on_t} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-attrs}@ = + [](const auto& data, const auto& child) noexcept -> decltype(auto) { + return @\exposid{JOIN-ENV}@(@\exposid{SCHED-ATTRS}@(data), @\exposid{FWD-ENV}@(get_env(child))); + }; + }; +} +\end{codeblock} + +\pnum +Let \tcode{sndr} and \tcode{env} be subexpressions +such that \tcode{Sndr} is \tcode{decltype((sndr))}. +If \tcode{\exposconcept{sender-for}} is \tcode{false}, +then +the expression \tcode{continues_on.transform_sender(sndr, env)} is ill-formed; +otherwise, it is equal to: +\begin{codeblock} +auto [_, data, child] = sndr; +return schedule_from(std::move(data), std::move(child)); +\end{codeblock} +\begin{note} +This causes the \tcode{continues_on(sndr, sch)} sender to become +\tcode{schedule_from(sch, sndr)} when it is connected with a receiver +whose execution domain does not customize \tcode{continues_on}. +\end{note} + +\pnum +Let \tcode{out_sndr} be a subexpression denoting +a sender returned from \tcode{continues_on(sndr, sch)} or one equal to such, and +let \tcode{OutSndr} be the type \tcode{decltype((out_sndr))}. +Let \tcode{out_rcvr} be a subexpression denoting a receiver +that has an environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +Let \tcode{op} be an lvalue referring to the operation state +that results from connecting \tcode{out_sndr} with \tcode{out_rcvr}. +Calling \tcode{start(op)} shall +start \tcode{sndr} on the current execution agent and +execute completion operations on \tcode{out_rcvr} +on an execution agent of the execution resource associated with \tcode{sch}. +If scheduling onto \tcode{sch} fails, +an error completion on \tcode{out_rcvr} shall be executed +on an unspecified execution agent. + +\rSec3[exec.schedule.from]{\tcode{execution::schedule_from}} + +\pnum +\tcode{schedule_from} schedules work dependent on the completion of a sender +onto a scheduler's associated execution resource. +\begin{note} +\tcode{schedule_from} is not meant to be used in user code; +it is used in the implementation of \tcode{continues_on}. +\end{note} + +\pnum +The name \tcode{schedule_from} denotes a customization point object. +For some subexpressions \tcode{sch} and \tcode{sndr}, +let \tcode{Sch} be \tcode{decltype((sch))} and +\tcode{Sndr} be \tcode{decltype((sndr))}. +If \tcode{Sch} does not satisfy \libconcept{scheduler}, or +\tcode{Sndr} does not satisfy \libconcept{sender}, +\tcode{schedule_from(sch, sndr)} is ill-formed. + +\pnum +Otherwise, +the expression \tcode{schedule_from(sch, sndr)} is expression-equivalent to: +\begin{codeblock} +transform_sender( + @\exposid{query-or-default}@(get_domain, sch, default_domain()), + @\exposid{make-sender}@(schedule_from, sch, sndr)) +\end{codeblock} +except that \tcode{sch} is evaluated only once. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{schedule_from_t} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-attrs}@ = @\seebelow@; + static constexpr auto @\exposid{get-state}@ = @\seebelow@; + static constexpr auto @\exposid{complete}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-attrs}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](const auto& data, const auto& child) noexcept -> decltype(auto) { + return @\exposid{JOIN-ENV}@(@\exposid{SCHED-ATTRS}@(data), @\exposid{FWD-ENV}@(get_env(child))); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-state}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](Sndr&& sndr, Rcvr& rcvr) noexcept(see below) + requires @\libconcept{sender_in}@<@\exposid{child-type}@, env_of_t> { + + auto& [_, sch, child] = sndr; + + using sched_t = decltype(auto(sch)); + using variant_t = @\seebelow@; + using receiver_t = @\seebelow@; + using operation_t = connect_result_t, receiver_t>; + constexpr bool nothrow = noexcept(connect(schedule(sch), receiver_t{nullptr})); + + struct @\exposid{state-type}@ { + Rcvr& @\exposid{rcvr}@; // \expos + variant_t @\exposid{async-result}@; // \expos + operation_t @\exposid{op-state}@; // \expos + + explicit @\exposid{state-type}@(sched_t sch, Rcvr& rcvr) noexcept(nothrow) + : @\exposid{rcvr}@(rcvr), @\exposid{op-state}@(connect(schedule(sch), receiver_t{this})) {} + }; + + return @\exposid{state-type}@{sch, rcvr}; +} +\end{codeblock} + +\pnum +Objects of the local class \exposid{state-type} can be used +to initialize a structured binding. + +\pnum +Let \tcode{Sigs} be +a pack of the arguments to the \tcode{completion_signatures} specialization +named by \tcode{completion_signatures_of_t, env_of_t>}. +Let \exposid{as-tuple} be an alias template +that transforms a completion signature \tcode{Tag(Args...)} into +the tuple specialization \tcode{decayed-tuple}. +Then \tcode{variant_t} denotes +the type \tcode{variant...>}, +except with duplicate types removed. + +\pnum +\tcode{receiver_t} is an alias for the following exposition-only class: +\begin{codeblock} +namespace std::execution { + struct @\exposid{receiver-type}@ { + using receiver_concept = receiver_t; + @\exposid{state-type}@* @\exposid{state}@; // \expos + + void set_value() && noexcept { + visit( + [this](Tuple& result) noexcept -> void { + if constexpr (!@\libconcept{same_as}@) { + auto& [tag, ...args] = result; + tag(std::move(@\exposid{state}@->@\exposid{rcvr}@), std::move(args)...); + } + }, + @\exposid{state}@->@\exposid{async-result}@); + } + + template + void set_error(Error&& err) && noexcept { + execution::set_error(std::move(@\exposid{state}@->@\exposid{rcvr}@), std::forward(err)); + } + + void set_stopped() && noexcept { + execution::set_stopped(std::move(@\exposid{state}@->@\exposid{rcvr}@)); + } + + decltype(auto) get_env() const noexcept { + return @\exposid{FWD-ENV}@(execution::get_env(@\exposid{state}@->@\exposid{rcvr}@)); + } + }; +} +\end{codeblock} + +\pnum +The expression in the \tcode{noexcept} clause of the lambda is \tcode{true} +if the construction of the returned \exposid{state-type} object +is not potentially throwing; +otherwise, \tcode{false}. + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{complete}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept + -> void { + using result_t = @\exposid{decayed-tuple}@; + constexpr bool nothrow = is_nothrow_constructible_v; + + @\exposid{TRY-EVAL}@(rcvr, [&]() noexcept(nothrow) { + state.@\exposid{async-result}@.template emplace(Tag(), std::forward(args)...); + }()); + + if (state.@\exposid{async-result}@.valueless_by_exception()) + return; + if (state.@\exposid{async-result}@.index() == 0) + return; + + start(state.@\exposid{op-state}@); +}; +\end{codeblock} + +\pnum +Let \tcode{out_sndr} be a subexpression denoting +a sender returned from \tcode{schedule_from(sch, sndr)} or one equal to such, +and let \tcode{OutSndr} be the type \tcode{decltype((out_sndr))}. +Let \tcode{out_rcvr} be a subexpression denoting a receiver +that has an environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +Let \tcode{op} be an lvalue referring to the operation state +that results from connecting \tcode{out_sndr} with \tcode{out_rcvr}. +Calling \tcode{start(op)} shall +start \tcode{sndr} on the current execution agent and +execute completion operations on \tcode{out_rcvr} +on an execution agent of the execution resource associated with \tcode{sch}. +If scheduling onto \tcode{sch} fails, +an error completion on \tcode{out_rcvr} shall be executed +on an unspecified execution agent. + +\rSec3[exec.on]{\tcode{execution::on}} + +\pnum +The \tcode{on} sender adaptor has two forms: +\begin{itemize} +\item +\tcode{on(sch, sndr)}, +which starts a sender \tcode{sndr} on an execution agent +belonging to a scheduler \tcode{sch}'s associated execution resource and +that, upon \tcode{sndr}'s completion, +transfers execution back to the execution resource +on which the \tcode{on} sender was started. +\item +\tcode{on(sndr, sch, closure)}, +which upon completion of a sender \tcode{sndr}, +transfers execution to an execution agent +belonging to a scheduler \tcode{sch}'s associated execution resource, +then executes a sender adaptor closure \tcode{closure} +with the async results of the sender, and +that then transfers execution back to the execution resource +on which \tcode{sndr} completed. +\end{itemize} + +\pnum +The name \tcode{on} denotes a pipeable sender adaptor object. +For subexpressions \tcode{sch} and \tcode{sndr}, +\tcode{on(sch, sndr)} is ill-formed if any of the following is true: +\begin{itemize} +\item +\tcode{decltype((sch))} does not satisfy \libconcept{scheduler}, or +\item +\tcode{decltype((sndr))} does not satisfy \libconcept{sender} and +\tcode{sndr} is not +a pipeable sender adaptor closure object\iref{exec.adapt.obj}, or +\item +\tcode{decltype((sndr))} satisfies \libconcept{sender} and +\tcode{sndr }is also a pipeable sender adaptor closure object. +\end{itemize} + +\pnum +Otherwise, if \tcode{decltype((sndr))} satisfies \libconcept{sender}, +the expression \tcode{on(sch, sndr)} is expression-equivalent to: +\begin{codeblock} +transform_sender( + @\exposid{query-or-default}@(get_domain, sch, default_domain()), + @\exposid{make-sender}@(on, sch, sndr)) +\end{codeblock} +except that \tcode{sch} is evaluated only once. + +\pnum +For subexpressions \tcode{sndr}, \tcode{sch}, and \tcode{closure}, if +\begin{itemize} +\item +\tcode{decltype((sch))} does not satisfy \libconcept{scheduler}, or +\item +\tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or +\item +\tcode{closure} is not a pipeable sender adaptor closure object\iref{exec.adapt.obj}, +\end{itemize} +the expression \tcode{on(sndr, sch, closure)} is ill-formed; +otherwise, it is expression-equivalent to: +\begin{codeblock} +transform_sender( + @\exposid{get-domain-early}@(sndr), + @\exposid{make-sender}@(on, @\exposid{product-type}@{sch, closure}, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. + +\pnum +Let \tcode{out_sndr} and \tcode{env} be subexpressions, +let \tcode{OutSndr} be \tcode{decltype((out_sndr))}, and +let \tcode{Env} be \tcode{decltype((\linebreak env))}. +If \tcode{\exposconcept{sender-for}} is \tcode{false}, +then the expressions \tcode{on.transform_env(out_sndr, env)} and +\tcode{on.transform_sender(out_sndr, env)} are ill-formed. + +\pnum +Otherwise: +Let \exposid{not-a-scheduler} be an unspecified empty class type, and +let \exposid{not-a-sender} be the exposition-only type: +\begin{codeblock} +struct @\exposid{not-a-sender}@ { + using sender_concept = sender_t; + + auto get_completion_signatures(auto&&) const { + return @\seebelow@; + } +}; +\end{codeblock} +where the member function \tcode{get_completion_signatures} returns +an object of a type that is not +a specialization of the \tcode{completion_signatures} class template. + +\pnum +The expression \tcode{on.transform_env(out_sndr, env)} +has effects equivalent to: +\begin{codeblock} +auto&& [_, data, _] = out_sndr; +if constexpr (@\libconcept{scheduler}@) { + return @\exposid{JOIN-ENV}@(@\exposid{SCHED-ENV}@(std::forward_like(data)), @\exposid{FWD-ENV}@(std::forward(env))); +} else { + return std::forward(env); +} +\end{codeblock} + +\pnum +The expression \tcode{on.transform_sender(out_sndr, env)} +has effects equivalent to: +\begin{codeblock} +auto&& [_, data, child] = out_sndr; +if constexpr (@\libconcept{scheduler}@) { + auto orig_sch = + @\exposid{query-with-default}@(get_scheduler, env, @\exposid{not-a-scheduler}@()); + + if constexpr (@\libconcept{same_as}@) { + return @\exposid{not-a-sender}@{}; + } else { + return continues_on( + starts_on(std::forward_like(data), std::forward_like(child)), + std::move(orig_sch)); + } +} else { + auto& [sch, closure] = data; + auto orig_sch = @\exposid{query-with-default}@( + get_completion_scheduler, + get_env(child), + @\exposid{query-with-default}@(get_scheduler, env, @\exposid{not-a-scheduler}@())); + + if constexpr (@\libconcept{same_as}@) { + return @\exposid{not-a-sender}@{}; + } else { + return @\exposid{write-env}@( + continues_on( + std::forward_like(closure)( + continues_on( + @\exposid{write-env}@(std::forward_like(child), @\exposid{SCHED-ENV}@(orig_sch)), + sch)), + orig_sch), + @\exposid{SCHED-ENV}@(sch)); + } +} +\end{codeblock} + +\pnum +\recommended +Implementations should use +the return type of \tcode{\exposid{not-a-sender}::get_completion_signatures} +to inform users that their usage of \tcode{on} is incorrect +because there is no available scheduler onto which to restore execution. + +\pnum +Let \tcode{out_sndr} be a subexpression denoting +a sender returned from \tcode{on(sch, sndr)} or one equal to such, and +let \tcode{OutSndr} be the type \tcode{decltype((out_sndr))}. +Let \tcode{out_rcvr} be a subexpression denoting a receiver +that has an environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +Let \tcode{op} be an lvalue referring to the operation state +that results from connecting \tcode{out_sndr} with \tcode{out_rcvr}. +Calling \tcode{start(op)} shall +\begin{itemize} +\item +remember the current scheduler, \tcode{get_scheduler(get_env(rcvr))}; +\item +start \tcode{sndr} on an execution agent belonging to +sch's associated execution resource; +\item +upon \tcode{sndr}'s completion, +transfer execution back to the execution resource +associated with the scheduler remembered in step 1; and +\item +forward \tcode{sndr}'s async result to \tcode{out_rcvr}. +\end{itemize} +If any scheduling operation fails, +an error completion on \tcode{out_rcvr} shall be executed +on an unspecified execution agent. + +\pnum +Let \tcode{out_sndr} be a subexpression denoting +a sender returned from \tcode{on(sndr, sch, closure)} or one equal to such, and +let \tcode{OutSndr} be the type \tcode{decltype((out_sndr))}. +Let \tcode{out_rcvr} be a subexpression denoting a receiver +that has an environment of type \tcode{Env} +such that \tcode{\libconcept{sender_in}} is \tcode{true}. +Let \tcode{op} be an lvalue referring to the operation state +that results from connecting \tcode{out_sndr} with \tcode{out_rcvr}. +Calling \tcode{start(op)} shall +\begin{itemize} +\item +remember the current scheduler, +which is the first of the following expressions that is well-formed: +\begin{itemize} +\item \tcode{get_completion_scheduler(get_env(sndr))} +\item \tcode{get_scheduler(get_env(rcvr))}; +\end{itemize} +\item +start \tcode{sndr} on the current execution agent; +\item +upon \tcode{sndr}'s completion, +transfer execution to an agent +owned by sch's associated execution resource; +\item +forward \tcode{sndr}'s async result as if by +connecting and starting a sender \tcode{closure(S)}, +where \tcode{S} is a sender +that completes synchronously with \tcode{sndr}'s async result; and +\item +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 \tcode{out_rcvr}. +\end{itemize} +If any scheduling operation fails, +an error completion on \tcode{out_rcvr} shall be executed on +an unspecified execution agent. + +\rSec3[exec.then]{\tcode{execution::then}, \tcode{execution::upon_error}, \tcode{execution::upon_stopped}} + +\pnum +\tcode{then} attaches an invocable as a continuation +for an input sender's value completion operation. +\tcode{upon_error} and \tcode{upon_stopped} do the same +for the error and stopped completion operations, respectively, +sending the result of the invocable as a value completion. + +\pnum +The names \tcode{then}, \tcode{upon_error}, and \tcode{upon_stopped} +denote pipeable sender adaptor objects. +Let the expression \exposid{then-cpo} be one of +\tcode{then}, \tcode{upon_error}, or \tcode{upon_stopped}. +For subexpressions \tcode{sndr} and \tcode{f}, +if \tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or +\tcode{decltype((f))} does not satisfy \exposid{movable-value}, +\tcode{\exposid{then-cpo}(\linebreak sndr, f) }is ill-formed. + +\pnum +Otherwise, +the expression \tcode{\exposid{then-cpo}(sndr, f)} is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(@\exposid{then-cpo}@, f, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. + +\pnum +For \tcode{then}, \tcode{upon_error}, and \tcode{upon_stopped}, +let \exposid{set-cpo} be +\tcode{set_value}, \tcode{set_error}, and \tcode{set_stopped}, respectively. +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{then-cpo} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@<@\exposid{decayed-typeof}@<@\exposid{then-cpo}@>> : @\exposid{default-impls}@ { + static constexpr auto @\exposid{complete}@ = + [] + (auto, auto& fn, auto& rcvr, Tag, Args&&... args) noexcept -> void { + if constexpr (@\libconcept{same_as}@>) { + @\exposid{TRY-SET-VALUE}@(rcvr, + invoke(std::move(fn), std::forward(args)...)); + } else { + Tag()(std::move(rcvr), std::forward(args)...); + } + }; + }; +} +\end{codeblock} + +\pnum +The expression \tcode{\exposid{then-cpo}(sndr, f)} has undefined behavior +unless it returns a sender\tcode{out_sndr} that +\begin{itemize} +\item +invokes \tcode{f} or a copy of such +with the value, error, or stopped result datums of \tcode{sndr} +for \tcode{then}, \tcode{upon_error}, and \tcode{upon_stopped}, respectively, +using the result value of \tcode{f} as \tcode{out_sndr}'s value completion, and +\item +forwards all other completion operations unchanged. +\end{itemize} + +\rSec3[exec.let]{\tcode{execution::let_value}, \tcode{execution::let_error}, \tcode{execution::let_stopped}} + +\pnum +\tcode{let_value}, \tcode{let_error}, and \tcode{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. + +\pnum +For \tcode{let_value}, \tcode{let_error}, and \tcode{let_stopped}, +let \exposid{set-cpo} be +\tcode{set_value}, \tcode{set_error}, and \tcode{set_stopped}, respectively. +Let the expression \exposid{let-cpo} be one of +\tcode{let_value}, \tcode{let_error}, or \tcode{let_stopped}. +For a subexpression \tcode{sndr}, +let \tcode{\exposid{let-env}(sndr)} be expression-equivalent to +the first well-formed expression below: +\begin{itemize} +\item +\tcode{SCHED-ENV(get_completion_scheduler<\exposid{decayed-typeof}<\exposid{set-cpo}>>(get_env(sndr)))} +\item +\tcode{\exposid{MAKE-ENV}(get_domain, get_domain(get_env(sndr)))} +\item +\tcode{(void(sndr), empty_env\{\})} +\end{itemize} + +\pnum +The names \tcode{let_value}, \tcode{let_error}, and \tcode{let_stopped} denote +pipeable sender adaptor objects. +For subexpressions \tcode{sndr} and \tcode{f}, +let \tcode{F} be the decayed type of \tcode{f}. +If \tcode{decltype((sndr))} does not satisfy \libconcept{sender} or +if \tcode{decltype((f))} does not satisfy \exposconcept{movable-value}, +the expression \tcode{\exposid{let-cpo}(sndr, f)} is ill-formed. +If \tcode{F} does not satisfy \libconcept{invocable}, +the expression \tcode{let_stopped(sndr, f)} is ill-formed. + +\pnum +Otherwise, +the expression \tcode{\exposid{let-cpo}(sndr, f)} is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(@\exposid{let-cpo}@, f, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{let-cpo} as follows: +\begin{codeblock} +namespace std::execution { + template + void @\exposid{let-bind}@(State& state, Rcvr& rcvr, Args&&... args); // \expos + + template<> + struct @\exposid{impls-for}@<@\exposid{decayed-typeof}@<@\exposid{let-cpo}@>> : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-state}@ = @\seebelow@; + static constexpr auto @\exposid{complete}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +Let \exposid{receiver2} denote the following exposition-only class template: +\begin{codeblock} +namespace std::execution { + template + struct @\exposid{receiver2}@ { + using receiver_concept = receiver_t; + + template + void set_value(Args&&... args) && noexcept { + execution::set_value(std::move(@\exposid{rcvr}@), std::forward(args)...); + } + + template + void set_error(Error&& err) && noexcept { + execution::set_error(std::move(@\exposid{rcvr}@), std::forward(err)); + } + + void set_stopped() && noexcept { + execution::set_stopped(std::move(@\exposid{rcvr}@)); + } + + decltype(auto) get_env() const noexcept { + return @\exposid{JOIN-ENV}@(@\exposid{env}@, @\exposid{FWD-ENV}@(execution::get_env(@\exposid{rcvr}@))); + } + + Rcvr& @\exposid{rcvr}@; // \expos + Env @\exposid{env}@; // \expos + }; +} +\end{codeblock} + +\pnum +\tcode{\exposid{impls-for}<\exposid{decayed-typeof}<\exposid{let-cpo}>>::\exposid{get-state}} +is initialized with a callable object equivalent to the following: +\begin{codeblock} +[](Sndr&& sndr, Rcvr& rcvr) requires @\seebelow@ { + auto& [_, fn, child] = sndr; + using fn_t = decay_t; + using env_t = decltype(@\exposid{let-env}@(child)); + using args_variant_t = @\seebelow@; + using ops2_variant_t = @\seebelow@; + + struct @\exposid{state-type}@ { + fn_t @\exposid{fn}@; // \expos + env_t @\exposid{env}@; // \expos + args_variant_t @\exposid{args}@; // \expos + ops2_variant_t @\exposid{ops2}@; // \expos + }; + return @\exposid{state-type}@{std::forward_like(fn), @\exposid{let-env}@(child), {}, {}}; +} +\end{codeblock} + +\pnum +Let \tcode{Sigs} be a pack of the arguments +to the \tcode{completion_signatures} specialization named by +\tcode{completion_signatures_of_t<\exposid{child-type}, env_of_t>}. +Let \tcode{LetSigs} be a pack of those types in \tcode{Sigs} +with a return type of \tcode{\exposid{decayed-typeof}<\exposid{set-cpo}>}. +Let \exposid{as-tuple} be an alias template +such that \tcode{\exposid{as-tuple}<\linebreak Tag(Args...)>} denotes +the type \tcode{\exposid{decayed-tuple}}. +Then \tcode{args_variant_t} denotes +the type \tcode{variant...>} +except with duplicate types removed. + +\pnum +Given a type \tcode{Tag} and a pack \tcode{Args}, +let \exposid{as-sndr2} be an alias template +such that \tcode{\exposid{as-sndr2}} denotes +the type \tcode{\exposid{call-result-t}\&...>}. +Then \tcode{ops2_variant_t} denotes +the type +\begin{codeblock} +variant, @\exposid{receiver2}@>...> +\end{codeblock} +except with duplicate types removed. + +\pnum +The \grammarterm{requires-clause} constraining the above lambda is satisfied +if and only if +the types \tcode{args_variant_t} and \tcode{ops2_variant_t} are well-formed. + +\pnum +The exposition-only function template \exposid{let-bind} +has effects equivalent to: +\begin{codeblock} +using args_t = @\exposid{decayed-tuple}@; +auto mkop2 = [&] { + return connect( + apply(std::move(state.fn), + state.args.template emplace(std::forward(args)...)), + @\exposid{receiver2}@{rcvr, std::move(state.env)}); +}; +start(state.ops2.template emplace(@\exposid{emplace-from}@{mkop2})); +\end{codeblock} + +\pnum +\tcode{\exposid{impls-for}<\exposid{decayed-typeof}>::\exposid{complete}} +is initialized with a callable object equivalent to the following: +\begin{codeblock} +[] + (auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void { + if constexpr (@\libconcept{same_as}@>) { + @\exposid{TRY-EVAL}@(rcvr, @\exposid{let-bind}@(state, rcvr, std::forward(args)...)); + } else { + Tag()(std::move(rcvr), std::forward(args)...); + } + } +\end{codeblock} + +\pnum +Let \tcode{sndr} and \tcode{env} be subexpressions, and +let \tcode{Sndr} be \tcode{decltype((sndr))}. +If +\tcode{\exposconcept{sender-for}>} +is \tcode{false}, +then the expression \tcode{\exposid{let-cpo}.transform_env(sndr, env)} +is ill-formed. +Otherwise, it is equal to +\tcode{\exposid{JOIN-ENV}(\exposid{let-env}(sndr), \exposid{FWD-ENV}(env))}. + +\pnum +Let the subexpression \tcode{out_sndr} denote +the result of the invocation \tcode{\exposid{let-cpo}(sndr, f)} or +an object equal to such, and +let the subexpression \tcode{rcvr} denote a receiver +such that the expression \tcode{connect(out_sndr, rcvr)} is well-formed. +The expression \tcode{connect(out_sndr, rcvr)} has undefined behavior +unless it creates an asynchronous operation\iref{exec.async.ops} that, +when started: +\begin{itemize} +\item +invokes \tcode{f} when \exposid{set-cpo} is called +with \tcode{sndr}'s result datums, +\item +makes its completion dependent on +the completion of a sender returned by \tcode{f}, and +\item +propagates the other completion operations sent by \tcode{sndr}. +\end{itemize} + +\rSec3[exec.bulk]{\tcode{execution::bulk}} + +\pnum +\tcode{bulk} runs a task repeatedly for every index in an index space. + +The name \tcode{bulk} denotes a pipeable sender adaptor object. +For subexpressions \tcode{sndr}, \tcode{shape}, and \tcode{f}, +let \tcode{Shape} be \tcode{decltype(auto(shape))}. +If +\begin{itemize} +\item +\tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or +\item +\tcode{Shape} does not satisfy \libconcept{integral}, or +\item +\tcode{decltype((f))} does not satisfy \exposconcept{movable-value}, +\end{itemize} +\tcode{bulk(sndr, shape, f)} is ill-formed. + +\pnum +Otherwise, +the expression \tcode{bulk(sndr, shape, f)} is expression-equivalent to: + +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(bulk, @\exposid{product-type}@{shape, f}, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{bulk_t} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{complete}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{complete}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[] + (Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void requires @\seebelow@ { + if constexpr (@\libconcept{same_as}@) { + auto& [shape, f] = state; + constexpr bool nothrow = noexcept(f(auto(shape), args...)); + @\exposid{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)...); + }()); + } else { + Tag()(std::move(rcvr), std::forward(args)...); + } + } +\end{codeblock} + +\pnum +The expression in the \grammarterm{requires-clause} of the lambda above +is \tcode{true} if and only +if \tcode{Tag} denotes a type other than \tcode{set_value_t} or +if the expression \tcode{f(auto(shape), args...)} is well-formed. + +\pnum +Let the subexpression \tcode{out_sndr} denote +the result of the invocation \tcode{bulk(sndr, shape, f)} or +an object equal to such, and +let the subexpression \tcode{rcvr} denote a receiver +such that the expression \tcode{connect(out_sndr, rcvr)} is well-formed. +The expression \tcode{connect(out_sndr, rcvr)} has undefined behavior +unless it creates an asynchronous operation\iref{exec.async.ops} that, +when started, +\begin{itemize} +\item +on a value completion operation, +invokes \tcode{f(i, args...)} +for every \tcode{i} of type \tcode{Shape} from \tcode{0} to \tcode{shape}, +where \tcode{args} is a pack of lvalue subexpressions +referring to the value completion result datums of the input sender, and +\item +propagates all completion operations sent by \tcode{sndr}. +\end{itemize} + +\rSec3[exec.split]{\tcode{execution::split}} + +\pnum +\tcode{split} adapts an arbitrary sender +into a sender that can be connected multiple times. + +\pnum +Let \exposid{split-env} be the type of an environment +such that, given an instance \tcode{env}, +the expression \tcode{get_stop_token(env)} is well-formed and +has type \tcode{inplace_stop_token.} + +\pnum +The name \tcode{split} denotes a pipeable sender adaptor object. +For a subexpression \tcode{sndr}, let \tcode{Sndr} be \tcode{decltype((sndr))}. +If \tcode{\libconcept{sender_in}} is \tcode{false}, +\tcode{split(sndr)} is ill-formed. + +\pnum +Otherwise, the expression \tcode{split(sndr)} is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(split, {}, sndr)) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. +\begin{note} +The default implementation of \tcode{transform_sender} +will have the effect of connecting the sender to a receiver. +It will return a sender with a different tag type. +\end{note} + +\pnum +Let \exposid{local-state} denote the following exposition-only class template: + +\begin{codeblock} +namespace std::execution { + struct @\exposid{local-state-base}@ { // \expos + virtual ~@\exposid{local-state-base}@() = default; + virtual void @\exposid{notify}@() noexcept = 0; // \expos + }; + + template + struct @\exposid{local-state}@ : @\exposid{local-state-base}@ { // \expos + using @\exposid{on-stop-callback}@ = // \expos + stop_callback_of_t>, @\exposid{on-stop-request}@>; + + @\exposid{local-state}@(Sndr&& sndr, Rcvr& rcvr) noexcept; + ~@\exposid{local-state}@(); + + void @\exposid{notify}@() noexcept override; + + private: + optional<@\exposid{on-stop-callback}@> @\exposid{on_stop}@; // \expos + @\exposid{shared-state}@* @\exposid{sh_state}@; // \expos + Rcvr* @\exposid{rcvr}@; // \expos + }; +} +\end{codeblock} + +\begin{itemdecl} +local-state(Sndr&& sndr, Rcvr& rcvr) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +auto& [_, data, _] = sndr; +this->@\exposid{sh_state}@ = data.sh_state.get(); +this->@\exposid{sh_state}@->@\exposid{inc-ref}@(); +this->@\exposid{rcvr}@ = addressof(rcvr); +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +~@\exposid{local-state}@(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +sh_state->@\exposid{dec-ref}@(); +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{notify}@() noexcept override; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +@\exposid{on_stop}@.reset(); +visit( + [this](const auto& tupl) noexcept -> void { + apply( + [this](auto tag, const auto&... args) noexcept -> void { + tag(std::move(*@\exposid{rcvr}@), args...); + }, + tupl); + }, + @\exposid{sh_state}@->result); +\end{codeblock} +\end{itemdescr} + +\pnum +Let \exposid{split-receiver} denote +the following exposition-only class template: +\begin{codeblock} +namespace std::execution { + template + struct @\exposid{split-receiver}@ { // \expos + using receiver_concept = receiver_t; + + template + void @\exposid{complete}@(Tag, Args&&... args) noexcept { // \expos + using tuple_t = @\exposid{decayed-tuple}@; + try { + @\exposid{sh_state}@->result.template emplace(Tag(), std::forward(args)...); + } catch (...) { + using tuple_t = tuple; + @\exposid{sh_state}@->result.template emplace(set_error, current_exception()); + } + @\exposid{sh_state}@->notify(); + } + + template + void set_value(Args&&... args) && noexcept { + @\exposid{complete}@(execution::set_value, std::forward(args)...); + } + + template + void set_error(Error&& err) && noexcept { + @\exposid{complete}@(execution::set_error, std::forward(err)); + } + + void set_stopped() && noexcept { + @\exposid{complete}@(execution::set_stopped); + } + + struct @\exposid{env}@ { // \expos + @\exposid{shared-state}@* @\exposid{sh-state}@; // \expos + + inplace_stop_token query(get_stop_token_t) const noexcept { + return @\exposid{sh-state}@->stop_src.get_token(); + } + }; + + @\exposid{env}@ get_env() const noexcept { + return @\exposid{env}@{@\exposid{sh_state}@}; + } + + @\exposid{shared-state}@* @\exposid{sh_state}@; // \expos + }; +} +\end{codeblock} + +\pnum +Let \exposid{shared-state} denote the following exposition-only class template: +\begin{codeblock} +namespace std::execution { + template + struct @\exposid{shared-state}@ { + using @\exposid{variant-type}@ = @\seebelow@; // \expos + using @\exposid{state-list-type}@ = @\seebelow@; // \expos + + explicit @\exposid{shared-state}@(Sndr&& sndr); + + void @\exposid{start-op}@() noexcept; // \expos + void @\exposid{notify}@() noexcept; // \expos + void @\exposid{inc-ref}@() noexcept; // \expos + void @\exposid{dec-ref}@() noexcept; // \expos + + inplace_stop_source @\exposid{stop_src}@{}; // \expos + @\exposid{variant-type}@ @\exposid{result}@{}; // \expos + @\exposid{state-list-type}@ @\exposid{waiting_states}@; // \expos + atomic @\exposid{completed}@{false}; // \expos + atomic @\exposid{ref_count}@{1}; // \expos + connect_result_t> @\exposid{op_state}@; // \expos + }; +} +\end{codeblock} + +\pnum +Let \tcode{Sigs} be a pack of the arguments +to the \tcode{completion_signatures} specialization +named by \tcode{completion_signatures_of_t}. +For type \tcode{Tag} and pack \tcode{Args}, +let \exposid{as-tuple} be an alias template +such that \tcode{\exposid{as-tuple}} denotes +the type \tcode{\exposid{decayed-tuple}}. +Then \exposid{variant-type} denotes the type +\begin{codeblock} +variant, tuple, @\exposid{as-tuple}@...> +\end{codeblock} +but with duplicate types removed. + +\pnum +Let \exposid{state-list-type} be a type +that stores a list of pointers to \exposid{local-state-base} objects and +that permits atomic insertion. + +\begin{itemdecl} +explicit @\exposid{shared-state}@(Sndr&& sndr); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Initializes \exposid{op_state} with the result of +\tcode{connect(std::forward(sndr), \exposid{split-re\-ceiver}\{this\})}. + +\pnum +\ensures +\exposid{waiting_states} is empty, and \exposid{completed} is \tcode{false}. +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{start-op}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Evaluates \tcode{\exposid{inc-ref}()}. +If \tcode{stop_src.stop_requested()} is \tcode{true}, +evaluates \tcode{\exposid{notify}()}; +otherwise, evaluates \tcode{start(\exposid{op_state})}. +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{notify}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Atomically does the following: +\begin{itemize} +\item +Sets \tcode{completed} to \tcode{true}, and +\item +Exchanges \tcode{waiting_states} with an empty list, +storing the old value in a local \tcode{prior_states}. +\end{itemize} +Then, for each pointer \tcode{p} in \tcode{prior_states}, +evaluates \tcode{p->\exposid{notify}()}. +Finally, evaluates \tcode{\exposid{dec-ref}()}. +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{inc-ref}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Increments \exposid{ref_count}. +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{dec-ref}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Decrements \exposid{ref_count}. +If the new value of \exposid{ref_count} is \tcode{0}, +calls \tcode{delete this}. + +\pnum +\sync +If an evaluation of \tcode{\exposid{dec-ref}()} does not +decrement the \tcode{ref_count} to \tcode{0} then +synchronizes with the evaluation of \tcode{dec-ref()} +that decrements \tcode{ref_count} to \tcode{0}. +\end{itemdescr} + +\pnum +Let \exposid{split-impl-tag} be an empty exposition-only class type. +Given an expression \tcode{sndr}, +the expression \tcode{split.transform_sender(sndr)} is equivalent to: +\begin{codeblock} +auto&& [tag, _, child] = sndr; +auto* sh_state = new @\exposid{shared-state}@{std::forward_like(child)}; +return @\exposid{make-sender}@(@\exposid{split-impl-tag}@(), @\exposid{shared-wrapper}@{sh_state, tag}); +\end{codeblock} +where \exposid{shared-wrapper} is an exposition-only class +that manages the reference count of the \exposid{shared-state} object +pointed to by sh_state. +\exposid{shared-wrapper} models copyable +with move operations nulling out the moved-from object, +copy operations incrementing the reference count +by calling \tcode{sh_state->\exposid{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 \tcode{sh_state->\exposid{dec-ref}()}. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{split-impl-tag} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@<@\exposid{split-impl-tag}@> : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-state}@ = @\seebelow@; + static constexpr auto @\exposid{start}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +The member +\tcode{\exposid{impls-for}<\exposid{split-impl-tag}>::\exposid{get-state}} +is initialized with a callable object equivalent to +the following lambda expression: +\begin{codeblock} +[](Sndr&& sndr, auto& rcvr) noexcept { + return @\exposid{local-state}@{std::forward(sndr), rcvr}; +} +\end{codeblock} + +\pnum +The member +\tcode{\exposid{impls-for}<\exposid{split-impl-tag}>::\exposid{start}} +is initialized with a callable object +that has a function call operator equivalent to the following: +\begin{codeblock} +template +void operator()(local-state& state, Rcvr& rcvr) const noexcept; +\end{codeblock} + +\effects +If \tcode{state.\exposid{sh_state}->\exposid{completed}} is \tcode{true}, +evaluates \tcode{state.\exposid{notify}()} and returns. +Otherwise, does the following in order: +\begin{itemize} +\item +Evaluates +\begin{codeblock} +state.@\exposid{on_stop}@.emplace( + get_stop_token(get_env(rcvr)), + @\exposid{on-stop-request}@{state.@\exposid{sh_state}@->@\exposid{stop_src}@}); +\end{codeblock} +\item +Then atomically does the following: +\begin{itemize} +\item +Reads the value \tcode{c} of +\tcode{state.\exposid{sh_state}->\exposid{completed}}, and +\item +Inserts \tcode{addressof(state)} into +\tcode{state.\exposid{sh_state}->\exposid{waiting_states}} +if \tcode{c} is \tcode{false}. +\end{itemize} +\item +If \tcode{c} is \tcode{true}, +calls \tcode{state.\exposid{notify}()} and returns. +\item +Otherwise, +if \tcode{addressof(state)} is the first item added to +\tcode{state.\exposid{sh_state}->\exposid{waiting_states}}, +evaluates \tcode{state.\exposid{sh_state}->\exposid{start-op}()}. +\end{itemize} + +\rSec3[exec.when.all]{\tcode{execution::when_all}} + +\pnum +\tcode{when_all} and \tcode{when_all_with_variant} +both adapt multiple input senders into a sender +that completes when all input senders have completed. +\tcode{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. +\tcode{when_all_with_variant(sndrs...)} is semantically equivalent to +w\tcode{hen_all(into_variant(sndrs)...)}, +where \tcode{sndrs} is a pack of subexpressions +whose types model \libconcept{sender}. + +\pnum +The names \tcode{when_all} and \tcode{when_all_with_variant} denote +customization point objects. +Let \tcode{sndrs} be a pack of subexpressions, +let \tcode{Sndrs} be a pack of the types \tcode{decltype((sndrs))...}, and +let \tcode{CD} be +the type \tcode{common_type_t}. +The expressions \tcode{when_all(sndrs...)} and +\tcode{when_all_with_variant(sndrs...)} are ill-formed +if any of the following is \tcode{true}: +\begin{itemize} +\item +\tcode{sizeof...(sndrs)} is \tcode{0}, or +\item +\tcode{(sender \&\& ...)} is \tcode{false}, or +\item +\tcode{CD} is ill-formed. +\end{itemize} + +\pnum +The expression \tcode{when_all(sndrs...)} is expression-equivalent to: +\begin{codeblock} +transform_sender(CD(), @\exposid{make-sender}@(when_all, {}, sndrs...)) +\end{codeblock} + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{when_all_t} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-attrs}@ = @\seebelow@; + static constexpr auto @\exposid{get-env}@ = @\seebelow@; + static constexpr auto @\exposid{get-state}@ = @\seebelow@; + static constexpr auto @\exposid{start}@ = @\seebelow@; + static constexpr auto @\exposid{complete}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-attrs}} +is initialized with a callable object +equivalent to the following lambda expression: +\begin{codeblock} +[](auto&&, auto&&... child) noexcept { + if constexpr (@\libconcept{same_as}@) { + return empty_env(); + } else { + return @\exposid{MAKE-ENV}@(get_domain, CD()); + } +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-env}} +is initialized with a callable object +equivalent to the following lambda expression: +\begin{codeblock} +[](auto&&, State& state, const Receiver& rcvr) noexcept { + return @\exposid{JOIN-ENV}@( + @\exposid{MAKE-ENV}@(get_stop_token, state.@\exposid{stop_src}@.get_token()), get_env(rcvr)); +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-state}} +is initialized with a callable object +equivalent to the following lambda expression: +\begin{codeblock} +[](Sndr&& sndr, Rcvr& rcvr) noexcept(@$e$@) -> decltype(e) { + return @$e$@; +} +\end{codeblock} +where $e$ is the expression +\begin{codeblock} +std::forward(sndr).apply(@\exposid{make-state}@()) +\end{codeblock} +and where \exposid{make-state} is the following exposition-only class template: +\begin{codeblock} +template +concept @\defexposconcept{max-1-sender-in}@ = @\libconcept{sender_in}@ && // \expos + (tuple_size_v> <= 1); + +enum class @\exposid{disposition}@ { @\exposid{started}@, @\exposid{error}@, @\exposid{stopped}@ }; // \expos + +template +struct @\exposid{make-state}@ { + template<@\exposconcept{max-1-sender-in}@>... Sndrs> + auto operator()(auto, auto, Sndrs&&... sndrs) const { + using values_tuple = @\seebelow@; + using errors_variant = @\seebelow@; + using stop_callback = stop_callback_of_t>, @\exposid{on-stop-request}@>; + + struct @\exposid{state-type}@ { + void @\exposid{arrive}@(Rcvr& rcvr) noexcept { // \expos + if (0 == --count) { + @\exposid{complete}@(rcvr); + } + } + + void @\exposid{complete}@(Rcvr& rcvr) noexcept; // \expos + + atomic @\exposid{count}@{sizeof...(sndrs)}; // \expos + inplace_stop_source @\exposid{stop_src}@{}; // \expos + atomic<@\exposid{disposition}@> disp{@\exposidnc{disposition}@::@\exposidnc{started}@}; // \expos + errors_variant @\exposid{errors}@{}; // \expos + values_tuple @\exposid{values}@{}; // \expos + optional @\exposid{on_stop}@{nullopt}; // \expos + }; + + return @\exposid{state-type}@{}; + } +}; +\end{codeblock} + +\pnum +Let \exposid{copy-fail} be \tcode{exception_ptr} +if decay-copying any of the child senders' result datums can potentially throw; +otherwise, \exposid{none-such}, +where \exposid{none-such} is an unspecified empty class type. + +\pnum +The alias \tcode{values_tuple} denotes the type +\begin{codeblock} +tuple, decayed-tuple, optional>...> +\end{codeblock} +if that type is well-formed; otherwise, \tcode{tuple<>}. + +\pnum +The alias \tcode{errors_variant} denotes +the type \tcode{variant<\exposid{none-such}, \exposid{copy-fail}, Es...>} +with duplicate types removed, +where \tcode{Es} is the pack of the decayed types +of all the child senders' possible error result datums. + +\pnum +The member +\tcode{void \exposid{state-type}::\exposid{complete}(Rcvr\& rcvr) noexcept} +behaves as follows: +\begin{itemize} +\item +If \tcode{disp} is equal to \tcode{\exposid{disposition}::\exposid{started}}, +evaluates: +\begin{codeblock} +auto tie = [](tuple& t) noexcept { return tuple(t); }; +auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); }; + +@\exposid{on_stop}@.reset(); +apply( + [&](auto&... opts) noexcept { + apply(set, tuple_cat(tie(*opts)...)); + }, + values); +\end{codeblock} +\item +Otherwise, +if \tcode{disp} is equal to \tcode{\exposid{disposition}::\exposid{error}}, +evaluates: +\begin{codeblock} +@\exposid{on_stop}@.reset(); +visit( + [&](Error& error) noexcept { + if constexpr (!@\libconcept{same_as}@) { + set_error(std::move(rcvr), std::move(error)); + } + }, + errors); +\end{codeblock} +\item +Otherwise, evaluates: +\begin{codeblock} +@\exposid{on_stop}@.reset(); +set_stopped(std::move(rcvr)); +\end{codeblock} +\end{itemize} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{start}} +is initialized with a callable object +equivalent to the following lambda expression: +\begin{codeblock} +[]( + State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void { + state.@\exposid{on_stop}@.emplace( + get_stop_token(get_env(rcvr)), + @\exposid{on-stop-request}@{state.@\exposid{stop_src}@}); + if (state.@\exposid{stop_src}@.stop_requested()) { + state.@\exposid{on_stop.}@reset(); + set_stopped(std::move(rcvr)); + } else { + (start(ops), ...); + } +} +\end{codeblock} + +\pnum +The member \exposid{\tcode{impls-for}::\exposid{complete}} +is initialized with a callable object +equivalent to the following lambda expression: +\begin{codeblock} +[]( + this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void { + if constexpr (@\libconcept{same_as}@) { + if (@\exposid{disposition}@::@\exposid{error}@ != state.disp.exchange(@\exposid{disposition}@::@\exposid{error}@)) { + state.@\exposid{stop_src}@.request_stop(); + @\exposid{TRY-EMPLACE-ERROR}@(state.errors, std::forward(args)...); + } + } else if constexpr (@\libconcept{same_as}@) { + auto expected = @\exposid{disposition}@::@\exposid{started}@; + if (state.disp.compare_exchange_strong(expected, @\exposid{disposition}@::@\exposid{stopped}@)) { + state.@\exposid{stop_src}@.request_stop(); + } + } else if constexpr (!@\libconcept{same_as}@>) { + if (state.disp == @\exposid{disposition}@::@\exposid{started}@) { + auto& opt = get(state.values); + @\exposid{TRY-EMPLACE-VALUE}@(complete, opt, std::forward(args)...); + } + } + state.@\exposid{arrive}@(rcvr); +} +\end{codeblock} +where \tcode{\exposid{TRY-EMPLACE-ERROR}(v, e)}, +for subexpressions \tcode{v} and \tcode{e}, is equivalent to: +\begin{codeblock} +try { + v.template emplace(e); +} catch (...) { + v.template emplace(current_exception()); +} +\end{codeblock} +if the expression \tcode{decltype(auto(e))(e)} is potentially throwing; +otherwise, \tcode{v.template emplace(e)}; +and where \tcode{\exposid{TRY-EMPLACE-VALUE}(c, o, as...)}, +for subexpressions \tcode{c}, \tcode{o}, and pack of subexpressions \tcode{as}, +is equivalent to: +\begin{codeblock} +try { + o.emplace(as...); +} catch (...) { + c(Index(), state, rcvr, set_error, current_exception()); + return; +} +\end{codeblock} +if the expression \tcode{decayed-tuple\{as...\}} +is potentially throwing; +otherwise, \tcode{o.emplace(\linebreak as...)}. + +\pnum +The expression \tcode{when_all_with_variant(sndrs...)} +is expression-equivalent to: +\begin{codeblock} +transform_sender(CD(), @\exposid{make-sender}@(when_all_with_variant, {}, sndrs...)); +\end{codeblock} + +\pnum +Given subexpressions \tcode{sndr} and \tcode{env}, +if +\tcode{\exposconcept{sender-for}} +is \tcode{false}, +then the expression \tcode{when_all_with_variant.transform_sender(sndr, env)} +is ill-formed; +otherwise, it is equivalent to: +\begin{codeblock} +auto&& [_, _, ...child] = sndr; +return when_all(into_variant(std::forward_like(child))...); +\end{codeblock} +\begin{note} +This causes the \tcode{when_all_with_variant(sndrs...)} sender +to become \tcode{when_all(into_variant(sndrs)...)} +when it is connected with a receiver +whose execution domain does not customize \tcode{when_all_with_variant}. +\end{note} + +\rSec3[exec.into.variant]{\tcode{execution::into_variant}} + +\pnum +\tcode{into_variant} adapts a sender with multiple value completion signatures +into a sender with just one value completion signature +consisting of a \tcode{variant} of \tcode{tuple}s. + +\pnum +The name \tcode{into_variant} denotes a pipeable sender adaptor object. +For a subexpression \tcode{sndr}, let \tcode{Sndr} be \tcode{decltype((sndr))}. +If \tcode{Sndr} does not satisfy \libconcept{sender}, +\tcode{into_variant(sndr)} is ill-formed. + +\pnum +Otherwise, the expression \tcode{into_variant(sndr)} +is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(into_variant, {}, sndr)) +\end{codeblock} +except that \tcode{sndr} is only evaluated once. + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{into_variant} as follows: +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-state}@ = @\seebelow@; + static constexpr auto @\exposid{complete}@ = @\seebelow@; + }; +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{get-state}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](Sndr&& sndr, Rcvr& rcvr) noexcept + -> type_identity, env_of_t>> { + return {}; +} +\end{codeblock} + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{complete}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[]( + auto, State, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void { + if constexpr (@\libconcept{same_as}@) { + using variant_type = typename State::type; + @\exposid{TRY-SET-VALUE}@(rcvr, variant_type(@\exposid{decayed-tuple}@{std::forward(args)...})); + } else { + Tag()(std::move(rcvr), std::forward(args)...); + } +} +\end{codeblock} + +\rSec3[exec.stopped.opt]{\tcode{execution::stopped_as_optional}} + +\pnum +\tcode{stopped_as_optional} maps a sender's stopped completion operation +into a value completion operation as an disengaged \tcode{optional}. +The sender's value completion operation +is also converted into an \tcode{optional}. +The result is a sender that never completes with stopped, +reporting cancellation by completing with an disengaged \tcode{optional}. + +\pnum +The name \tcode{stopped_as_optional} denotes a pipeable sender adaptor object. +For a subexpression \tcode{sndr}, let \tcode{Sndr} be \tcode{decltype((sndr))}. +The expression \tcode{stopped_as_optional(sndr)} is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(stopped_as_optional, {}, sndr)) +\end{codeblock} +except that \tcode{sndr} is only evaluated once. + +\pnum +Let \tcode{sndr} and \tcode{env} be subexpressions +such that \tcode{Sndr} is \tcode{decltype((sndr))} and +\tcode{Env} is \tcode{decltype((env))}. +If \tcode{\exposconcept{sender-for}} +is \tcode{false}, or +if the type \tcode{\exposid{single-sender-value-type}} +is ill-formed or \tcode{void}, +then the expression \tcode{stopped_as_optional.transform_sender(sndr, env)} +is ill-formed; +otherwise, it is equivalent to: +\begin{codeblock} +auto&& [_, _, child] = sndr; +using V = @\exposid{single-sender-value-type}@; +return let_stopped( + then(std::forward_like(child), + [](Ts&&... ts) noexcept(is_nothrow_constructible_v) { + return optional(in_place, std::forward(ts)...); + }), + []() noexcept { return just(optional()); }); +\end{codeblock} + +\rSec3[exec.stopped.err]{\tcode{execution::stopped_as_error}} + +\pnum +\tcode{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. + +\pnum +The name \tcode{stopped_as_error} denotes a pipeable sender adaptor object. +For some subexpressions \tcode{sndr} and \tcode{err}, +let \tcode{Sndr} be \tcode{decltype((sndr))} and +let \tcode{Err} be \tcode{decltype((err))}. +If the type \tcode{Sndr} does not satisfy \libconcept{sender} or +if the type \tcode{Err} does not satisfy \exposconcept{movable-value}, +\tcode{stopped_as_error(sndr, err)} is ill-formed. +Otherwise, the expression \tcode{stopped_as_error(sndr, err)} +is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(stopped_as_error, err, sndr)) +\end{codeblock} +except that \tcode{sndr} is only evaluated once. + +\pnum +Let \tcode{sndr} and \tcode{env} be subexpressions +such that \tcode{Sndr} is \tcode{decltype((sndr))} and +\tcode{Env} is \tcode{decltype((env))}. +If \tcode{\exposconcept{sender-for}} is \tcode{false}, +then the expression \tcode{stopped_as_error.transform_sender(sndr, env)} +is ill-formed; +otherwise, it is equivalent to: +\begin{codeblock} +auto&& [_, err, child] = sndr; +using E = decltype(auto(err)); +return let_stopped( + std::forward_like(child), + [err = std::forward_like(err)]() mutable noexcept(is_nothrow_move_constructible_v) { + return just_error(std::move(err)); + }); +\end{codeblock} + +\rSec2[exec.consumers]{Sender consumers} + +\rSec3[exec.sync.wait]{\tcode{this_thread::sync_wait}} + +\pnum +\tcode{this_thread::sync_wait} and \tcode{this_thread::sync_wait_with_variant} +are used +to block the current thread of execution +until the specified sender completes and +to return its async result. +\tcode{sync_wait} mandates +that the input sender has exactly one value completion signature. + +\pnum +Let \exposid{sync-wait-env} be the following exposition-only class type: +\begin{codeblock} +namespace std::this_thread { + struct @\exposid{sync-wait-env}@ { + execution::run_loop* @\exposid{loop}@; // \expos + + auto query(execution::get_scheduler_t) const noexcept { + return @\exposid{loop}@->get_scheduler(); + } + + auto query(execution::get_delegation_scheduler_t) const noexcept { + return @\exposid{loop}@->get_scheduler(); + } + }; +} +\end{codeblock} + +\pnum +Let \exposid{sync-wait-result-type} and +\exposid{sync-wait-with-variant-result-type} +be exposition-only alias templates defined as follows: +\begin{codeblock} +namespace std::this_thread { + template Sndr> + using sync-wait-result-type = + optional>; + + template Sndr> + using @\exposid{sync-wait-with-variant-result-type}@ = + optional>; +} +\end{codeblock} + +\pnum +The name \tcode{this_thread::sync_wait} denotes a customization point object. +For a subexpression \tcode{sndr}, let \tcode{Sndr} be \tcode{decltype((sndr))}. +If \tcode{\libconcept{sender_in}} +is \tcode{false}, +the expression \tcode{this_thread::sync_wait(sndr)} is ill-formed. +Otherwise, it is expression-equivalent to the following, +except that \tcode{sndr} is evaluated only once: +\begin{codeblock} +apply_sender(@\exposid{get-domain-early}@(sndr), sync_wait, sndr) +\end{codeblock} +\mandates +\begin{itemize} +\item +The type \tcode{\exposid{sync-wait-result-type}} is well-formed. +\item +\tcode{\libconcept{same_as}>} +is \tcode{true}, where $e$ is the apply_sender expression above. +\end{itemize} + +\pnum +Let \exposid{sync-wait-state} and \exposid{sync-wait-receiver} +be the following exposition-only class templates: +\begin{codeblock} +namespace std::this_thread { + template + struct @\exposid{sync-wait-state}@ { // \expos + execution::run_loop @\exposid{loop}@; // \expos + exception_ptr @\exposid{error}@; // \expos + @\exposid{sync-wait-result-type}@ @\exposidnc{result}@; // \expos + }; + + template + struct @\exposid{sync-wait-receiver}@ { // \expos + using receiver_concept = execution::receiver_t; + @\exposidnc{sync-wait-state}@* @\exposid{state}@; // \expos + + template + void set_value(Args&&... args) && noexcept; + + template + void set_error(Error&& err) && noexcept; + + void set_stopped() && noexcept; + + @\exposid{sync-wait-env}@ get_env() const noexcept { return {&@\exposid{state}@->@\exposid{loop}@}; } + }; +} +\end{codeblock} + +\begin{itemdecl} +template +void set_value(Args&&... args) && noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +try { + @\exposid{state}@->@\exposid{result}@.emplace(std::forward(args)...); +} catch (...) { + @\exposid{state}@->@\exposid{error}@ = current_exception(); +} +@\exposid{state}@->@\exposid{loop}@.finish(); +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +template +void set_error(Error&& err) && noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +@\exposid{state}@->@\exposid{error}@ = @\exposid{AS-EXCEPT-PTR}@(std::forward(err)); // see \ref{exec.general} +@\exposid{state}@->@\exposid{loop}@.finish(); +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +void set_stopped() && noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to \tcode{\exposid{state}->\exposid{loop}.finish()}. +\end{itemdescr} + +\pnum +For a subexpression \tcode{sndr}, let \tcode{Sndr} be \tcode{decltype((sndr))}. +If \tcode{\libconcept{sender_to}>} +is \tcode{false}, +the expression \tcode{sync_wait.apply_sender(sndr)} is ill-formed; +otherwise, it is equivalent to: +\begin{codeblock} +@\exposid{sync-wait-state}@ state; +auto op = connect(sndr, @\exposid{sync-wait-receiver}@{&state}); +start(op); + +state.@\exposid{loop}@.run(); +if (state.@\exposid{error}@) { + rethrow_exception(std::move(state.error)); +} +return std::move(state.@\exposid{result}@); +\end{codeblock} + +\pnum +The behavior of \tcode{this_thread::sync_wait(sndr)} is undefined unless: +\begin{itemize} +\item +It blocks the current thread of execution\iref{defns.block} +with forward progress guarantee delegation\iref{intro.progress} +until the specified sender completes. +\begin{note} +The default implementation of \tcode{sync_wait} achieves +forward progress guarantee delegation by providing a \tcode{run_loop} scheduler +via the \tcode{get_delegation_scheduler} query +on the \exposid{sync-wait-receiver}'s environment. +The \tcode{run_loop} is driven by the current thread of execution. +\end{note} +\item +It returns the specified sender's async results as follows: +\begin{itemize} +\item +For a value completion, +the result datums are returned in +a \tcode{tuple} in an engaged \tcode{optional} object. +\item +For an error completion, an exception is thrown. +\item +For a stopped completion, a disengaged \tcode{optional} object is returned. +\end{itemize} +\end{itemize} + +\rSec3[exec.sync.wait.var]{\tcode{this_thread::sync_wait_with_variant}} + +\pnum +The name \tcode{this_thread::sync_wait_with_variant} denotes +a customization point object. +For a subexpression \tcode{sndr}, +let \tcode{Sndr} be \tcode{decltype(into_variant(sndr))}. +If \tcode{\libconcept{sender_in}} +is \tcode{false}, +\tcode{this_thread::sync_wait_with_variant(sndr)} is ill-formed. +Otherwise, it is expression-equivalent to the following, +except \tcode{sndr} is evaluated only once: +\begin{codeblock} +apply_sender(@\exposid{get-domain-early}@(sndr), sync_wait_with_variant, sndr) +\end{codeblock} +\mandates +\begin{itemize} +\item +The type \tcode{\exposid{sync-wait-with-variant-result-type}} +is well-formed. +\item +\tcode{\libconcept{same_as}>} +is \tcode{true}, where $e$ is the \tcode{ap\-ply_sender} expression above. +\end{itemize} + +\pnum +If \tcode{\exposconcept{callable}} is \tcode{false}, +the expression \tcode{sync_wait_with_variant.apply_sender(\linebreak sndr)} is ill-formed. +Otherwise, it is equivalent to: +\begin{codeblock} +using result_type = @\exposid{sync-wait-with-variant-result-type}@; +if (auto opt_value = sync_wait(into_variant(sndr))) { + return result_type(std::move(get<0>(*opt_value))); +} +return result_type(nullopt); +\end{codeblock} + +\pnum +The behavior of \tcode{this_thread::sync_wait_with_variant(sndr)} +is undefined unless: +\begin{itemize} +\item +It blocks the current thread of execution\iref{defns.block} +with forward progress guarantee delegation\iref{intro.progress} +until the specified sender completes. +\begin{note} +The default implementation of \tcode{sync_wait_with_variant} achieves +forward progress guarantee delegation by relying on +the forward progress guarantee delegation provided by \tcode{sync_wait}. +\end{note} +\item +It returns the specified sender's async results as follows: +\begin{itemize} +\item +For a value completion, +the result datums are returned in an engaged \tcode{optional} object +that contains a \tcode{variant} of \tcode{tuple}s. +\item +For an error completion, an exception is thrown. +\item +For a stopped completion, a disengaged \tcode{optional} object is returned. +\end{itemize} +\end{itemize} + +\rSec1[exec.util]{Sender/receiver utilities} + +\rSec2[exec.util.cmplsig]{\tcode{execution::completion_signatures}} + +\pnum +\tcode{completion_signatures} is a type +that encodes a set of completion signatures\iref{exec.async.ops}. + +\pnum +\begin{example} +\begin{codeblock} +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()>; +}; +\end{codeblock} +Declares \tcode{my_sender} to be a sender +that can complete by calling one of the following +for a receiver expression \tcode{rcvr}: +\begin{itemize} +\item \tcode{set_value(rcvr)} +\item \tcode{set_value(rcvr, int\{...\}, float\{...\})} +\item \tcode{set_error(rcvr, exception_ptr\{...\})} +\item \tcode{set_error(rcvr, error_code\{...\})} +\item \tcode{set_stopped(rcvr)} +\end{itemize} +\end{example} + +\pnum +This subclause makes use of the following exposition-only entities: +\begin{codeblock} +template + concept @\defexposconcept{completion-signature}@ = @\seebelow@; +\end{codeblock} + +\pnum +A type \tcode{Fn} satisfies \exposconcept{completion-signature} +if and only if it is a function type with one of the following forms: +\begin{itemize} +\item +\tcode{set_value_t(Vs...)}, +where \tcode{Vs} is a pack of object or reference types. +\item +\tcode{set_error_t(Err)}, +where \tcode{Err} is an object or reference type. +\item +\tcode{set_stopped_t()} +\end{itemize} + +\pnum +\begin{codeblock} +template + struct @\exposid{indirect-meta-apply}@ { + template class T, class... As> + using @\exposid{meta-apply}@ = T; // \expos + }; + +template + concept @\defexposconcept{always-true}@ = true; // \expos + +template class Tuple, + template class Variant> + using @\exposid{gather-signatures}@ = @\seebelow@; +\end{codeblock} + +\pnum +Let \tcode{Fns} be a pack of the arguments of +the \tcode{completion_signatures} specialization named by \tcode{Completions}, +let \tcode{TagFns} be a pack of the function types in \tcode{Fns} +whose return types are \tcode{Tag}, and +let $\tcode{Ts}_n$ be a pack of the function argument types +in the $n$-th type in \tcode{TagFns}. +Then, given two variadic templates \tcode{Tuple} and \tcode{Variant}, +the type \tcode{\exposid{gather-signatures}} +names the type +\begin{codeblock} +@\exposid{META-APPLY}@(Variant, @\exposid{META-APPLY}@(Tuple, Ts@$_0$@...), + @\itcorr[1]\exposid{META-APPLY}@(Tuple, Ts@$_1$@...), + @\itcorr[1]\ldots@, + @\itcorr[1]\exposid{META-APPLY}@(Tuple, Ts@$_{m-1}$@...)) +\end{codeblock} +where $m$ is the size of the pack \tcode{TagFns} and +\tcode{META-APPLY(T, As...)} is equivalent to: +\begin{codeblock} +typename @\exposid{indirect-meta-apply}@<@\exposid{always-true}@>::template @\exposid{meta-apply}@ +\end{codeblock} + +\pnum +\begin{note} +The purpose of \exposid{META-APPLY} is to make it valid +to use non-variadic templates as \tcode{Variant} and \tcode{Tuple} arguments +to \exposid{gather-signatures}. +\end{note} + +\pnum +\begin{codeblock} +namespace std::execution { + template<@\exposconcept{completion-signature}@... Fns> + struct completion_signatures {}; + + template class Tuple = @\exposid{decayed-tuple}@, + template class Variant = @\exposid{variant-or-empty}@> + requires @\libconcept{sender_in}@ + using value_types_of_t = + @\exposid{gather-signatures}@, Tuple, Variant>; + + template class Variant = @\exposid{variant-or-empty}@> + requires @\libconcept{sender_in}@ + using error_types_of_t = + @\exposid{gather-signatures}@, + type_identity_t, Variant>; + + template + requires @\libconcept{sender_in}@ + inline constexpr bool sends_stopped = + !@\libconcept{same_as}@<@\exposid{type-list}@<>, + @\exposid{gather-signatures}@, + @\exposid{type-list}@, @\exposid{type-list}@>>; +} +\end{codeblock} + +\rSec2[exec.util.cmplsig.trans]{execution::transform_completion_signatures} + +\pnum +\tcode{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 \tcode{completion_signature}s. +\pnum +\begin{example} +Given a sender \tcode{Sndr} and an environment \tcode{Env}, +adapt the completion signatures of \tcode{Sndr} by +lvalue-ref qualifying the values, +adding an additional \tcode{exception_ptr} error completion +if its not already there, and +leaving the other completion signatures alone. +\begin{codeblock} +template + using my_set_value_t = + completion_signatures< + set_value_t(add_lvalue_reference_t...)>; + +using my_completion_signatures = + transform_completion_signatures< + completion_signatures_of_t, + completion_signatures, + my_set_value_t>; +\end{codeblock} +\end{example} + +\pnum +This subclause makes use of the following exposition-only entities: +\begin{codeblock} +template + using default-set-value = + completion_signatures; + +template + using default-set-error = + completion_signatures; +\end{codeblock} + +\pnum +\begin{codeblock} +namespace std::execution { + template<@\exposconcept{valid-completion-signatures}@ InputSignatures, + @\exposconcept{valid-completion-signatures}@ AdditionalSignatures = completion_signatures<>, + template class SetValue = @\exposid{default-set-value}@, + template class SetError = @\exposid{default-set-error}@, + @\exposconcept{valid-completion-signatures}@ SetStopped = completion_signatures> + using transform_completion_signatures = completion_signatures<@\seebelow@>; +} +\end{codeblock} + +\pnum +\tcode{SetValue} shall name an alias template +such that for any pack of types \tcode{As}, +the type \tcode{SetValue} is either ill-formed or else +\tcode{\exposconcept{valid-completion-signatures}>} is satisfied. +\tcode{SetError} shall name an alias template +such that for any type \tcode{Err}, +\tcode{SetError} is either ill-formed or else +\tcode{\exposconcept{valid-completion-signatures}>} is satisfied. + +\pnum +Let \tcode{Vs} be a pack of the types in the \exposid{type-list} named by +\tcode{\exposid{gather-signatures}}. + +\pnum +Let \tcode{Es} be a pack of the types in the \exposid{type-list} named by +\tcode{\exposid{gather-signatures}}, +where \exposid{error-list} is an alias template +such that \tcode{\exposid{error-list}<\linebreak Ts...>} is +\tcode{\exposid{type-list}...>}. + +\pnum +Let \tcode{Ss} name the type \tcode{completion_signatures<>} if +\tcode{\exposid{gather-signatures}} +is an alias for the type \tcode{\exposid{type-list}<>}; +otherwise, \tcode{SetStopped}. + +\pnum +If any of the above types are ill-formed, +then +\begin{codeblock} +transform_completion_signatures +\end{codeblock} +is ill-formed. +Otherwise, +\begin{codeblock} +transform_completion_signatures +\end{codeblock} +is the type \tcode{completion_signatures} +where \tcode{Sigs...} is the unique set of types in all the template arguments +of all the \tcode{completion_signatures} specializations in the set +\tcode{AdditionalSignatures}, \tcode{Vs...}, \tcode{Es...}, \tcode{Ss}. + +\rSec1[exec.ctx]{Execution contexts} + +\rSec2[exec.run.loop]{\tcode{execution::run_loop}} + +\rSec3[exec.run.loop.general]{General} + +\pnum +A \tcode{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 \tcode{run} member function removes elements from the queue and +executes them in a loop on the thread of execution that calls \tcode{run}. + +\pnum +A \tcode{run_loop} instance has an associated \defn{count} +that corresponds to the number of work items that are in its queue. +Additionally, a \tcode{run_loop} instance has an associated state +that can be one of \defn{starting}, \defn{running}, or \defn{finishing}. + +\pnum +Concurrent invocations of the member functions of \tcode{run_loop} +other than \tcode{run} and its destructor do not introduce data races. +The member functions +\exposid{pop-front}, \exposid{push-back}, and \tcode{finish} +execute atomically. + +\pnum +\recommended +Implementations should use an intrusive queue of operation states +to hold the work units to make scheduling allocation-free. + +\begin{codeblock} +namespace std::execution { + class run_loop { + // \ref{exec.run.loop.types}, associated types + class @\exposid{run-loop-scheduler}@; // \expos + class @\exposid{run-loop-sender}@; // \expos + struct @\exposid{run-loop-opstate-base}@ { // \expos + virtual void @\exposid{execute}@() = 0; // \expos + run_loop* @\exposid{loop}@; // \expos + run-loop-opstate-base* @\exposid{next}@; // \expos + }; + template + using @\exposid{run-loop-opstate}@ = @\unspec@; // \expos + + // \ref{exec.run.loop.members}, member functions + @\exposid{run-loop-opstate-base}@* @\exposid{pop-front}@(); // \expos + void @\exposid{push-back}@(@\exposid{run-loop-opstate-base}@*); // \expos + + public: + // \ref{exec.run.loop.ctor}, constructor and destructor + run_loop() noexcept; + run_loop(run_loop&&) = delete; + ~run_loop(); + + // \ref{exec.run.loop.members}, member functions + @\exposid{run-loop-scheduler}@ get_scheduler(); + void run(); + void finish(); + }; +} +\end{codeblock} + +\rSec3[exec.run.loop.types]{Associated types} + +\begin{itemdecl} +class @\exposid{run-loop-scheduler}@; +\end{itemdecl} + +\pnum +\exposid{run-loop-scheduler} is an unspecified type +that models \tcode{scheduler}. + +\pnum +Instances of \exposid{run-loop-scheduler} remain valid +until the end of the lifetime of the \tcode{run_loop} instance +from which they were obtained. + +\pnum +Two instances of \exposid{run-loop-scheduler} compare equal +if and only if they were obtained from the same \tcode{run_loop} instance. + +\pnum +Let \exposid{sch} be an expression of type \exposid{run-loop-scheduler}. +The expression \tcode{schedule(\exposid{sch})} +has type \exposid{run-loop-\newline sender} and +is not potentially-throwing if \exposid{sch} is not potentially-throwing. + +\begin{itemdecl} +class @\exposid{run-loop-sender}@; +\end{itemdecl} + +\pnum +\exposid{run-loop-sender} is an exposition-only type +that satisfies \tcode{sender}. +For any type \tcode{Env}, +\tcode{completion_signatures_of_t<\exposid{run-loop-sender}, Env>} is +\begin{codeblock} +completion_signatures +\end{codeblock} + +\pnum +An instance of \exposid{run-loop-sender} remains valid +until the end of the lifetime of its associated \tcode{run_loop} instance. + +\pnum +Let \exposid{sndr} be an expression of type \exposid{run-loop-sender}, +let \exposid{rcvr} be an expression +such that \tcode{receiver_of} is \tcode{true} +where \tcode{CS} is the \tcode{completion_signatures} specialization above. +Let \tcode{C} be either \tcode{set_value_t} or \tcode{set_stopped_t}. +Then: +\begin{itemize} +\item +The expression \tcode{connect(\exposid{sndr}, \exposid{rcvr})} +has type \tcode{\exposid{run-loop-opstate}>} +and is potentially-throwing if and only if +\tcode{(void(\exposid{sndr}), auto(\exposid{rcvr}))} is potentially-throwing. +\item +The expression \tcode{get_completion_scheduler(get_env(\exposid{sndr}))} +is potentially-throwing if and only if \exposid{sndr} is potentially-throwing, +has type \exposid{run-loop-scheduler}, and +compares equal to the \exposid{run-loop-\newline scheduler} instance +from which \exposid{sndr} was obtained. +\end{itemize} + +\begin{itemdecl} +template + struct @\exposid{run-loop-opstate}@; +\end{itemdecl} + +\pnum +\tcode{\exposid{run-loop-opstate}} +inherits privately and unambiguously from \exposid{run-loop-opstate-base}. + +\pnum +Let $o$ be a non-const lvalue of type \tcode{\exposid{run-loop-opstate}}, +and let \tcode{REC($o$)} be a non-const lvalue reference to an instance of type \tcode{Rcvr} +that was initialized with the expression \exposid{rcvr} +passed to the invocation of connect that returned $o$. +Then: +\begin{itemize} +\item +The object to which \tcode{\exposid{REC}($o$)} refers +remains valid for the lifetime of the object to which $o$ refers. +\item +The type \tcode{\exposid{run-loop-opstate}} overrides +\tcode{\exposid{run-loop-opstate-base}::\exposid{execute}()} +such that \tcode{$o$.\exposid{exe\-cute}()} is equivalent to: +\begin{codeblock} +if (get_stop_token(@\exposid{REC}@(@$o$@)).stop_requested()) { + set_stopped(std::move(@\exposid{REC}@(@$o$@))); +} else { + set_value(std::move(@\exposid{REC}@(@$o$@))); +} +\end{codeblock} +\item +The expression \tcode{start($o$)} is equivalent to: +\begin{codeblock} +try { + @$o$@.@\exposid{loop}@->@\exposid{push-back}@(addressof(@$o$@)); +} catch(...) { + set_error(std::move(@\exposid{REC}@(@$o$@)), current_exception()); +} +\end{codeblock} +\end{itemize} + +\rSec3[exec.run.loop.ctor]{Constructor and destructor} + +\begin{itemdecl} +run_loop() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\ensures +\exposid{count} is \tcode{0} and \exposid{state} is \exposid{starting}. +\end{itemdescr} + +\begin{itemdecl} +~run_loop(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +If \exposid{count} is not \tcode{0} or if \exposid{state} is \exposid{running}, +invokes \tcode{terminate}\iref{except.terminate}. +Otherwise, has no effects. +\end{itemdescr} + +\rSec3[exec.run.loop.members]{Member functions} + +\begin{itemdecl} +@\exposid{run-loop-opstate-base}@* @\exposid{pop-front}@(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Blocks\iref{defns.block} until one of the following conditions is \tcode{true}: +\begin{itemize} +\item +\exposid{count} is \tcode{0} and \exposid{state} is \exposid{finishing}, +in which case \exposid{pop-front} returns \tcode{nullptr}; or +\item +\exposid{count} is greater than \tcode{0}, +in which case an item is removed from the front of the queue, +\exposid{count} is decremented by \tcode{1}, and +the removed item is returned. +\end{itemize} +\end{itemdescr} + +\begin{itemdecl} +void @\exposid{push-back}@(@\exposid{run-loop-opstate-base}@* item); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Adds \tcode{item} to the back of the queue and +increments \exposid{count} by \tcode{1}. + +\pnum +\sync +This operation synchronizes with +the \exposid{pop-front} operation that obtains \tcode{item}. +\end{itemdescr} + +\begin{itemdecl} +@\exposid{run-loop-scheduler}@ get_scheduler(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\returns +An instance of \exposid{run-loop-scheduler} +that can be used to schedule work onto this \tcode{run_loop} instance. +\end{itemdescr} + +\begin{itemdecl} +void run(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\expects +\exposid{state} is \exposid{starting}. + +\pnum +\effects +Sets the \exposid{state} to \exposid{running}. Then, equivalent to: +\begin{codeblock} +while (auto* op = @\exposid{pop-front}@()) { + op->@\exposid{execute}@(); +} +\end{codeblock} + +\pnum +\remarks +When \exposid{state} changes, it does so without introducing data races. +\end{itemdescr} + +\begin{itemdecl} +void finish(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Changes \exposid{state} to \exposid{finishing}. + +\pnum +\sync +\tcode{finish} synchronizes with the \exposid{pop-front} operation +that returns \tcode{nullptr}. +\end{itemdescr} + +\rSec1[exec.coro.util]{Coroutine utilities} + +\rSec2[exec.as.awaitable]{\tcode{execution::as_awaitable}} + +\pnum +\tcode{as_awaitable} transforms an object into one +that is awaitable within a particular coroutine. +Subclause \ref{exec.coro.util} makes use of +the following exposition-only entities: +\begin{codeblock} +namespace std::execution { + template + concept @\defexposconcept{awaitable-sender}@ = + @\exposconcept{single-sender}@> && + @\libconcept{sender_to}@ && // \seebelow + requires (Promise& p) { + { p.unhandled_stopped() } -> @\libconcept{convertible_to}@>; + }; + + template + class @\exposidnc{sender-awaitable}@; // \expos +} +\end{codeblock} + +\pnum +The type \tcode{\exposid{sender-awaitable}} is equivalent to: + +\begin{codeblock} +namespace std::execution { + template + class @\exposidnc{sender-awaitable}@ { + struct @\exposidnc{unit}@ {}; // \expos + using @\exposidnc{value-type}@ = // \expos + @\exposidnc{single-sender-value-type}@>; + using @\exposidnc{result-type }@= // \expos + conditional_t, unit, @\exposid{value-type}@>; + struct @\exposidnc{awaitable-receiver}@; // \expos + + variant @\exposidnc{result}@{}; // \expos + connect_result_t @\exposidnc{state}@; // \expos + + public: + @\exposid{sender-awaitable}@(Sndr&& sndr, Promise& p); + static constexpr bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle) noexcept { start(@\exposid{state}@); } + @\exposid{value-type}@ await_resume(); + }; +} +\end{codeblock} + +\pnum +\exposid{awaitable-receiver} is equivalent to: +\begin{codeblock} +struct @\exposid{awaitable-receiver}@ { + using receiver_concept = receiver_t; + variant* @\exposidnc{result-ptr}@; // \expos + coroutine_handle @\exposidnc{continuation}@; // \expos + // \seebelow +}; +\end{codeblock} + +\pnum +Let \tcode{rcvr} be an rvalue expression of type \exposid{awaitable-receiver}, +let \tcode{crcvr} be a const lvalue that refers to \tcode{rcvr}, +let \tcode{vs} be a pack of subexpressions, and +let \tcode{err} be an expression of type \tcode{Err}. Then: +\begin{itemize} +\item +If \tcode{\libconcept{constructible_from}<\exposid{result-type}, decltype((vs))...>} +is satisfied, +the expression \tcode{set_value(\newline rcvr, vs...)} is equivalent to: +\begin{codeblock} +try { + rcvr.@\exposid{result-ptr}@->template emplace<1>(vs...); +} catch(...) { + rcvr.@\exposid{result-ptr}@->template emplace<2>(current_exception()); +} +rcvr.@\exposid{continuation}@.resume(); +\end{codeblock} +Otherwise, \tcode{set_value(rcvr, vs...)} is ill-formed. +\item +The expression \tcode{set_error(rcvr, err)} is equivalent to: +\begin{codeblock} +rcvr.@\exposid{result-ptr}@->template emplace<2>(@\exposid{AS-EXCEPT-PTR}@(err)); // see \ref{exec.general} +rcvr.@\exposid{continuation}@.resume(); +\end{codeblock} +\item +The expression \tcode{set_stopped(rcvr)} is equivalent to: +\begin{codeblock} +static_cast>(rcvr.@\exposid{continuation}@.promise().unhandled_stopped()).resume(); +\end{codeblock} +\item +For any expression \tcode{tag} +whose type satisfies \exposconcept{forwarding-query} and +for any pack of subexpressions \tcode{as}, +\tcode{get_env(crcvr).query(tag, as...)} is expression-equivalent to: +\begin{codeblock} +tag(get_env(as_const(crcvr.@\exposid{continuation}@.promise())), as...) +\end{codeblock} +\end{itemize} + +\begin{itemdecl} +@\exposid{sender-awaitable}@(Sndr&& sndr, Promise& p); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Initializes \exposid{state} with +\begin{codeblock} +connect(std::forward(sndr), + @\exposid{awaitable-receiver}@{addressof(result), coroutine_handle::from_promise(p)}) +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +@\exposid{value-type}@ await_resume(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (@\exposid{result}@.index() == 2) + rethrow_exception(get<2>(@\exposid{result}@)); +if constexpr (!is_void_v<@\exposid{value-type}@>) + return std::forward<@\exposid{value-type}@>(get<1>(@\exposid{result}@)); +\end{codeblock} +\end{itemdescr} + +\pnum +\tcode{as_awaitable} is a customization point object. +For subexpressions \tcode{expr} and \tcode{p} +where \tcode{p} is an lvalue, +\tcode{Expr} names the type \tcode{decltype((expr))} and +\tcode{Promise} names the type \tcode{decay_t}, +\tcode{as_awaitable(expr, p)} is expression-equivalent to, +except that the evaluations of \tcode{expr} and \tcode{p} +are indeterminately sequenced: +\begin{itemize} +\item +\tcode{expr.as_awaitable(p)} if that expression is well-formed. + +\mandates +\tcode{\exposconcept{is-awaitable}} is \tcode{true}, +where \tcode{A} is the type of the expression above. +\item +Otherwise, \tcode{(void(p), expr)} +if \tcode{\exposconcept{is-awaitable}} is \tcode{true}, +where \tcode{U} is an unspecified class type +that is not \tcode{Promise} and +that lacks a member named \tcode{await_transform}. + +\expects +\tcode{\exposconcept{is-awaitable}} is \tcode{true} and +the expression \tcode{co_await expr} +in a coroutine with promise type \tcode{U} is expression-equivalent to +the same expression in a coroutine with promise type \tcode{Promise}. +\item +Otherwise, \tcode{\exposid{sender-awaitable}\{expr, p\}} +if \tcode{\exposconcept{awaitable-sender}} is \tcode{true}. +\item +Otherwise, \tcode{(void(p), expr)}. +\end{itemize} + +\rSec2[exec.with.awaitable.senders]{\tcode{execution::with_awaitable_senders}} + +\pnum +\tcode{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 \tcode{unhandled_stopped} +such that if a sender completes by calling \tcode{set_stopped}, +it is treated as if an uncatchable "stopped" exception were thrown +from the \grammarterm{await-expression}. +\begin{note} +The coroutine is never resumed, and +the \tcode{unhandled_stopped} of the coroutine caller's promise type is called. +\end{note} + +\begin{codeblock} +namespace std::execution { + template<@\exposconcept{class-type}@ Promise> + struct with_awaitable_senders { + template + requires (!@\libconcept{same_as}@) + void set_continuation(coroutine_handle h) noexcept; + + coroutine_handle<> continuation() const noexcept { return @\exposid{continuation}@; } + + coroutine_handle<> unhandled_stopped() noexcept { + return @\exposid{stopped-handler}@(@\exposid{continuation}@.address()); + } + + template + @\seebelow@ await_transform(Value&& value); + + private: + [[noreturn]] static coroutine_handle<> + @\exposid{default-unhandled-stopped}@(void*) noexcept { // \expos + terminate(); + } + coroutine_handle<> @\exposid{continuation}@{}; // \expos + coroutine_handle<> (*@\exposid{stopped-handler}@)(void*) noexcept = // \expos + &@\exposid{default-unhandled-stopped}@; + }; +} +\end{codeblock} + +\begin{itemdecl} +template + requires (!@\libconcept{same_as}@) +void set_continuation(coroutine_handle h) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +@\exposid{continuation}@ = h; +if constexpr ( requires(OtherPromise& other) { other.unhandled_stopped(); } ) { + @\exposid{stopped-handler}@ = [](void* p) noexcept -> coroutine_handle<> { + return coroutine_handle::from_address(p) + .promise().unhandled_stopped(); + }; +} else { + @\exposid{stopped-handler}@ = &@\exposid{default-unhandled-stopped}@; +} +\end{codeblock} +\end{itemdescr} + +\begin{itemdecl} +template +@\exposid{call-result-t}@ await_transform(Value&& value); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +return as_awaitable(std::forward(value), static_cast(*this)); +\end{codeblock} +\end{itemdescr} diff --git a/source/lib-intro.tex b/source/lib-intro.tex index 7ac50b576c..a55c939074 100644 --- a/source/lib-intro.tex +++ b/source/lib-intro.tex @@ -43,6 +43,7 @@ \ref{input.output} & Input/output library \\ \ref{re} & Regular expressions library \\ \ref{thread} & Concurrency support library \\ +\ref{exec} & Execution control library \\ \end{floattable} \pnum @@ -129,6 +130,10 @@ and manage threads, including atomic operations, mutual exclusion, and interthread communication. +\pnum +The execution control library\iref{exec} provides components +supporting execution of function objects. + \rSec1[library.c]{The C standard library} \pnum @@ -525,6 +530,14 @@ } \end{codeblock} +\pnum +An object \tcode{dst} is said to be \defn{decay-copied from} +a subexpression \tcode{src} +if the type of \tcode{dst} is +\begin{codeblock} +decay_t +\end{codeblock} and \tcode{dst} is copy-initialized from \tcode{src}. + \rSec3[type.descriptions]{Type descriptions} \rSec4[type.descriptions.general]{General} @@ -2852,6 +2865,22 @@ \end{codeblock} \end{example} +\pnum +The following exposition-only concept defines +the minimal requirements on an Allocator type. +\begin{codeblock} +template +concept @\defexposconcept{simple-allocator}@ = + requires(Alloc alloc, size_t n) { + { *alloc.allocate(n) } -> @\libconcept{same_as}@; + { alloc.deallocate(alloc.allocate(n), n) }; + } && + @\libconcept{copy_constructible}@ && + @\libconcept{equality_comparable}@; +\end{codeblock} +A type \tcode{Alloc} models \exposconcept{simple-allocator} +if it meets the requirements of \ref{allocator.requirements.general}. + \rSec4[allocator.requirements.completeness]{Allocator completeness requirements} \pnum diff --git a/source/std.tex b/source/std.tex index 76f5ed3fc8..e7a523d790 100644 --- a/source/std.tex +++ b/source/std.tex @@ -135,6 +135,7 @@ \include{iostreams} \include{regex} \include{threads} +\include{exec} %%-------------------------------------------------- %% appendices diff --git a/source/support.tex b/source/support.tex index 3896b28626..bebd8957aa 100644 --- a/source/support.tex +++ b/source/support.tex @@ -770,6 +770,7 @@ #define @\defnlibxname{cpp_lib_saturation_arithmetic}@ 202311L // also in \libheader{numeric} #define @\defnlibxname{cpp_lib_scoped_lock}@ 201703L // also in \libheader{mutex} #define @\defnlibxname{cpp_lib_semaphore}@ 201907L // also in \libheader{semaphore} +#define @\defnlibxname{cpp_lib_senders}@ 202406L // also in \libheader{execution} #define @\defnlibxname{cpp_lib_shared_mutex}@ 201505L // also in \libheader{shared_mutex} #define @\defnlibxname{cpp_lib_shared_ptr_arrays}@ 201707L // also in \libheader{memory} #define @\defnlibxname{cpp_lib_shared_ptr_weak_type}@ 201606L // also in \libheader{memory} diff --git a/source/threads.tex b/source/threads.tex index 6d9a79775a..2bb44f9531 100644 --- a/source/threads.tex +++ b/source/threads.tex @@ -453,46 +453,80 @@ Such a request is called a \defn{stop request}. \pnum -\tcode{stop_source}, \tcode{stop_token}, and \tcode{stop_callback} -implement semantics of shared ownership of a \defn{stop state}. -Any \tcode{stop_source}, \tcode{stop_token}, or \tcode{stop_callback} -that shares ownership of the same stop state is an \defn{associated} -\tcode{stop_source}, \tcode{stop_token}, or \tcode{stop_callback}, respectively. -The last remaining owner of the stop state automatically -releases the resources associated with the stop state. - -\pnum -A \tcode{stop_token} can be passed to an operation which can either +The concepts +\exposconcept{stoppable-source}, +\libconcept{stoppable_token}, and +\exposconcept{stoppable-callback-for} +specify the required syntax and semantics of +shared access to a \defn{stop state}. +Any object modeling \exposconcept{stoppable-source}, +\libconcept{stoppable_token}, or +\exposconcept{stoppable-callback-for} +that refers to the same stop state is +an \defn{associated} +\exposconcept{stoppable-source}, +\libconcept{stoppable_token}, or +\exposconcept{stoppable-callback-for}, +respectively. + +\pnum +An object of a type that models \libconcept{stoppable_token} +can be passed to an operation that can either \begin{itemize} \item actively poll the token to check if there has been a stop request, or - \item register a callback using the \tcode{stop_callback} class template which + \item register a callback that will be called in the event that a stop request is made. \end{itemize} -A stop request made via a \tcode{stop_source} will be visible to all -associated \tcode{stop_token} and \tcode{stop_source} objects. +A stop request made via an object +whose type models \exposconcept{stoppable-source} +will be visible to +all associated \libconcept{stoppable_token} and +\exposconcept{stoppable-source} objects. Once a stop request has been made it cannot be withdrawn (a subsequent stop request has no effect). \pnum -Callbacks registered via a \tcode{stop_callback} object are called when -a stop request is first made by any associated \tcode{stop_source} object. +Callbacks registered via an object +whose type models \exposconcept{stoppable-callback-for} +are called when a stop request is first made +by any associated \exposconcept{stoppable-source} object. \pnum -Calls to the functions \tcode{request_stop}, \tcode{stop_requested}, -and \tcode{stop_possible} -do not introduce data races. -A call to \tcode{request_stop} that returns \tcode{true} -synchronizes with a call to \tcode{stop_requested} -on an associated \tcode{stop_token} or \tcode{stop_source} object -that returns \tcode{true}. -Registration of a callback synchronizes with the invocation of that callback. +The types \tcode{stop_source} and \tcode{stop_token} and +the class template \tcode{stop_callback} implement +the semantics of shared ownership of a stop state. +The last remaining owner of the stop state automatically releases +the resources associated with the stop state. +\pnum +An object of type \tcode{inplace_stop_source} +is the sole owner of its stop state. +An object of type \tcode{inplace_stop_token} or +of a specialization of the class template \tcode{inplace_stop_callback} +does not participate in ownership of its associated stop state. +\begin{note} +They are for use when all uses of the associated token and callback objects +are known to nest within the lifetime of the \tcode{inplace_stop_source} object. +\end{note} \rSec2[thread.stoptoken.syn]{Header \tcode{} synopsis} \indexheader{stop_token}% \begin{codeblock} namespace std { + // \ref{stoptoken.concepts}, stop token concepts + template + concept @\exposconcept{stoppable-callback-for}@ = @\seebelow@; // \expos + + template + concept @\libconcept{stoppable_token}@ = @\seebelow@; + + template + concept @\libconcept{unstoppable_token}@ = @\seebelow@; + + template + concept @\exposconcept{stoppable-source}@ = @\seebelow@; // \expos + // \ref{stoptoken}, class \tcode{stop_token} class stop_token; @@ -508,136 +542,323 @@ // \ref{stopcallback}, class template \tcode{stop_callback} template class stop_callback; -} -\end{codeblock} + // \ref{stoptoken.never}, class \tcode{never_stop_token} + class never_stop_token; -\rSec2[stoptoken]{Class \tcode{stop_token}}% -\indexlibraryglobal{stop_token}% + // \ref{stoptoken.inplace}, class \tcode{inplace_stop_token} + class inplace_stop_token; -\rSec3[stoptoken.general]{General} - -\pnum -\indexlibraryglobal{stop_token}% -The class \tcode{stop_token} provides an interface for querying whether -a stop request has been made (\tcode{stop_requested}) -or can ever be made (\tcode{stop_possible}) -using an associated \tcode{stop_source} object\iref{stopsource}. -A \tcode{stop_token} can also be passed to a -\tcode{stop_callback}\iref{stopcallback} constructor -to register a callback to be called when a stop request has been made -from an associated \tcode{stop_source}. - -\begin{codeblock} -namespace std { - class stop_token { - public: - // \ref{stoptoken.cons}, constructors, copy, and assignment - stop_token() noexcept; - - stop_token(const stop_token&) noexcept; - stop_token(stop_token&&) noexcept; - stop_token& operator=(const stop_token&) noexcept; - stop_token& operator=(stop_token&&) noexcept; - ~stop_token(); - void swap(stop_token&) noexcept; + // \ref{stopsource.inplace}, class \tcode{inplace_stop_source} + class inplace_stop_source; - // \ref{stoptoken.mem}, stop handling - bool stop_requested() const noexcept; - bool stop_possible() const noexcept; + // \ref{stopcallback.inplace}, class template \tcode{inplace_stop_callback} + template + class inplace_stop_callback; - friend bool operator==(const stop_token& lhs, const stop_token& rhs) noexcept; - friend void swap(stop_token& lhs, stop_token& rhs) noexcept; - }; + template + using stop_callback_for_t = T::template callback_type; } \end{codeblock} +\rSec2[stoptoken.concepts]{Stop token concepts} -\rSec3[stoptoken.cons]{Constructors, copy, and assignment} +\pnum +The exposition-only \exposconcept{stoppable-callback-for} concept +checks for a callback compatible with a given \tcode{Token} type. +\begin{codeblock} +template + concept @\defexposconcept{stoppable-callback-for}@ = // \expos + @\libconcept{invocable}@ && + @\libconcept{constructible_from}@ && + requires { typename stop_callback_for_t; } && + @\libconcept{constructible_from}@, const Token&, Initializer>; +\end{codeblock} -\indexlibraryctor{stop_token}% -\begin{itemdecl} -stop_token() noexcept; -\end{itemdecl} +\pnum +Let \tcode{t} and \tcode{u} be distinct, valid objects of type \tcode{Token} +that reference the same logical stop state; +let \tcode{init} be an expression such that +\tcode{\libconcept{same_as}} is \tcode{true}; and +let \tcode{SCB} denote the type \tcode{stop_callback_for_t}. -\begin{itemdescr} \pnum -\ensures -\tcode{stop_possible()} is \tcode{false} and -\tcode{stop_requested()} is \tcode{false}. +The concept +\tcode{\exposconcept{stoppable-callback-for}} +is modeled only if: +\begin{itemize} +\item +The following concepts are modeled: +\begin{itemize} +\item \tcode{\libconcept{constructible_from}} +\item \tcode{\libconcept{constructible_from}} +\item \tcode{\libconcept{constructible_from}} +\end{itemize} + +\item +An object of type \tcode{SCB} has +an associated callback function of type \tcode{CallbackFn}. +Let \tcode{scb} be an object of type \tcode{SCB} and +let \tcode{callback_fn} denote \tcode{scb's} associated callback function. +Direct-non-list-initializing \tcode{scb} from +arguments \tcode{t} and \tcode{init} +shall execute a \defnadj{stoppable callback}{registration} as follows: +\begin{itemize} +\item +If \tcode{t.stop_possible()} is \tcode{true}: +\begin{itemize} +\item +\tcode{callback_fn} shall be direct-initialized with \tcode{init}. +\item +Construction of \tcode{scb} shall only throw exceptions +thrown by the initialization of \tcode{callback_fn} from \tcode{init}. +\item +The callback invocation \tcode{std::forward(callback_fn)()} +shall be registered with \tcode{t}'s associated stop state as follows: +\begin{itemize} +\item +If \tcode{t.stop_requested()} evaluates to \tcode{false} +at the time of registration, +the callback invocation is added to the stop state's list of callbacks +such that \tcode{std::forward(\newline callback_fn)()} is evaluated +if a stop request is made on the stop state. +\item +Otherwise, \tcode{std::forward(callback_fn)()} +shall be immediately evaluated +on the thread executing \tcode{scb}'s constructor, and +the callback invocation shall not be added to the list of callback invocations. +\end{itemize} +If the callback invocation was added to stop state's list of callbacks, +\tcode{scb} shall be associated with the stop state. +\end{itemize} +\item \begin{note} -Because the created \tcode{stop_token} object can never receive a stop request, -no resources are allocated for a stop state. +If \tcode{t.stop_possible()} is \tcode{false}, +there is no requirement +that the initialization of \tcode{scb} +causes the initialization of \tcode{callback_fn}. \end{note} -\end{itemdescr} +\end{itemize} -\indexlibraryctor{stop_token}% -\begin{itemdecl} -stop_token(const stop_token& rhs) noexcept; -\end{itemdecl} +\item +Destruction of \tcode{scb} shall execute +a \defnadj{stoppable}{callback deregistration} as follows (in order): +\begin{itemize} +\item +If the constructor of \tcode{scb} did not register +a callback invocation with \tcode{t}'s stop state, +then the stoppable callback deregistration shall have no effect +other than destroying \tcode{callback_fn} if it was constructed. +\item +Otherwise, the invocation of \tcode{callback_fn} shall be removed +from the associated stop state. +\item +If \tcode{callback_fn} is concurrently executing on another thread, +then the stoppable callback deregistration shall block\iref{defns.block} +until the invocation of \tcode{callback_fn} returns +such that the return from the invocation of \tcode{callback_fn} +strongly happens before\iref{intro.races} +the destruction of \tcode{callback_fn}. +\item +If \tcode{callback_fn} is executing on the current thread, +then the destructor shall not block +waiting for the return from the invocation of \tcode{callback_fn}. +\item +A stoppable callback deregistration shall not block +on the completion of the invocation of some other callback +registered with the same logical stop state. +\item +The stoppable callback deregistration shall destroy \tcode{callback_fn}. +\end{itemize} +\end{itemize} -\begin{itemdescr} \pnum -\ensures -\tcode{*this == rhs} is \tcode{true}. -\begin{note} -\tcode{*this} and \tcode{rhs} share the ownership of the same stop state, -if any. -\end{note} -\end{itemdescr} +The \libconcept{stoppable_token} concept checks +for the basic interface of a stop token +that is copyable and allows polling to see if stop has been requested and +also whether a stop request is possible. +The \libconcept{unstoppable_token} concept checks +for a \libconcept{stoppable_token} type that does not allow stopping. +\begin{codeblock} +template class> + struct @\exposid{check-type-alias-exists}@; // \expos + +template + concept @\deflibconcept{stoppable_token}@ = + requires (const Token tok) { + typename @\exposid{check-type-alias-exists}@; + { tok.stop_requested() } noexcept -> @\libconcept{same_as}@; + { tok.stop_possible() } noexcept -> @\libconcept{same_as}@; + { Token(tok) } noexcept; // see implicit expression variations\iref{concepts.equality} + } && + @\libconcept{copyable}@ && + @\libconcept{equality_comparable}@ && + @\libconcept{swappable}@; + +template + concept @\deflibconcept{unstoppable_token}@ = + @\libconcept{stoppable_token}@ && + requires (const Token tok) { + requires bool_constant<(!tok.stop_possible())>::value; + }; +\end{codeblock} -\indexlibraryctor{stop_token}% -\begin{itemdecl} -stop_token(stop_token&& rhs) noexcept; -\end{itemdecl} -\begin{itemdescr} \pnum -\ensures -\tcode{*this} contains the value of \tcode{rhs} -prior to the start of construction -and \tcode{rhs.stop_possible()} is \tcode{false}. -\end{itemdescr} +An object whose type models \libconcept{stoppable_token} +has at most one associated logical stop state. +A \libconcept{stoppable_token} object with no associated stop state +is said to be \defn{disengaged}. -\indexlibrarydtor{stop_token}% -\begin{itemdecl} -~stop_token(); -\end{itemdecl} +\pnum +Let \tcode{SP} be an evaluation of \tcode{t.stop_possible()} +that is \tcode{false}, and +let SR be an evaluation of \tcode{t.stop_requested()} that is \tcode{true}. -\begin{itemdescr} \pnum -\effects -Releases ownership of the stop state, if any. -\end{itemdescr} +The type \tcode{Token} models \libconcept{stoppable_token} only if: +\begin{itemize} +\item +Any evaluation of \tcode{u.stop_possible()} or \tcode{u.stop_requested()} +that happens after\iref{intro.races} \tcode{SP} is \tcode{false}. +\item +Any evaluation of \tcode{u.stop_possible()} or \tcode{u.stop_requested()} +that happens after \tcode{SR} is \tcode{true}. +\item +For any types \tcode{CallbackFn} and \tcode{Initializer} such that +\tcode{\exposconcept{stoppable-callback-for}} +is satisfied, +\tcode{\exposconcept{stoppable-callback-for}} +is modeled. +\item +If \tcode{t} is disengaged, +evaluations of \tcode{t.stop_possible()} and \tcode{t.stop_requested()} +are \tcode{false}. +\item +If \tcode{t} and \tcode{u} reference the same stop state, or +if both \tcode{t} and \tcode{u} are disengaged, +\tcode{t == u} is \tcode{true}; otherwise, it is \tcode{false}. +\end{itemize} -\indexlibrarymember{operator=}{stop_token}% -\begin{itemdecl} -stop_token& operator=(const stop_token& rhs) noexcept; -\end{itemdecl} +\pnum +An object +whose type models the exposition-only \exposconcept{stoppable-source} concept +can be queried +whether stop has been requested (\tcode{stop_requested}) and +whether stop is possible (\tcode{stop_possible}). +It is a factory for associated stop tokens (\tcode{get_token}), and +a stop request can be made on it (\tcode{request_stop}). +It maintains a list of registered stop callback invocations +that it executes when a stop request is first made. +\begin{codeblock} +template + concept @\defexposconcept{stoppable-source}@ = // \expos + requires (Source& src, const Source csrc) { // see implicit expression variations\iref{concepts.equality} + { csrc.get_token() } -> stoppable_token; + { csrc.stop_possible() } noexcept -> @\libconcept{same_as}@; + { csrc.stop_requested() } noexcept -> @\libconcept{same_as}@; + { src.request_stop() } -> @\libconcept{same_as}@; + }; +\end{codeblock} -\begin{itemdescr} \pnum -\effects -Equivalent to: \tcode{stop_token(rhs).swap(*this)}. +An object whose type models \exposconcept{stoppable-source} has +at most one associated logical stop state. +If it has no associated stop state, it is said to be disengaged. +Let \tcode{s} be an object whose type models \exposconcept{stoppable-source} and +that is disengaged. +\tcode{s.stop_possible()} and \tcode{s.stop_requested()} shall be \tcode{false}. \pnum -\returns -\tcode{*this}. -\end{itemdescr} +Let \tcode{t} be an object whose type models \exposconcept{stoppable-source}. +If \tcode{t} is disengaged, +\tcode{t.get_token()} shall return a disengaged stop token; +otherwise, it shall return +a stop token that is associated with the stop state of \tcode{t}. -\indexlibrarymember{operator=}{stop_token}% -\begin{itemdecl} -stop_token& operator=(stop_token&& rhs) noexcept; -\end{itemdecl} +\pnum +Calls to the member functions +\tcode{request_stop}, \tcode{stop_requested}, and \tcode{stop_possible} and +similarly named member functions +on associated \libconcept{stoppable_token} objects +do not introduce data races. +A call to \tcode{request_stop} that returns \tcode{true} synchronizes with +a call to \tcode{stop_requested} on +an associated +\libconcept{stoppable_token} or \exposconcept{stoppable-source} object +that returns \tcode{true}. +Registration of a callback synchronizes with the invocation of that callback. -\begin{itemdescr} \pnum -\effects -Equivalent to: \tcode{stop_token(std::move(rhs)).swap(*this)}. +If the \exposconcept{stoppable-source} is disengaged, +\tcode{request_stop} shall have no effect and return \tcode{false}. +Otherwise, it shall execute a \defnadj{stop request}{operation} +on the associated stop state. +A stop request operation determines +whether the stop state has received a stop request, and +if not, makes a stop request. +The determination and making of the stop request shall happen atomically, +as-if by a read-modify-write operation\iref{intro.races}. +If the request was made, +the stop state's registered callback invocations shall be +synchronously executed. +If an invocation of a callback exits via an exception +then terminate shall be invoked\iref{except.terminate}. +\begin{note} +No constraint is placed on the order +in which the callback invocations are executed. +\end{note} +\tcode{request_stop} shall return \tcode{true} if a stop request was made, and +\tcode{false} otherwise. +After a call to \tcode{request_stop} either +a call to \tcode{stop_possible} shall return \tcode{false} or +a call to \tcode{stop_requested} shall return \tcode{true}. +\begin{note} +A stop request includes notifying +all condition variables of type \tcode{condition_variable_any} +temporarily registered during +an interruptible wait\iref{thread.condvarany.intwait}. +\end{note} + +\rSec2[stoptoken]{Class \tcode{stop_token}}% +\indexlibraryglobal{stop_token}% + +\rSec3[stoptoken.general]{General} \pnum -\returns -\tcode{*this}. -\end{itemdescr} +\indexlibraryglobal{stop_token}% +The class \tcode{stop_token} models the concept \libconcept{stoppable_token}. +It shares ownership of its stop state, if any, +with its associated \tcode{stop_source} object\iref{stopsource} and +any \tcode{stop_token} objects to which it compares equal. + +\begin{codeblock} +namespace std { + class stop_token { + public: + template + using callback_type = stop_callback; + + stop_token() noexcept = default; + + // \ref{stoptoken.mem}, member functions + void swap(stop_token&) noexcept; + + bool stop_requested() const noexcept; + bool stop_possible() const noexcept; + + bool operator==(const stop_token& rhs) noexcept = default; + + private: + shared_ptr<@\unspec@> @\exposid{stop-state}@{}; // \expos + }; +} +\end{codeblock} + +\pnum +\exposid{stop-state} refers to the \tcode{stop_token}'s associated stop state. +A \tcode{stop_token} object is disengaged when \exposid{stop-state} is empty. + +\rSec3[stoptoken.mem]{Member functions} \indexlibrarymember{swap}{stop_token}% \begin{itemdecl} @@ -647,11 +868,12 @@ \begin{itemdescr} \pnum \effects -Exchanges the values of \tcode{*this} and \tcode{rhs}. +Equivalent to: +\begin{codeblock} +@\exposid{stop-state}@.swap(rhs.@\exposid{stop-state}@); +\end{codeblock} \end{itemdescr} -\rSec3[stoptoken.mem]{Members} - \indexlibrarymember{stop_requested}{stop_token}% \begin{itemdecl} bool stop_requested() const noexcept; @@ -660,7 +882,7 @@ \begin{itemdescr} \pnum \returns -\tcode{true} if \tcode{*this} has ownership of a stop state +\tcode{true} if \exposid{stop-state} refers to a stop state that has received a stop request; otherwise, \tcode{false}. \end{itemdescr} @@ -675,88 +897,50 @@ \returns \tcode{false} if \begin{itemize} -\item \tcode{*this} does not have ownership of a stop state, or +\item \tcode{*this} is disengaged, or \item a stop request was not made and there are no associated \tcode{stop_source} objects; \end{itemize} otherwise, \tcode{true}. \end{itemdescr} -\rSec3[stoptoken.nonmembers]{Non-member functions} - -\indexlibrarymember{operator==}{stop_token}% -\begin{itemdecl} -bool operator==(const stop_token& lhs, const stop_token& rhs) noexcept; -\end{itemdecl} - -\begin{itemdescr} -\pnum -\returns -\tcode{true} if \tcode{lhs} and \tcode{rhs} have ownership of the same stop state -or if both \tcode{lhs} and \tcode{rhs} do not have ownership of a stop state; -otherwise \tcode{false}. -\end{itemdescr} - -\indexlibrarymember{swap}{stop_token}% -\begin{itemdecl} -friend void swap(stop_token& x, stop_token& y) noexcept; -\end{itemdecl} - -\begin{itemdescr} -\pnum -\effects -Equivalent to: \tcode{x.swap(y)}. -\end{itemdescr} - \rSec2[stopsource]{Class \tcode{stop_source}}% \indexlibraryglobal{stop_source}% \rSec3[stopsource.general]{General} -\pnum -\indexlibraryglobal{stop_source}% -The class \tcode{stop_source} implements the semantics of making a stop request. -A stop request made on a \tcode{stop_source} object is visible to all -associated \tcode{stop_source} and \tcode{stop_token}\iref{stoptoken} objects. -Once a stop request has been made it cannot be withdrawn -(a subsequent stop request has no effect). - -\indexlibraryglobal{nostopstate_t}% -\indexlibraryglobal{nostopstate}% - \begin{codeblock} namespace std { - // no-shared-stop-state indicator - struct nostopstate_t { - explicit nostopstate_t() = default; - }; - inline constexpr nostopstate_t nostopstate{}; - class stop_source { public: // \ref{stopsource.cons}, constructors, copy, and assignment stop_source(); - explicit stop_source(nostopstate_t) noexcept; + explicit stop_source(nostopstate_t) noexcept {} - stop_source(const stop_source&) noexcept; - stop_source(stop_source&&) noexcept; - stop_source& operator=(const stop_source&) noexcept; - stop_source& operator=(stop_source&&) noexcept; - ~stop_source(); + // \ref{stopsource.mem}, member functions void swap(stop_source&) noexcept; - // \ref{stopsource.mem}, stop handling - stop_token get_token() const noexcept; - bool stop_possible() const noexcept; - bool stop_requested() const noexcept; bool request_stop() noexcept; - friend bool operator==(const stop_source& lhs, const stop_source& rhs) noexcept; - friend void swap(stop_source& lhs, stop_source& rhs) noexcept; + bool operator==(const stop_source& rhs) noexcept = default; + + private: + shared_ptr<@\unspec@> @\exposid{stop-state}@{}; // \expos }; } \end{codeblock} +\pnum +\exposid{stop-state} refers to the \tcode{stop_source}'s associated stop state. +A \tcode{stop_source} object is disengaged when \exposid{stop-state} is empty. + +\pnum +\tcode{stop_source} models +\exposconcept{stoppable-source}, +\libconcept{copyable}, +\libconcept{equality_comparable}, and +\libconcept{swappable}. + \rSec3[stopsource.cons]{Constructors, copy, and assignment} \indexlibraryctor{stop_source}% @@ -767,7 +951,7 @@ \begin{itemdescr} \pnum \effects -Initialises \tcode{*this} to have ownership of a new stop state. +Initializes \exposid{stop-state} with a pointer to a new stop state. \pnum \ensures @@ -779,323 +963,439 @@ \tcode{bad_alloc} if memory cannot be allocated for the stop state. \end{itemdescr} -\indexlibraryctor{stop_source}% +\rSec3[stopsource.mem]{Member functions} + +\indexlibrarymember{swap}{stop_source}% \begin{itemdecl} -explicit stop_source(nostopstate_t) noexcept; +void swap(stop_source& rhs) noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\ensures -\tcode{stop_possible()} is \tcode{false} and -\tcode{stop_requested()} is \tcode{false}. -\begin{note} -No resources are allocated for the state. -\end{note} +\effects +Equivalent to: +\begin{codeblock} +@\exposid{stop-state}@.swap(rhs.@\exposid{stop-state}@); +\end{codeblock} \end{itemdescr} -\indexlibraryctor{stop_source}% +\indexlibrarymember{get_token}{stop_source}% \begin{itemdecl} -stop_source(const stop_source& rhs) noexcept; +stop_token get_token() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\ensures -\tcode{*this == rhs} is \tcode{true}. -\begin{note} -\tcode{*this} and \tcode{rhs} share the ownership of the same stop state, -if any. -\end{note} +\returns +\tcode{stop_token()} if \tcode{stop_possible()} is \tcode{false}; +otherwise a new associated \tcode{stop_token} object; +i.e., its \exposid{stop-state} member is equal to +the \exposid{stop-state} member of \tcode{*this}. \end{itemdescr} -\indexlibraryctor{stop_source}% +\indexlibrarymember{stop_possible}{stop_source}% \begin{itemdecl} -stop_source(stop_source&& rhs) noexcept; +bool stop_possible() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\ensures -\tcode{*this} contains the value of \tcode{rhs} -prior to the start of construction -and \tcode{rhs.stop_possible()} is \tcode{false}. +\returns +\tcode{\exposidnc{stop-state} != nullptr}. \end{itemdescr} -\indexlibrarydtor{stop_source}% +\indexlibrarymember{stop_requested}{stop_source}% \begin{itemdecl} -~stop_source(); +bool stop_requested() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\effects -Releases ownership of the stop state, if any. +\returns +\tcode{true} if \exposid{stop-state} refers to a stop state +that has received a stop request; +otherwise, \tcode{false}. \end{itemdescr} -\indexlibrarymember{operator=}{stop_source}% +\indexlibrarymember{request_stop}{stop_source}% \begin{itemdecl} -stop_source& operator=(const stop_source& rhs) noexcept; +bool request_stop() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \effects -Equivalent to: \tcode{stop_source(rhs).swap(*this)}. +Executes a stop request operation\iref{stoptoken.concepts} on +the associated stop state, if any. +\end{itemdescr} + +\rSec2[stopcallback]{Class template \tcode{stop_callback}}% +\indexlibraryglobal{stop_callback}% + +\rSec3[stopcallback.general]{General} \pnum -\returns -\tcode{*this}. -\end{itemdescr} +\indexlibraryglobal{stop_callback}% +\begin{codeblock} +namespace std { + template + class stop_callback { + public: + using callback_type = CallbackFn; + + // \ref{stopcallback.cons}, constructors and destructor + template + explicit stop_callback(const stop_token& st, Initializer&& init) + noexcept(is_nothrow_constructible_v); + template + explicit stop_callback(stop_token&& st, Initializer&& init) + noexcept(is_nothrow_constructible_v); + ~stop_callback(); + + stop_callback(const stop_callback&) = delete; + stop_callback(stop_callback&&) = delete; + stop_callback& operator=(const stop_callback&) = delete; + stop_callback& operator=(stop_callback&&) = delete; + + private: + CallbackFn @\exposid{callback-fn}@; // \expos + }; + + template + stop_callback(stop_token, CallbackFn) -> stop_callback; +} +\end{codeblock} -\indexlibrarymember{operator=}{stop_source}% +\pnum +\mandates +\tcode{stop_callback} is instantiated with an argument for the +template parameter \tcode{CallbackFn} +that satisfies both \libconcept{invocable} +and \libconcept{destructible}. + +\pnum +\remarks +For a type \tcode{Initializer}, +if +\tcode{\exposconcept{stoppable-callback-for}} +is satisfied, then +\tcode{\exposconcept{stoppable-callback-for}} is modeled. +The exposition-only \exposid{callback-fn} member is +the associated callback function\iref{stoptoken.concepts} of +\tcode{stop_callback<\newline CallbackFn>} objects. + +\rSec3[stopcallback.cons]{Constructors and destructor} + +\indexlibraryctor{stop_callback}% \begin{itemdecl} -stop_source& operator=(stop_source&& rhs) noexcept; +template + explicit stop_callback(const stop_token& st, Initializer&& init) + noexcept(is_nothrow_constructible_v); + +template + explicit stop_callback(stop_token&& st, Initializer&& init) + noexcept(is_nothrow_constructible_v); \end{itemdecl} \begin{itemdescr} \pnum -\effects -Equivalent to: \tcode{stop_source(std::move(rhs)).swap(*this)}. +\constraints +\tcode{CallbackFn} and \tcode{Initializer} satisfy +\tcode{\libconcept{constructible_from}}. \pnum -\returns -\tcode{*this}. +\effects +Initializes \exposid{callback-fn} with \tcode{std::forward(init)} +and executes a stoppable callback registration\iref{stoptoken.concepts}. +If a callback is registered with \tcode{st}'s shared stop state, +then \tcode{*this} acquires shared ownership of that stop state. \end{itemdescr} -\indexlibrarymember{swap}{stop_source}% +\indexlibrarydtor{stop_callback}% \begin{itemdecl} -void swap(stop_source& rhs) noexcept; +~stop_callback(); \end{itemdecl} \begin{itemdescr} \pnum \effects -Exchanges the values of \tcode{*this} and \tcode{rhs}. +Executes a stoppable callback deregistration\iref{stoptoken.concepts} and +releases ownership of the stop state, if any. \end{itemdescr} -\rSec3[stopsource.mem]{Members} +\rSec2[stoptoken.never]{Class \tcode{never_stop_token}} + +\pnum +The class \tcode{never_stop_token} models +the \libconcept{unstoppable_token} concept. +It provides a stop token interface, +but also provides static information +that a stop is never possible nor requested. +\begin{codeblock} +namespace std { + class never_stop_token { + struct @\exposid{callback-type}@ { // \expos + explicit @\exposid{callback-type}@(never_stop_token, auto&&) noexcept {} + }; + public: + template + using callback_type = @\exposid{callback-type}@; + + static constexpr bool stop_requested() noexcept { return false; } + static constexpr bool stop_possible() noexcept { return false; } + + bool operator==(const never_stop_token&) const = default; + }; +} +\end{codeblock} + +\rSec2[stoptoken.inplace]{Class \tcode{inplace_stop_token}} + +\rSec3[stoptoken.inplace.general]{General} + +\pnum +The class \tcode{inplace_stop_token} models +the concept \libconcept{stoppable_token}. +It references the stop state of +its associated \tcode{inplace_stop_source} object\iref{stopsource.inplace}, +if any. +\begin{codeblock} +namespace std { + class inplace_stop_token { + public: + template + using callback_type = inplace_stop_callback; + + inplace_stop_token() = default; + bool operator==(const inplace_stop_token&) const = default; + + // \ref{stoptoken.inplace.mem}, member functions + bool stop_requested() const noexcept; + bool stop_possible() const noexcept; + void swap(inplace_stop_token&) noexcept; + + private: + const inplace_stop_source* @\exposid{stop-source}@ = nullptr; // \expos + }; +} +\end{codeblock} + +\rSec3[stoptoken.inplace.mem]{Member functions} -\indexlibrarymember{get_token}{stop_source sc}% \begin{itemdecl} -stop_token get_token() const noexcept; +void swap(inplace_stop_token& rhs) noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\returns -\tcode{stop_token()} if \tcode{stop_possible()} is \tcode{false}; -otherwise a new associated \tcode{stop_token} object. +\effects +Exchanges the values of \exposid{stop-source} and rhs.\exposid{stop-source}. \end{itemdescr} -\indexlibrarymember{stop_possible}{stop_source}% \begin{itemdecl} -bool stop_possible() const noexcept; +bool stop_requested() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum -\returns -\tcode{true} if \tcode{*this} has ownership of a stop state; -otherwise, \tcode{false}. +\effects +Equivalent to: +\begin{codeblock} +return @\exposid{stop-source}@ != nullptr && @\exposid{stop-source}@->stop_requested(); +\end{codeblock} + +\pnum +\begin{note} +As specified in \ref{basic.life}, +the behavior of \tcode{stop_requested} is undefined +unless the call strongly happens before the start of +the destructor of the associated \tcode{inplace_stop_source} object, if any. +\end{note} \end{itemdescr} -\indexlibrarymember{stop_requested}{stop_source}% \begin{itemdecl} -bool stop_requested() const noexcept; +stop_possible() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum \returns -\tcode{true} if \tcode{*this} has ownership of a stop state -that has received a stop request; -otherwise, \tcode{false}. +\tcode{\exposidnc{stop-source} != nullptr}. + +\pnum +\begin{note} +As specified in \ref{basic.stc.general}, +the behavior of \tcode{stop_possible} is implementation-defined +unless the call strongly happens before +the end of the storage duration of +the associated \tcode{inplace_stop_source} object, if any. +\end{note} \end{itemdescr} -\indexlibrarymember{request_stop}{stop_source}% +\rSec2[stopsource.inplace]{Class \tcode{inplace_stop_source}} + +\rSec3[stopsource.inplace.general]{General} + +\pnum +The class \tcode{inplace_stop_source} models \exposconcept{stoppable-source}. + +\begin{codeblock} +namespace std { + class inplace_stop_source { + public: + // \ref{stopsource.inplace.cons}, constructors + constexpr inplace_stop_source() noexcept; + + inplace_stop_source(inplace_stop_source&&) = delete; + inplace_stop_source(const inplace_stop_source&) = delete; + inplace_stop_source& operator=(inplace_stop_source&&) = delete; + inplace_stop_source& operator=(const inplace_stop_source&) = delete; + ~inplace_stop_source(); + + // \ref{stopsource.inplace.mem}, stop handling + constexpr inplace_stop_token get_token() const noexcept; + static constexpr bool stop_possible() noexcept { return true; } + bool stop_requested() const noexcept; + bool request_stop() noexcept; + }; +} +\end{codeblock} + +\rSec3[stopsource.inplace.cons]{Constructors} + \begin{itemdecl} -bool request_stop() noexcept; +constexpr inplace_stop_source() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \effects -If \tcode{*this} does not have ownership of a stop state, returns \tcode{false}. -Otherwise, atomically determines whether the owned stop state -has received a stop request, -and if not, makes a stop request. -The determination and making of the stop request are an -atomic read-modify-write operation\iref{intro.races}. -If the request was made, -the callbacks registered by associated \tcode{stop_callback} objects -are synchronously called. -If an invocation of a callback exits via an exception -then \tcode{terminate} is invoked\iref{except.terminate}. -\begin{note} -A stop request includes notifying all condition variables -of type \tcode{condition_variable_any} -temporarily registered during -an interruptible wait\iref{thread.condvarany.intwait}. -\end{note} +Initializes a new stop state inside \tcode{*this}. \pnum \ensures -\tcode{stop_possible()} is \tcode{false} -or \tcode{stop_requested()} is \tcode{true}. +\tcode{stop_requested()} is \tcode{false}. +\end{itemdescr} + +\rSec3[stopsource.inplace.mem]{Member functions} + +\begin{itemdecl} +constexpr inplace_stop_token get_token() const noexcept; +\end{itemdecl} +\begin{itemdescr} \pnum \returns -\tcode{true} if this call made a stop request; -otherwise \tcode{false}. +A new associated \tcode{inplace_stop_token} object +whose \exposid{stop-source} member is equal to \tcode{this}. \end{itemdescr} -\rSec3[stopsource.nonmembers]{Non-member functions} - -\indexlibrarymember{operator==}{stop_source}% \begin{itemdecl} -friend bool operator==(const stop_source& lhs, const stop_source& rhs) noexcept; +bool stop_requested() const noexcept; \end{itemdecl} \begin{itemdescr} \pnum \returns -\tcode{true} if \tcode{lhs} and \tcode{rhs} have ownership -of the same stop state -or if both \tcode{lhs} and \tcode{rhs} do not have ownership of a stop state; -otherwise \tcode{false}. +\tcode{true} if the stop state inside \tcode{*this} +has received a stop request; otherwise, \tcode{false}. \end{itemdescr} -\indexlibrarymember{swap}{stop_source}% \begin{itemdecl} -friend void swap(stop_source& x, stop_source& y) noexcept; +bool request_stop() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \effects -Equivalent to: \tcode{x.swap(y)}. +Executes a stop request operation\iref{stoptoken.concepts}. + +\pnum +\ensures +\tcode{stop_requested()} is \tcode{true}. \end{itemdescr} -\rSec2[stopcallback]{Class template \tcode{stop_callback}}% -\indexlibraryglobal{stop_callback}% +\rSec2[stopcallback.inplace]{Class template \tcode{inplace_stop_callback}} -\rSec3[stopcallback.general]{General} +\rSec3[stopcallback.inplace.general]{General} -\pnum -\indexlibraryglobal{stop_callback}% \begin{codeblock} namespace std { - template - class stop_callback { + template + class inplace_stop_callback { public: - using callback_type = Callback; + using callback_type = CallbackFn; - // \ref{stopcallback.cons}, constructors and destructor - template - explicit stop_callback(const stop_token& st, C&& cb) - noexcept(is_nothrow_constructible_v); - template - explicit stop_callback(stop_token&& st, C&& cb) - noexcept(is_nothrow_constructible_v); - ~stop_callback(); + // \ref{stopcallback.inplace.cons}, constructors and destructor + template + explicit inplace_stop_callback(inplace_stop_token st, Initializer&& init) + noexcept(is_nothrow_constructible_v); + ~inplace_stop_callback(); - stop_callback(const stop_callback&) = delete; - stop_callback(stop_callback&&) = delete; - stop_callback& operator=(const stop_callback&) = delete; - stop_callback& operator=(stop_callback&&) = delete; + inplace_stop_callback(inplace_stop_callback&&) = delete; + inplace_stop_callback(const inplace_stop_callback&) = delete; + inplace_stop_callback& operator=(inplace_stop_callback&&) = delete; + inplace_stop_callback& operator=(const inplace_stop_callback&) = delete; private: - Callback callback; // \expos + CallbackFn @\exposid{callback-fn}@; // \expos }; - template - stop_callback(stop_token, Callback) -> stop_callback; + template + inplace_stop_callback(inplace_stop_token, CallbackFn) + -> inplace_stop_callback; } \end{codeblock} \pnum \mandates -\tcode{stop_callback} is instantiated with an argument for the -template parameter \tcode{Callback} -that satisfies both \libconcept{invocable} -and \libconcept{destructible}. +\tcode{CallbackFn} satisfies both +\libconcept{invocable} and \libconcept{destructible}. \pnum -\expects -\tcode{stop_callback} is instantiated with an argument for the -template parameter \tcode{Callback} -that models both \libconcept{invocable} -and \libconcept{destructible}. - +\remarks +For a type \tcode{Initializer}, if +\begin{codeblock} +@\exposconcept{stoppable-callback-for}@ +\end{codeblock} +is satisfied, then +\begin{codeblock} +@\exposconcept{stoppable-callback-for}@ +\end{codeblock} +is modeled. +For an \tcode{inplace_stop_callback} object, +the exposition-only \exposid{callback-fn} member is +its associated callback function\iref{stoptoken.concepts}. -\rSec3[stopcallback.cons]{Constructors and destructor} +\rSec3[stopcallback.inplace.cons]{Constructors and destructor} -\indexlibraryctor{stop_callback}% \begin{itemdecl} -template -explicit stop_callback(const stop_token& st, C&& cb) - noexcept(is_nothrow_constructible_v); -template -explicit stop_callback(stop_token&& st, C&& cb) - noexcept(is_nothrow_constructible_v); +template + explicit inplace_stop_callback(inplace_stop_token st, Initializer&& init) + noexcept(is_nothrow_constructible_v); \end{itemdecl} + \begin{itemdescr} \pnum \constraints -\tcode{Callback} and \tcode{C} satisfy \tcode{\libconcept{constructible_from}}. - -\pnum -\expects -\tcode{Callback} and \tcode{C} model \tcode{\libconcept{constructible_from}}. +\tcode{\libconcept{constructible_from}} is satisfied. \pnum \effects -Initializes \tcode{callback} with \tcode{std::forward(cb)}. -If \tcode{st.stop_requested()} is \tcode{true}, then -\tcode{std::forward(callback)()} -is evaluated in the current thread before the constructor returns. -Otherwise, if \tcode{st} has ownership of a stop state, -acquires shared ownership of that stop state and registers -the callback with that stop state -such that \tcode{std::forward(callback)()} -is evaluated by the first call to \tcode{request_stop()} -on an associated \tcode{stop_source}. - -\pnum -\throws -Any exception thrown by the initialization of \tcode{callback}. - -\pnum -\remarks -If evaluating -\tcode{std::forward(callback)()} -exits via an exception, -then \tcode{terminate} is invoked\iref{except.terminate}. +Initializes \exposid{callback-fn} with \tcode{std::forward(init)} +and executes a stoppable callback registration\iref{stoptoken.concepts}. \end{itemdescr} -\indexlibrarydtor{stop_callback}% \begin{itemdecl} -~stop_callback(); +~inplace_stop_callback(); \end{itemdecl} \begin{itemdescr} \pnum \effects -Unregisters the callback from the owned stop state, if any. -The destructor does not block waiting for the execution of another callback -registered by an associated \tcode{stop_callback}. -If \tcode{callback} is concurrently executing on another thread, -then the return from the invocation of \tcode{callback} -strongly happens before\iref{intro.races} -\tcode{callback} is destroyed. -If \tcode{callback} is executing on the current thread, -then the destructor does not block\iref{defns.block} waiting for -the return from the invocation of \tcode{callback}. -Releases ownership of the stop state, if any. +Executes a stoppable callback deregistration\iref{stoptoken.concepts}. \end{itemdescr} - \rSec1[thread.threads]{Threads} \rSec2[thread.threads.general]{General} diff --git a/source/utilities.tex b/source/utilities.tex index baa0320ca7..587f0845f8 100644 --- a/source/utilities.tex +++ b/source/utilities.tex @@ -10829,6 +10829,25 @@ struct greater_equal; // freestanding struct less_equal; // freestanding } + + template + concept @\defexposconceptnc{callable}@ = // \expos + requires (Fn&& fn, Args&&... args) { + std::forward(fn)(std::forward(args)...); + }; + + template + concept @\defexposconceptnc{nothrow-callable}@ = // \expos + @\exposconcept{callable}@ && + requires (Fn&& fn, Args&&... args) { + { std::forward(fn)(std::forward(args)...) } noexcept; + }; + + template + using @\exposidnc{call-result-t}@ = decltype(declval()(declval()...)); // \expos + + template + using @\exposidnc{decayed-typeof}@ = decltype(auto(T)); // \expos } \end{codeblock} @@ -15205,6 +15224,7 @@ object of an execution policy type indicates the kinds of parallelism allowed in the execution of an algorithm and expresses the consequent requirements on the element access functions. +Execution policy types are declared in header \libheaderref{execution}. \begin{example} \begin{codeblock} using namespace std; @@ -15230,37 +15250,6 @@ parameters for efficient execution. \end{note} -\rSec2[execution.syn]{Header \tcode{} synopsis} - -\indexheader{execution}% -\begin{codeblock} -namespace std { - // \ref{execpol.type}, execution policy type trait - template struct is_execution_policy; - template constexpr bool @\libglobal{is_execution_policy_v}@ = is_execution_policy::value; -} - -namespace std::execution { - // \ref{execpol.seq}, sequenced execution policy - class sequenced_policy; - - // \ref{execpol.par}, parallel execution policy - class parallel_policy; - - // \ref{execpol.parunseq}, parallel and unsequenced execution policy - class parallel_unsequenced_policy; - - // \ref{execpol.unseq}, unsequenced execution policy - class unsequenced_policy; - - // \ref{execpol.objects}, execution policy objects - inline constexpr sequenced_policy seq{ @\unspec@ }; - inline constexpr parallel_policy par{ @\unspec@ }; - inline constexpr parallel_unsequenced_policy par_unseq{ @\unspec@ }; - inline constexpr unsequenced_policy unseq{ @\unspec@ }; -} -\end{codeblock} - \rSec2[execpol.type]{Execution policy type trait} \indexlibraryglobal{is_execution_policy}% @@ -15390,7 +15379,7 @@ \begin{itemdescr} \pnum -The header \libheader{execution} declares global objects associated with each type of execution policy. +The header \libheaderref{execution} declares global objects associated with each type of execution policy. \end{itemdescr} \rSec1[charconv]{Primitive numeric conversions} diff --git a/source/xrefdelta.tex b/source/xrefdelta.tex index cd814e0a13..6bbcbd109b 100644 --- a/source/xrefdelta.tex +++ b/source/xrefdelta.tex @@ -85,6 +85,11 @@ % https://github.com/cplusplus/draft/pull/6653 \movedxref{mismatch}{alg.mismatch} +% P3400R10 std::execution +\movedxref{stopsource.nonmembers}{stopsource} +\movedxref{stoptoken.cons}{stopsource} +\movedxref{stoptoken.nonmembers}{stopsource} + %%% Deprecated features. %%% Example: %