diff --git a/source/exceptions.tex b/source/exceptions.tex index 79684e4c51..dc7309d86b 100644 --- a/source/exceptions.tex +++ b/source/exceptions.tex @@ -1126,8 +1126,17 @@ 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. +whose promise type has an \tcode{unhandled_stopped} member function, or +\item% +when an object scope of type +\tcode{std::execution::simple_counting_scope} or +\tcode{std::execution::counting_scope} +is destroyed and +\tcode{scope.state} is not equal to +\exposid{joined}, +\exposid{unused}, or +\exposid{unused-and-closed}\iref{exec.simple.counting.ctor}. \end{itemize} \end{note} diff --git a/source/exec.tex b/source/exec.tex index 8fa3c30a24..f782d15f4d 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -635,6 +635,8 @@ struct @\libglobal{into_variant_t}@ { @\unspec@ }; struct @\libglobal{stopped_as_optional_t}@ { @\unspec@ }; struct @\libglobal{stopped_as_error_t}@ { @\unspec@ }; + struct @\libglobal{associate_t}@ { @\unspec@ }; + struct @\libglobal{spawn_future_t}@ { @\unspec@ }; inline constexpr starts_on_t @\libglobal{starts_on}@{}; inline constexpr continues_on_t @\libglobal{continues_on}@{}; @@ -653,6 +655,8 @@ inline constexpr into_variant_t @\libglobal{into_variant}@{}; inline constexpr stopped_as_optional_t @\libglobal{stopped_as_optional}@{}; inline constexpr stopped_as_error_t @\libglobal{stopped_as_error}@{}; + inline constexpr associate_t @\libglobal{associate}@{}; + inline constexpr spawn_future_t @\libglobal{spawn_future}@{}; // \ref{exec.util}, sender and receiver utilities // \ref{exec.util.cmplsig} @@ -701,6 +705,10 @@ } namespace std::execution { + // \ref{exec.consumers}, consumers + struct @\libglobal{spawn_t}@ { @\unspec@ }; + inline constexpr spawn_t spawn{}; + // \ref{exec.as.awaitable} struct @\libglobal{as_awaitable_t}@ { @\unspec@ }; inline constexpr as_awaitable_t @\libglobal{as_awaitable}@{}; @@ -708,6 +716,17 @@ // \ref{exec.with.awaitable.senders} template<@\exposconcept{class-type}@ Promise> struct with_awaitable_senders; + + // \ref{exec.scope} + // \ref{exec.scope.concepts}, scope concepts + template + concept @\libconcept{scope_token}@ = @\seebelow@; + + // \ref{exec.scope.simple.counting} + class simple_counting_scope; + + // \ref{exec.scope.counting} + class counting_scope; } \end{codeblock} @@ -4507,656 +4526,1538 @@ }); \end{codeblock} -\rSec2[exec.consumers]{Sender consumers} - -\rSec3[exec.sync.wait]{\tcode{this_thread::sync_wait}} +\rSec3[exec.associate]{\tcode{std::execution::associate}} \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. +\tcode{associate} tries to associate +a sender with an async scope such that +the scope can track the lifetime of any asynchronous operations +created with the sender. \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 +Let \exposid{associate-data} be the following exposition-only class template: - auto query(execution::get_scheduler_t) const noexcept { - return @\exposid{loop}@->get_scheduler(); +\indexlibraryglobal{execution::\exposid{associate-data}}% +\begin{codeblock} +namespace std::execution { + template<@\libconcept{scope_token}@ Token, @\libconcept{sender}@ Sender> + struct @\exposid{associate-data}@ { // \expos + using @\exposid{wrap-sender}@ = // \expos + remove_cvref_t().wrap(declval()))>; + + explicit @\exposid{associate-data}@(Token t, Sender&& s) + : @\exposid{sndr}@(t.wrap(std::forward(s))), + @\exposid{token}@(t) { + if (!@\exposid{token}@.try_associate()) + @\exposid{sndr}@.reset(); } - auto query(execution::get_delegation_scheduler_t) const noexcept { - return @\exposid{loop}@->get_scheduler(); - } - }; -} -\end{codeblock} + @\exposid{associate-data}@(const @\exposid{associate-data}@& other) + noexcept(is_nothrow_copy_constructible_v<@\exposid{wrap-sender}@> && + noexcept(other.@\exposid{token}@.try_associate())); -\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 @\exposid{sync-wait-result-type}@ = - optional>; + @\exposid{associate-data}@(@\exposid{associate-data}@&& other) + noexcept(is_nothrow_move_constructible_v<@\exposid{wrap-sender}@>); - template Sndr> - using @\exposid{sync-wait-with-variant-result-type}@ = - optional>; -} -\end{codeblock} + ~@\exposid{associate-data}@(); -\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 \tcode{apply_sender} expression above. -\end{itemize} + optional> + release() && noexcept(is_nothrow_move_constructible_v<@\exposid{wrap-sender}@>); -\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 + private: + optional<@\exposid{wrap-sender}@> @\exposid{sndr}@; // \expos + Token @\exposid{token}@; // \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}@}; } - }; + template<@\libconcept{scope_token}@ Token, @\libconcept{sender}@ Sender> + @\exposid{associate-data}@(Token, Sender&&) -> @\exposid{associate-data}@; } \end{codeblock} +\pnum +For an \exposid{associate-data} object \tcode{a}, +\tcode{a.\exposid{sndr}.has_value()} is \tcode{true} +if and only if +an association was successfully made and is owned by \tcode{a}. + +\indexlibraryctor{execution::\exposid{associate-data}}% \begin{itemdecl} -template -void set_value(Args&&... args) && noexcept; +@\exposid{associate-data}@(const @\exposid{associate-data}@& other) + noexcept(is_nothrow_copy_constructible_v<@\exposid{wrap-sender}@> && + noexcept(other.@\exposid{token}@.try_associate())); \end{itemdecl} \begin{itemdescr} +\pnum +\constraints +copy_constructible<\exposid{wrap-sender}> is \tcode{true}. + \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} +Value-initializes \exposid{sndr} and +initializes \exposid{token} with \tcode{other.\exposid{token}}. +If \tcode{other.\exposid{sndr}.has_value()} is \tcode{false}, +no further effects; +otherwise, +calls \tcode{\exposid{token}.try_associate()} and, +if that returns \tcode{true}, +calls \tcode{\exposid{sndr}.emplace(*other.\exposid{sndr})} and, +if that exits with an exception, +calls \tcode{\exposid{token}.disassociate()} before propagating the exception. \end{itemdescr} +\indexlibraryctor{execution::\exposid{associate-data}}% \begin{itemdecl} -template -void set_error(Error&& err) && noexcept; +@\exposid{associate-data}@(@\exposid{associate-data}@&& other) + noexcept(is_nothrow_move_constructible_v<@\exposid{wrap-sender}@>); \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} +Initializes \exposid{sndr} with \tcode{std::move(other.\exposid{sndr})} and +initializes \exposid{token} with \tcode{std::move(other.\linebreak{}\exposid{token})} and +then calls \tcode{other.\exposid{sndr}.reset()}. \end{itemdescr} +\indexlibrarydtor{execution::\exposid{associate-data}}% \begin{itemdecl} -void set_stopped() && noexcept; +~@\exposid{associate-data}@(); \end{itemdecl} \begin{itemdescr} \pnum \effects -Equivalent to \tcode{\exposid{state}->\exposid{loop}.finish()}. +If \tcode{\exposid{sndr}.has_value()} returns \tcode{false} then no effect; +otherwise, invokes \tcode{\exposid{sndr}.reset()} +before invoking \tcode{\exposid{token}.disassociate()}. \end{itemdescr} +\indexlibrarymember{release}{execution::\exposid{associate-data}}% +\begin{itemdecl} +optional> + release() && noexcept(is_nothrow_move_constructible_v<@\exposid{wrap-sender}@>); +\end{itemdecl} + +\begin{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: +\effects +If \tcode{\exposid{sndr}.has_value()} returns \tcode{false} then +returns an \tcode{optional} that does not contain a value; +otherwise returns an \tcode{optional} +containing a value of type \tcode{pair} +as if by: \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.@\exposid{error}@)); -} -return std::move(state.@\exposid{result}@); +return optional(pair(@\exposid{token}@, std::move(*@\exposid{sndr}@))); \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: +\ensures +\exposid{sndr} does not contain a value. +\end{itemdescr} + +\pnum +The name \tcode{associate} denotes a pipeable sender adaptor object. +For subexpressions \tcode{sndr} and \tcode{token}: \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. +If \tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or +\tcode{remove_cvref_t} +does not satisfy \libconcept{scope_token}, then +\tcode{associate(sndr, token)} is ill-formed. \item -For a stopped completion, a disengaged \tcode{optional} object is returned. -\end{itemize} +Otherwise, +the expression \tcode{associate(sndr, token)} +is expression-equivalent to: +\begin{codeblock} +transform_sender(@\exposid{get-domain-early}@(sndr), + @\exposid{make-sender}@(associate, @\exposid{associate-data}@(token, sndr))) +\end{codeblock} +except that \tcode{sndr} is evaluated only once. \end{itemize} -\rSec3[exec.sync.wait.var]{\tcode{this_thread::sync_wait_with_variant}} +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{associate_t} as follows: + +%%FIXME: Where did data-type and FWD-ENV-T come from? +\indexlibraryglobal{execution::\exposid{impls-for}}% +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{get-state}@ = @\seebelow@; // \expos + static constexpr auto @\exposid{start}@ = @\seebelow@; // \expos + + template + static consteval void @\exposid{check-types}@() { // \expos + using associate_data_t = remove_cvref_t<@\exposid{data-type}@>; + using child_type_t = typename associate_data_t::@\exposid{wrap-sender}@; + (void)get_completion_signatures(); + } + }; +} +\end{codeblock} \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: +The member \tcode{\exposid{impls-for}::\exposid{get-state}} +is initialized with a callable object equivalent to the following lambda: + \begin{codeblock} -apply_sender(@\exposid{get-domain-early}@(sndr), sync_wait_with_variant, sndr) +[](Sndr&& sndr, Rcvr& rcvr) noexcept(@\seebelow@) { + auto [_, data] = std::forward(sndr); + auto dataParts = std::move(data).release(); + + using scope_tkn = decltype(dataParts->first); + using wrap_sender = decltype(dataParts->second); + using op_t = connect_result_t; + + struct op_state { + bool @\exposid{associated}@ = \tcode{false}; // \expos + union { + Rcvr* @\exposid{rcvr}@; // \expos + struct { + scope_tkn @\exposid{token}@; // \expos + op_t @\exposid{op}@; // \expos + } @\exposid{assoc}@; // \expos + }; + + explicit op_state(Rcvr& r) noexcept + : @\exposid{rcvr}@(addressof(r)) {} + + explicit op_state(scope_tkn tkn, wrap_sender&& sndr, Rcvr& r) try + : @\exposid{associated}@(\tcode{true}), + @\exposid{assoc}@(tkn, connect(std::move(sndr), std::move(r))) { + } + catch (...) { + tkn.disassociate(); + throw; + } + + op_state(op_state&&) = delete; + + ~op_state() { + if (@\exposid{associated}@) { + @\exposid{assoc}@.@\exposid{op}@.~op_t(); + @\exposid{assoc}@.@\exposid{token}@.disassociate(); + @\exposid{assoc}@.@\exposid{token}@.~scope_tkn(); + } + } + + void @\exposid{run}@() noexcept { // \expos + if (@\exposid{associated}@) + @\exposid{start}@(@\exposid{assoc}@.@\exposid{op}@); + else + set_stopped(std::move(*@\exposid{rcvr}@)); + } + }; + + if (dataParts) + return op_state{std::move(dataParts->first), std::move(dataParts->second), rcvr}; + else + return op_state{rcvr}; +} \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: +The expression in the \tcode{noexcept} clause of +\tcode{\exposid{impls-for}::\exposid{get-state}} is \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))); +is_nothrow_constructible_v, Sndr> && +is_nothrow_move_constructible_v<@\exposid{wrap-sender}@> && +@\exposconcept{nothrow-callable}@ +\end{codeblock} +where \exposid{wrap-sender} is the type +\tcode{remove_cvref_t<\exposid{data-type}>::\exposid{wrap-sender}}. + +\pnum +The member \tcode{\exposid{impls-for}::\exposid{start}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](auto& state, auto&) noexcept -> void { + state.@\exposid{run}@(); } -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: +%%FIXME: What are "sndr" and "token" referring to here? +The evaluation of \tcode{associate(sndr, token)} +may cause side effects observable +via \tcode{token}{'s} associated async scope object. + +\pnum +The expression \tcode{spawn_future(sndr, token, env)} +has the following effects: + \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. +Uses \tcode{alloc} to allocate and construct an object \tcode{s} of +a type that is a specialization of \exposid{spawn-future-\linebreak{}state} +from \tcode{alloc}, \tcode{token.wrap(sndr)}, \tcode{token}, and \tcode{senv}. +If an exception is thrown then +any constructed objects are destroyed and +any allocated memory is deallocated. + \item -For an error completion, an exception is thrown. +Constructs an object \tcode{u} of +a type that is a specialization of \tcode{unique_ptr} such that: + \begin{itemize} + \item + \tcode{u.get()} is equal to the address of \tcode{s}, and + \item + \tcode{u.get_deleter()(u.release())} is equivalent to + \tcode{u.release()->\exposid{abandon}()}. + \end{itemize} + \item -For a stopped completion, a disengaged \tcode{optional} object is returned. -\end{itemize} +Returns \tcode{\exposid{make-sender}(spawn_future, std::move(u))}. \end{itemize} -\rSec1[exec.util]{Sender/receiver utilities} +\pnum +The expression \tcode{spawn_future(sndr, token)} is expression-equivalent to +\tcode{spawn_future(sndr, token, ex\-ecution::env<>())}. -\rSec2[exec.util.cmplsig]{\tcode{execution::completion_signatures}} +\rSec3[exec.stop.when]{Exposition-only \tcode{std::execution::\exposid{stop-when}}} \pnum -\tcode{completion_signatures} is a type -that encodes a set of completion signatures\iref{exec.async.ops}. +%%FIXME: Should stop-when be declared somewhere as \expos? +\exposid{stop-when} fuses an additional stop token \tcode{t} +into a sender so that, upon connecting to a receiver \tcode{r}, +the resulting operation state receives stop requests from both +\tcode{t} and the token returned from \tcode{get_stop_token(get_env(r))}. \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}: +The name \exposid{stop-when} denotes an exposition-only sender adaptor. +For subexpressions \tcode{sndr} and \tcode{token}: \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)} +\item +If \tcode{decltype((sndr))} does not satisfy \libconcept{sender}, or +\tcode{remove_cvref_t} +does not satisfy \libconcept{stoppable_token}, +then \tcode{\exposid{stop-when}(sndr, token)} is ill-formed. + +\item +Otherwise, +if \tcode{remove_cvref_t} models +\libconcept{unstoppable_token} then +\tcode{\exposid{stop-when}(\linebreak{}sndr, token)} is expression-equivalent to +\tcode{sndr}. + +\item +Otherwise, +\tcode{\exposid{stop-when}(sndr, token)} returns a sender \tcode{osndr}. +%%FIXME: What is rtoken if osndr is not connected to a receiver? +If \tcode{osndr} is connected to a receiver \tcode{r}, +let \tcode{rtoken} be the result of \tcode{get_stop_token(get_env(r))}. + +\begin{itemize} +\item +If the type of \tcode{rtoken} models \libconcept{unstoppable_token} then +the effects of connecting \tcode{osndr} to \tcode{r} +are equivalent to +\tcode{connect(write_env(sndr, prop(get_stop_token, token)), r)}. + +\item +Otherwise, +the effects of connecting \tcode{osndr} to \tcode{r} +are equivalent to +\tcode{connect(write_env(sndr, prop(get_stop_token, stoken)), r)} +where \tcode{stoken} is an object of +an exposition-only type \placeholder{stoken-t} such that: + \begin{itemize} + \item + \placeholder{stoken-t} models \libconcept{stoppable_token}; + \item + \tcode{stoken.stop_requested()} returns + \tcode{token.stop_requested() || rtoken.stop_reques-\linebreak{}ted()}; + \item + \tcode{stoken.stop_possible()} returns + \tcode{token.stop_possible() || rtoken.stop_possible()}; and + \item + for types \tcode{Fn} and \tcode{Init} such that both + \tcode{\libconcept{invocable}} and + \tcode{\libconcept{constructible_from}} + are modeled, + \tcode{\placeholder{stoken-t}::callback_type} models + \tcode{\exposconcept{stoppable-callback-for}}. + \begin{tailnote} + For an object \tcode{fn} of type \tcode{Fn} + constructed from a value, \tcode{init}, of type \tcode{Init}, + registering \tcode{fn} using + \tcode{\placeholder{stoken-t}::callback_type(stoken, init)} + results in an invocation of \tcode{fn} when + a callback registered with \tcode{token} or \tcode{rtoken} would be invoked. + \tcode{fn} is invoked at most once. + \end{tailnote} + \end{itemize} +\end{itemize} \end{itemize} -\end{example} + +\rSec3[exec.spawn.future]{\tcode{std::execution::spawn_future}} \pnum -This subclause makes use of the following exposition-only entities: +\tcode{spawn_future} attempts to associate the given input sender +with the given token's async scope and, on success, +eagerly starts the input sender; +the return value is a sender that, when connected and started, +completes with either +the result of the eagerly-started input sender or with +\tcode{set_stopped} if the input sender was not started. + +\pnum +The name \tcode{spawn_future} denotes a customization point object. +For subexpressions \tcode{sndr}, \tcode{token}, and \tcode{env}, +\begin{itemize} +\item let \tcode{Sndr} be \tcode{decltype((sndr))}, +\item let \tcode{Token} be \tcode{remove_cvref_t}, and +\item let \tcode{Env} be \tcode{remove_cvref_t}. +\end{itemize} +If any of +\tcode{\libconcept{sender}}, +\tcode{\libconcept{scope_token}}, or +\tcode{queryable} +are not satisfied, +the expression \tcode{spawn_future(sndr, token, env)} is ill-formed. + +\pnum +Let \exposid{spawn-future-state-base} be the exposition-only class template: + +\indexlibraryglobal{execution::\exposid{spawn-future-state-base}}% \begin{codeblock} -template - concept @\defexposconcept{completion-signature}@ = @\seebelow@; +namespace std::execution { + template + struct @\exposid{spawn-future-state-base}@; // \expos + + template + struct @\exposid{spawn-future-state-base}@> { // \expos + using @\exposid{variant_t}@ = @\seebelow@; // \expos + @\exposid{variant_t}@ @\exposid{result}@; // \expos + virtual void @\exposid{complete}@() noexcept = 0; // \expos + }; +} \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: +Let \tcode{Sigs} be the pack of arguments to +the \tcode{completion_signatures} specialization provided as +a parameter to the \exposid{spawn-future-state-base} class template. +Let \placeholder{as-tuple} be an alias template that +transforms a completion signature \tcode{Tag(Args...)} +into the tuple specialization \tcode{\exposid{decayed-tuple}}. + \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. +If \tcode{is_nothrow_constructible_v, Arg>} is \tcode{true} +for every type \tcode{Arg} +in every parameter pack \tcode{Args} +in every completion signature \tcode{Tag(Args...)} +in \tcode{Sigs} then +\exposid{variant_t} denotes the type +\tcode{variant, \placeholder{as-tuple}...>}, +except with duplicate types removed. + \item -\tcode{set_stopped_t()} +Otherwise +\exposid{variant_t} denotes the type +\tcode{variant, tuple, \placeholder{as-tuple}...>}, +except with duplicate types removed. \end{itemize} \pnum +Let \exposid{spawn-future-receiver} be the exposition-only class template: + +\indexlibraryglobal{execution::\exposid{spawn-future-receiver}}% \begin{codeblock} -template - struct @\exposid{indirect-meta-apply}@ { - template class T, class... As> - using @\exposid{meta-apply}@ = T; // \expos - }; +namespace std::execution { + template + struct @\exposid{spawn-future-receiver}@ { // \expos + using receiver_concept = receiver_t; -template - concept @\defexposconcept{always-true}@ = true; // \expos + @\exposid{spawn-future-state-base}@* @\exposid{state}@; // \expos -template class Tuple, - template class Variant> - using @\exposid{gather-signatures}@ = @\seebelow@; + template + void set_value(T&&... t) && noexcept { + @\exposid{set-complete}@(std::forward(t)...); + } + + template + void set_error(E&& e) && noexcept { + @\exposid{set-complete}@(std::forward(e)); + } + + void set_stopped() && noexcept { + @\exposid{set-complete}@(); + } + + private: + template + void @\exposid{set-complete}@(T&&... t) noexcept { // \expos + constexpr bool nothrow = (is_nothrow_constructible_v, T> && ...); + try { + @\exposid{state}@->@\exposid{result}@.template emplace<@\exposid{decayed-tuple}@>(CPO{}, + std::forward(t)...); + } + catch (...) { + if constexpr (!nothrow) { + using tuple_t = @\exposid{decayed-tuple}@; + @\exposid{state}@->@\exposid{result}@.template emplace(set_error_t{}, current_exception()); + } + } + @\exposid{state}@->@\exposid{complete}@(); + } + }; +} \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: +Let \placeholder{ssource-t} be an unspecified type +that models \exposconcept{stoppable-source} and +let \tcode{ssource} be an lvalue of type \placeholder{ssource-t}. +Let \placeholder{stoken-t} be \tcode{decltype(ssource.get_token())}. +Let \exposid{future-spawned-sender} be the alias template: + \begin{codeblock} -typename @\exposid{indirect-meta-apply}@<@\exposid{always-true}@>::template @\exposid{meta-apply}@ +template<@\libconcept{sender}@ Sender, class Env> +using @\exposid{future-spawned-sender}@ = // \expos + decltype(write_env(@\exposid{stop-when}@(declval(), declval<@\placeholder{stoken-t}@>()), declval())); \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} +Let \exposid{spawn-future-state} be the exposition-only class template: -\pnum +\indexlibraryglobal{execution::\exposid{spawn-future-state}}% \begin{codeblock} namespace std::execution { - template<@\exposconcept{completion-signature}@... Fns> - struct completion_signatures {}; + template + struct @\exposid{spawn-future-state}@ // \expos + : @\exposid{spawn-future-state-base}@>> { + using @\exposid{sigs-t}@ = // \expos + completion_signatures_of_t<@\exposid{future-spawned-sender}@>; + using @\exposid{receiver-t}@ = // \expos + @\exposid{spawn-future-receiver}@<@\exposid{sigs-t}@>; + using @\exposid{op-t}@ = // \expos + connect_result_t<@\exposid{future-spawned-sender}@, @\exposid{receiver-t}@>; + + @\exposid{spawn-future-state}@(Alloc alloc, Sender&& sndr, Token token, Env env) // \expos + : @\exposid{alloc}@(std::move(alloc)), + @\exposid{op}@(connect( + write_env(@\exposid{stop-when}@(std::forward(sndr), @\exposid{ssource}@.get_token()), std::move(env)), + @\exposid{receiver-t}@(this))), + @\exposid{token}@(std::move(token)), + @\exposid{associated}@(token.try_associate()) { + if (associated) + @\exposid{start}@(@\exposid{op}@); + else + set_stopped(@\exposid{receiver-t}@(this)); + } - template, - 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>; + void @\exposid{complete}@() noexcept override; // \expos + void @\exposid{consume}@(@\libconcept{receiver}@ auto& rcvr) noexcept; // \expos + void @\exposid{abandon}@() noexcept; // \expos - template, - template class Variant = @\exposid{variant-or-empty}@> - requires @\libconcept{sender_in}@ - using error_types_of_t = - @\exposid{gather-signatures}@, - type_identity_t, Variant>; + private: + using @\exposid{alloc-t}@ = // \expos + typename allocator_traits::template rebind_alloc<@\exposid{spawn-future-state}@>; - template> - requires @\libconcept{sender_in}@ - constexpr bool sends_stopped = - !@\libconcept{same_as}@<@\exposid{type-list}@<>, - @\exposid{gather-signatures}@, - @\exposid{type-list}@, @\exposid{type-list}@>>; + @\exposid{alloc-t}@ @\exposid{alloc}@; // \expos + @\exposid{ssource-t}@ @\exposid{ssource}@; // \expos + @\exposid{op-t}@ @\exposid{op}@; // \expos + Token @\exposid{token}@; // \expos + bool @\exposid{associated}@; // \expos + + void @\exposid{destroy}@() noexcept; // \expos + }; } \end{codeblock} -\rSec2[exec.util.cmplsig.trans]{\tcode{execution::transform_completion_signatures}} +\pnum +For purposes of determining the existence of a data race, +\exposid{complete}, \exposid{consume}, and \exposid{abandon} +behave as atomic operations\iref{intro.multithread}. +These operations on a single object of a type +that is a specialization of \exposid{spawn-future-state} +appear to occur in a single total order. +\indexlibrarymember{\exposid{complete}}{execution::\exposid{spawn-future-state}}% +\begin{itemdecl} +void @\exposid{complete}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} \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. +\effects +\begin{itemize} +\item +No effects if this invocation of \exposid{complete} happens before +an invocation of \exposid{consume} or \exposid{abandon} on *this; +\item +otherwise, +if an invocation of \exposid{consume} on \tcode{*this} happens before +this invocation of \exposid{complete} then +there is a receiver, \tcode{rcvr}, registered and +that receiver is completed as if by \tcode{\exposid{consume}(rcvr)}; +\item +otherwise, +\exposid{destroy} is invoked. +\end{itemize} +\end{itemdescr} + +\indexlibrarymember{\exposid{consume}}{execution::\exposid{spawn-future-state}}% +\begin{itemdecl} +void @\exposid{consume}@(@\libconcept{receiver}@ auto& rcvr) noexcept; +\end{itemdecl} + +\begin{itemdescr} \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 it is not already there, and -leaving the other completion signatures alone. +\effects +\begin{itemize} +\item +If this invocation of \exposid{consume} happens before +an invocation of \exposid{complete} on \tcode{*this} then +\tcode{rcvr} is registered to be completed when +\exposid{complete} is subsequently invoked on \tcode{*this}; + +\item +otherwise, +\tcode{rcvr} is completed as if by: \begin{codeblock} -template - using my_set_value_t = - completion_signatures; +std::move(this->@\exposid{result}@).visit( + [&rcvr](auto&& tuple) noexcept { + if constexpr (!@\libconcept{same_as}@, monostate>) { + apply([&rcvr](auto cpo, auto&&... vals) { + cpo(std::move(rcvr), std::move(vals)...); + }, std::move(tuple)); + } + }); +\end{codeblock} +\end{itemize} +\end{itemdescr} -using my_completion_signatures = - transform_completion_signatures< - completion_signatures_of_t, - completion_signatures, - my_set_value_t>; +\indexlibrarymember{\exposid{abandon}}{execution::\exposid{spawn-future-state}}% +\begin{itemdecl} +void @\exposid{abandon}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +\begin{itemize} +\item +If this invocation of \exposid{abandon} happens before +an invocation of \exposid{complete} on \tcode{*this} then +equivalent to: +\begin{codeblock} +@\exposid{ssource}@.request_stop(); \end{codeblock} -\end{example} +\item +otherwise, +\exposid{destroy} is invoked. +\end{itemize} +\end{itemdescr} +\indexlibrarymember{\exposid{destroy}}{execution::\exposid{spawn-future-state}}% +\begin{itemdecl} +void @\exposid{destroy}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} \pnum -This subclause makes use of the following exposition-only entities: +\effects +Equivalent to: \begin{codeblock} -template - using @\exposid{default-set-value}@ = - completion_signatures; +auto token = std::move(this->@\exposid{token}@); +bool associated = this->@\exposid{associated}@; -template - using @\exposid{default-set-error}@ = - completion_signatures; +{ + auto alloc = std::move(this->@\exposid{alloc}@); + + allocator_traits<@\exposid{alloc-t}@>::@\exposid{destroy}@(alloc, this); + allocator_traits<@\exposid{alloc-t}@>::deallocate(alloc, this, 1); +} + +if (associated) + token.disassociate(); \end{codeblock} +\end{itemdescr} \pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \tcode{spawn_future_t} as follows: + +\indexlibraryglobal{execution::\exposid{impls-for}}% \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@>; + template<> + struct @\exposid{impls-for}@ : @\exposid{default-impls}@ { + static constexpr auto @\exposid{start}@ = @\seebelow@; // \expos + }; } \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. +The member \tcode{\exposid{impls-for}::\exposid{start}} +is initialized with a callable object equivalent to the following lambda: +\begin{codeblock} +[](auto& state, auto& rcvr) noexcept -> void { + state->@\exposid{consume}@(rcvr); +} +\end{codeblock} \pnum -Let \tcode{Vs} be a pack of the types in the \exposid{type-list} named by -\tcode{\exposid{gather-signatures}}. +For the expression \tcode{spawn_future(sndr, token, env)} +let \tcode{new_sender} be the expression \tcode{token.wrap(sndr)} and +let \tcode{alloc} and \tcode{senv} be defined as follows: +\begin{itemize} +\item +if the expression \tcode{get_allocator(env)} is well-formed, then +\tcode{alloc} is the result of \tcode{get_allocator(env)} and +\tcode{senv} is the expression \tcode{env}; +\item +otherwise, +if the expression \tcode{get_allocator(get_env(new_sender))} is well-formed, then +\tcode{alloc} is the result of \tcode{get_allocator(get_env(new_sender))} and +\tcode{senv} is the expression +\tcode{\exposid{JOIN-ENV}(prop(get_allocator, alloc), env)}; +\item +otherwise, +\tcode{alloc} is \tcode{allocator()} and +\tcode{senv} is the expression \tcode{env}. +\end{itemize} -\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}...>}. +\rSec2[exec.consumers]{Sender consumers} + +\rSec3[exec.sync.wait]{\tcode{this_thread::sync_wait}} \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}. +\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 -If any of the above types are ill-formed, -then -\begin{codeblock} -transform_completion_signatures -\end{codeblock} -is ill-formed. -Otherwise, +Let \exposid{sync-wait-env} be the following exposition-only class type: \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.envs]{Queryable utilities} - -\rSec2[exec.prop]{Class template \tcode{prop}} +namespace std::this_thread { + struct @\exposid{sync-wait-env}@ { + execution::run_loop* @\exposid{loop}@; // \expos -\begin{codeblock} -namespace std::execution { - template - struct @\libglobal{prop}@ { - QueryTag @\exposid{query_}@; // \expos - ValueType @\exposid{value_}@; // \expos + auto query(execution::get_scheduler_t) const noexcept { + return @\exposid{loop}@->get_scheduler(); + } - constexpr const ValueType& query(QueryTag) const noexcept { - return @\exposid{value_}@; + auto query(execution::get_delegation_scheduler_t) const noexcept { + return @\exposid{loop}@->get_scheduler(); } }; - - template - prop(QueryTag, ValueType) -> prop>; } \end{codeblock} \pnum -Class template \tcode{prop} is for building a queryable object -from a query object and a value. +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 @\exposid{sync-wait-result-type}@ = + optional>; + + template Sndr> + using @\exposid{sync-wait-with-variant-result-type}@ = + optional>; +} +\end{codeblock} \pnum -\mandates -\tcode{\exposconcept{callable}>} -is modeled, -where \exposid{prop-like} is the following exposition-only class template: +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} -template -struct @\exposid{prop-like}@ { // \expos - const ValueType& query(auto) const noexcept; -}; +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 \tcode{apply_sender} expression above. +\end{itemize} \pnum -\begin{example} +Let \exposid{sync-wait-state} and \exposid{sync-wait-receiver} +be the following exposition-only class templates: \begin{codeblock} -template<@\libconcept{sender}@ Sndr> -sender auto parameterize_work(Sndr sndr) { - // Make an environment such that \tcode{get_allocator(env)} returns a reference to a copy of \tcode{my_alloc\{\}}. - auto e = prop(get_allocator, my_alloc{}); +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 + }; - // Parameterize the input sender so that it will use our custom execution environment. - return write_env(sndr, e); -} -\end{codeblock} -\end{example} + template + struct @\exposid{sync-wait-receiver}@ { // \expos + using receiver_concept = execution::receiver_t; + @\exposidnc{sync-wait-state}@* @\exposid{state}@; // \expos -\pnum -Specializations of \tcode{prop} are not assignable. + template + void set_value(Args&&... args) && noexcept; -\rSec2[exec.env]{Class template \tcode{env}} + template + void set_error(Error&& err) && noexcept; -\begin{codeblock} -namespace std::execution { - template<@\exposconcept{queryable}@... Envs> - struct @\libglobal{env}@ { - Envs@$_0$@ @$\exposid{envs}_0$@; // \expos - Envs@$_1$@ @$\exposid{envs}_1$@; // \expos - @\vdots@ - Envs@$_{n-1}$@ @$\exposid{envs}_{n-1}$@; // \expos + void set_stopped() && noexcept; - template - constexpr decltype(auto) query(QueryTag q) const noexcept(@\seebelow@); + @\exposid{sync-wait-env}@ get_env() const noexcept { return {&@\exposid{state}@->@\exposid{loop}@}; } }; - - template - env(Envs...) -> env...>; } \end{codeblock} -\pnum -The class template \tcode{env} is used to construct a queryable object -from several queryable objects. -Query invocations on the resulting object are resolved -by attempting to query each subobject in lexical order. - -\pnum -Specializations of \tcode{env} are not assignable. - -\pnum -It is unspecified -whether \tcode{env} supports initialization -using a parenthesized \grammarterm{expression-list}\iref{dcl.init}, -unless the \grammarterm{expression-list} consist of -a single element of type (possibly const) \tcode{env}. +\begin{itemdecl} +template +void set_value(Args&&... args) && noexcept; +\end{itemdecl} +\begin{itemdescr} \pnum -\begin{example} +\effects +Equivalent to: \begin{codeblock} -template<@\libconcept{sender}@ Sndr> -sender auto parameterize_work(Sndr sndr) { - // Make an environment such that: - // \tcode{get_allocator(env)} returns a reference to a copy of \tcode{my_alloc\{\}} - // \tcode{get_scheduler(env)} returns a reference to a copy of \tcode{my_sched\{\}} - auto e = env{prop(get_allocator, my_alloc{}), - prop(get_scheduler, my_sched{})}; - - // Parameterize the input sender so that it will use our custom execution environment. - return write_env(sndr, e); +try { + @\exposid{state}@->@\exposid{result}@.emplace(std::forward(args)...); +} catch (...) { + @\exposid{state}@->@\exposid{error}@ = current_exception(); } +@\exposid{state}@->@\exposid{loop}@.finish(); \end{codeblock} -\end{example} +\end{itemdescr} -\indexlibrarymember{query}{env}% \begin{itemdecl} -template -constexpr decltype(auto) query(QueryTag q) const noexcept(@\seebelow@); +template +void set_error(Error&& err) && noexcept; \end{itemdecl} \begin{itemdescr} \pnum -Let \exposconcept{has-query} be the following exposition-only concept: +\effects +Equivalent to: \begin{codeblock} -template - concept @\defexposconcept{has-query}@ = // \expos - requires (const Env& env) { - env.query(QueryTag()); - }; +@\exposid{state}@->@\exposid{error}@ = @\exposid{AS-EXCEPT-PTR}@(std::forward(err)); // see \ref{exec.general} +@\exposid{state}@->@\exposid{loop}@.finish(); \end{codeblock} +\end{itemdescr} -\pnum -Let \exposid{fe} be the first element of -$\exposid{envs}_0$, $\exposid{envs}_1$, $\dotsc$, $\exposid{envs}_{n-1}$ -such that the expression \tcode{\exposid{fe}.query(q)} is well-formed. - -\pnum -\constraints -\tcode{(\exposconcept{has-query} || ...)} is \tcode{true}. +\begin{itemdecl} +void set_stopped() && noexcept; +\end{itemdecl} +\begin{itemdescr} \pnum \effects -Equivalent to: \tcode{return \exposid{fe}.query(q);} - -\pnum -\remarks -The expression in the \tcode{noexcept} clause is equivalent -to \tcode{noexcept(\exposid{fe}.query(q))}. +Equivalent to \tcode{\exposid{state}->\exposid{loop}.finish()}. \end{itemdescr} -\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}, \defn{finishing}, or \defn{finished}. +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); -\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. +state.@\exposid{loop}@.run(); +if (state.@\exposid{error}@) { + rethrow_exception(std::move(state.@\exposid{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} + +\rSec3[exec.spawn]{\tcode{std::execution::spawn}} + +\pnum +\tcode{spawn} attempts to associate the given input sender with +the given token's async scope and, on success, +eagerly starts the input sender. + +\pnum +The name \tcode{spawn} denotes a customization point object. +For subexpressions \tcode{sndr}, \tcode{token}, and \tcode{env}, +\begin{itemize} +\item let \tcode{Sndr} be \tcode{decltype((sndr))}, +\item let \tcode{Token} be \tcode{remove_cvref_t}, and +\item let \tcode{Env} be \tcode{remove_cvref_t}. +\end{itemize} +If any of +\tcode{\libconcept{sender}}, +\tcode{\libconcept{scope_token}}, or +\tcode{queryable} +are not satisfied, +the expression \tcode{spawn(\linebreak{}sndr, token, env)} is ill-formed. + +\pnum +Let \exposid{spawn-state-base} be the exposition-only class: + +\indexlibraryglobal{execution::\exposid{spawn-state-base}}% +\begin{codeblock} +namespace std::execution { + struct @\exposid{spawn-state-base}@ { // \expos + virtual void @\exposid{complete}@() noexcept = 0; // \expos + }; +} +\end{codeblock} + +\pnum +Let \exposid{spawn-receiver} be the exposition-only class: + +\indexlibraryglobal{execution::\exposid{spawn-receiver}}% +\begin{codeblock} +namespace std::execution { + struct @\exposid{spawn-receiver}@ { // \expos + using receiver_concept = receiver_t; + + @\exposid{spawn-state-base}@* @\exposid{state}@; // \expos + void set_value() && noexcept { @\exposid{state}@->@\exposid{complete}@(); } + void set_stopped() && noexcept { @\exposid{state}@->@\exposid{complete}@(); } + }; +} +\end{codeblock} + +\pnum +Let \exposid{spawn-state} be the exposition-only class template: + +\indexlibraryglobal{execution::\exposid{spawn-state}}% +\begin{codeblock} +namespace std::execution { + template + struct @\exposid{spawn-state}@ : @\exposid{spawn-state-base}@ { // \expos + using @\exposid{op-t}@ = connect_result_t; // \expos + + @\exposid{spawn-state}@(Alloc alloc, Sender&& sndr, Token token); // \expos + void @\exposid{complete}@() noexcept override; // \expos + void @\exposid{run}@(); // \expos + + private: + using @\exposid{alloc-t}@ = // \expos + typename allocator_traits::template rebind_alloc<@\exposid{spawn-state}@>; + + @\exposid{alloc-t}@ @\exposid{alloc}@; // \expos + @\exposid{op-t}@ @\exposid{op}@; // \expos + Token @\exposid{token}@; // \expos + + void @\exposid{destroy}@() noexcept; // \expos + }; +} +\end{codeblock} + +\indexlibraryctor{execution::\exposid{spawn-state}}% +\begin{itemdecl} +@\exposid{spawn-state}@(Alloc alloc, Sender&& sndr, Token token); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Initializes +\exposid{alloc} with \tcode{alloc}, +\exposid{token} with \tcode{token}, and +\exposid{op} with: +\begin{codeblock} +connect(std::move(sndr), @\exposid{spawn-receiver}@(this)) +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{\exposid{run}}{execution::\exposid{spawn-state}}% +\begin{itemdecl} +void @\exposid{run}@(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +if (@\exposid{token}@.try_associate()) + start(@\exposid{op}@); +else + @\exposid{destroy}@(); +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{\exposid{complete}}{execution::\exposid{spawn-state}}% +\begin{itemdecl} +void @\exposid{complete}@() noexcept override; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +auto token = std::move(this->@\exposid{token}@); + +@\exposid{destroy}@(); +token.disassociate(); +\end{codeblock} +\end{itemdescr} + +\indexlibrarymember{\exposid{destroy}}{execution::\exposid{spawn-state}}% +\begin{itemdecl} +void @\exposid{destroy}@() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to: +\begin{codeblock} +auto alloc = std::move(this->@\exposid{alloc}@); + +allocator_traits<@\exposid{alloc-t}@>::@\exposid{destroy}@(alloc, this); +allocator_traits<@\exposid{alloc-t}@>::deallocate(alloc, this, 1); +\end{codeblock} +\end{itemdescr} + +\pnum +For the expression \tcode{spawn(sndr, token, env)} +let \tcode{new_sender} be the expression \tcode{token.wrap(sndr)} and +let \tcode{alloc} and \tcode{senv} be defined as follows: +\begin{itemize} +\item +if the expression \tcode{get_allocator(env)} is well-formed, then +\tcode{alloc} is the result of \tcode{get_allocator(env)} and +\tcode{senv} is the expression \tcode{env}, +\item +otherwise +if the expression \tcode{get_allocator(get_env(new_sender))} is well-formed, then +\tcode{alloc} is the result of \tcode{get_allocator(get_env(new_sender))} and +\tcode{senv} is the expression \tcode{\exposid{JOIN-ENV}(prop(get_allocator, alloc), env)}, +\item +otherwise +\tcode{alloc} is \tcode{allocator()} and +\tcode{senv} is the expression \tcode{env}. +\end{itemize} + +\pnum +The expression \tcode{spawn(sndr, token, env)} is of type \tcode{void} and +has the following effects: +%%FIXME: Was this supposed to be more than a single bullet? +\begin{itemize} +\item +Uses \tcode{alloc} to allocate and construct an object \tcode{o} of +type that is a specialization of \tcode{\exposid{spawn-state}} from +\tcode{alloc}, \tcode{write_env(token.wrap(sndr), senv)}, and \tcode{token} +and then +invokes \tcode{o.\exposid{run}()}. +If an exception is thrown then +any constructed objects are destroyed and any allocated memory is deallocated. +\end{itemize} + +\pnum +The expression \tcode{spawn(sndr, token)} is expression-equivalent to +\tcode{spawn(sndr, token, execution::env<>(\linebreak{}))}. + +\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, + 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, + 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}@ + 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]{\tcode{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 it is not already there, and +leaving the other completion signatures alone. +\begin{codeblock} +template + using my_set_value_t = + completion_signatures; + +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 @\exposid{default-set-value}@ = + completion_signatures; + +template + using @\exposid{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.envs]{Queryable utilities} + +\rSec2[exec.prop]{Class template \tcode{prop}} + +\begin{codeblock} +namespace std::execution { + template + struct @\libglobal{prop}@ { + QueryTag @\exposid{query_}@; // \expos + ValueType @\exposid{value_}@; // \expos + + constexpr const ValueType& query(QueryTag) const noexcept { + return @\exposid{value_}@; + } + }; + + template + prop(QueryTag, ValueType) -> prop>; +} +\end{codeblock} + +\pnum +Class template \tcode{prop} is for building a queryable object +from a query object and a value. + +\pnum +\mandates +\tcode{\exposconcept{callable}>} +is modeled, +where \exposid{prop-like} is the following exposition-only class template: +\begin{codeblock} +template +struct @\exposid{prop-like}@ { // \expos + const ValueType& query(auto) const noexcept; +}; +\end{codeblock} + +\pnum +\begin{example} +\begin{codeblock} +template<@\libconcept{sender}@ Sndr> +sender auto parameterize_work(Sndr sndr) { + // Make an environment such that \tcode{get_allocator(env)} returns a reference to a copy of \tcode{my_alloc\{\}}. + auto e = prop(get_allocator, my_alloc{}); + + // Parameterize the input sender so that it will use our custom execution environment. + return write_env(sndr, e); +} +\end{codeblock} +\end{example} + +\pnum +Specializations of \tcode{prop} are not assignable. + +\rSec2[exec.env]{Class template \tcode{env}} + +\begin{codeblock} +namespace std::execution { + template<@\exposconcept{queryable}@... Envs> + struct @\libglobal{env}@ { + Envs@$_0$@ @$\exposid{envs}_0$@; // \expos + Envs@$_1$@ @$\exposid{envs}_1$@; // \expos + @\vdots@ + Envs@$_{n-1}$@ @$\exposid{envs}_{n-1}$@; // \expos + + template + constexpr decltype(auto) query(QueryTag q) const noexcept(@\seebelow@); + }; + + template + env(Envs...) -> env...>; +} +\end{codeblock} + +\pnum +The class template \tcode{env} is used to construct a queryable object +from several queryable objects. +Query invocations on the resulting object are resolved +by attempting to query each subobject in lexical order. + +\pnum +Specializations of \tcode{env} are not assignable. + +\pnum +It is unspecified +whether \tcode{env} supports initialization +using a parenthesized \grammarterm{expression-list}\iref{dcl.init}, +unless the \grammarterm{expression-list} consist of +a single element of type (possibly const) \tcode{env}. + +\pnum +\begin{example} +\begin{codeblock} +template<@\libconcept{sender}@ Sndr> +sender auto parameterize_work(Sndr sndr) { + // Make an environment such that: + // \tcode{get_allocator(env)} returns a reference to a copy of \tcode{my_alloc\{\}} + // \tcode{get_scheduler(env)} returns a reference to a copy of \tcode{my_sched\{\}} + auto e = env{prop(get_allocator, my_alloc{}), + prop(get_scheduler, my_sched{})}; + + // Parameterize the input sender so that it will use our custom execution environment. + return write_env(sndr, e); +} +\end{codeblock} +\end{example} + +\indexlibrarymember{query}{env}% +\begin{itemdecl} +template +constexpr decltype(auto) query(QueryTag q) const noexcept(@\seebelow@); +\end{itemdecl} + +\begin{itemdescr} +\pnum +Let \exposconcept{has-query} be the following exposition-only concept: +\begin{codeblock} +template + concept @\defexposconcept{has-query}@ = // \expos + requires (const Env& env) { + env.query(QueryTag()); + }; +\end{codeblock} + +\pnum +Let \exposid{fe} be the first element of +$\exposid{envs}_0$, $\exposid{envs}_1$, $\dotsc$, $\exposid{envs}_{n-1}$ +such that the expression \tcode{\exposid{fe}.query(q)} is well-formed. + +\pnum +\constraints +\tcode{(\exposconcept{has-query} || ...)} is \tcode{true}. + +\pnum +\effects +Equivalent to: \tcode{return \exposid{fe}.query(q);} + +\pnum +\remarks +The expression in the \tcode{noexcept} clause is equivalent +to \tcode{noexcept(\exposid{fe}.query(q))}. +\end{itemdescr} + +\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}, \defn{finishing}, or \defn{finished}. + +\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 @@ -5165,503 +6066,1075 @@ \begin{codeblock} namespace std::execution { - class @\libglobal{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 - @\exposid{run-loop-opstate-base}@* @\exposid{next}@; // \expos - }; - template - using @\exposid{run-loop-opstate}@ = @\unspec@; // \expos + class @\libglobal{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 + @\exposid{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 \libconcept{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 \libconcept{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{\libconcept{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} + +\indexlibraryctor{run_loop}% +\begin{itemdecl} +run_loop() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\ensures +\exposid{count} is \tcode{0} and \exposid{state} is \exposid{starting}. +\end{itemdescr} + +\indexlibrarydtor{run_loop}% +\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} sets \exposid{state} to \exposid{finished} +and 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} + +\indexlibrarymember{get_scheduler}{run_loop}% +\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} + +\indexlibrarymember{run}{run_loop}% +\begin{itemdecl} +void run(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\expects +\exposid{state} is either \exposid{starting} or \exposid{finishing}. + +\pnum +\effects +If \exposid{state} is \exposid{starting}, +sets the \exposid{state} to \exposid{running}, +otherwise leaves \exposid{state} unchanged. +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} + +\indexlibrarymember{finish}{run_loop}% +\begin{itemdecl} +void finish(); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\expects +\exposid{state} is either \exposid{starting} or \exposid{running}. + +\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 @\libglobal{with_awaitable_senders}@ { + template + requires (!@\libconcept{same_as}@) + void set_continuation(coroutine_handle h) noexcept; + + coroutine_handle<> @\libmember{continuation}{with_awaitable_senders}@() const noexcept { return @\exposid{continuation}@; } + + coroutine_handle<> @\libmember{unhandled_stopped}{with_awaitable_senders}@() noexcept { + return @\exposid{stopped-handler}@(@\exposid{continuation}@.address()); + } + + template + @\seebelow@ await_transform(Value&& value); - // \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 + 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} - public: - // \ref{exec.run.loop.ctor}, constructor and destructor - run_loop() noexcept; - run_loop(run_loop&&) = delete; - ~run_loop(); +\indexlibrarymember{set_continuation}{with_awaitable_senders}% +\begin{itemdecl} +template + requires (!@\libconcept{same_as}@) +void set_continuation(coroutine_handle h) noexcept; +\end{itemdecl} - // \ref{exec.run.loop.members}, member functions - @\exposid{run-loop-scheduler}@ get_scheduler(); - void run(); - void finish(); +\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} -\rSec3[exec.run.loop.types]{Associated types} - +\indexlibrarymember{await_transform}{with_awaitable_senders}% \begin{itemdecl} -class @\exposid{run-loop-scheduler}@; +template +@\exposid{call-result-t}@ await_transform(Value&& value); \end{itemdecl} +\begin{itemdescr} \pnum -\exposid{run-loop-scheduler} is an unspecified type -that models \libconcept{scheduler}. +\effects +Equivalent to: +\begin{codeblock} +return as_awaitable(std::forward(value), static_cast(*this)); +\end{codeblock} +\end{itemdescr} -\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. +\rSec1[exec.scope]{Execution scope utilities} -\pnum -Two instances of \exposid{run-loop-scheduler} compare equal -if and only if they were obtained from the same \tcode{run_loop} instance. +\rSec2[exec.scope.concepts]{Execution scope concepts} \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} +The \libconcept{scope_token} concept defines the requirements on +a type \tcode{Token} that can be used to create associations +between senders and an async scope. \pnum -\exposid{run-loop-sender} is an exposition-only type -that satisfies \libconcept{sender}. -For any type \tcode{Env}, -\tcode{completion_signatures_of_t<\exposid{run-loop-sender}, Env>} is +%%FIXME: Should test-sender and test-env be declared somewhere as \expos? +Let \placeholder{test-sender} and \placeholder{test-env} +be unspecified types such that +\tcode{\libconcept{sender_in}<\placeholder{test-sender}, \placeholder{test-env}>} +is modeled. + \begin{codeblock} -completion_signatures +namespace std::execution { + template + concept @\deflibconcept{scope_token}@ = + @\libconcept{copyable}@ && + requires(const Token token) { + { token.try_associate() } -> @\libconcept{same_as}@; + { token.disassociate() } noexcept -> @\libconcept{same_as}@; + { token.wrap(declval<@\placeholder{test-sender}@>()) } -> @\libconcept{sender_in}@<@\placeholder{test-env}@>; + }; +} \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. +A type \tcode{Token} models \libconcept{scope_token} only if: -\pnum -Let \exposid{sndr} be an expression of type \exposid{run-loop-sender}, -let \exposid{rcvr} be an expression -such that \tcode{\libconcept{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. +no exceptions are thrown from +copy construction, +move construction, +copy assignment, or +move assignment +of objects of type \tcode{Token}; and + \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. +given an lvalue token of type (possibly const) \tcode{Token}, +for all expressions \tcode{sndr} such that +\tcode{decltype((\linebreak{}sndr))} models \libconcept{sender}: + \begin{itemize} + \item + \tcode{token.wrap(sndr)} is a valid expression, + \item + \tcode{decltype(token.wrap(sndr))} models \libconcept{sender}, and + \item + \tcode{completion_signatures_of_t} + contains the same completion signatures as + \tcode{completion_signatures_of_t} + for all types \tcode{E} such that + \tcode{\libconcept{sender_in}} is modeled. + \end{itemize} \end{itemize} -\begin{itemdecl} -template - struct @\exposid{run-loop-opstate}@; -\end{itemdecl} +\rSec2[exec.counting.scopes]{Counting Scopes} -\pnum -\tcode{\exposid{run-loop-opstate}} -inherits privately and unambiguously from \exposid{run-loop-opstate-base}. +\rSec3[exec.counting.scopes.general]{General} \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: +Scopes of type \tcode{simple_counting_scope} and \tcode{counting_scope} +maintain counts of associations. +Let: \begin{itemize} \item -The object to which \tcode{\exposid{REC}($o$)} refers -remains valid for the lifetime of the object to which $o$ refers. +\tcode{Scope} be either \tcode{simple_counting_scope} or \tcode{counting_scope}, \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: +\tcode{scope} be an object of type \tcode{Scope}, +\item +\tcode{tkn} be an object of type \tcode{Scope::token} +obtained from \tcode{scope.get_token()}, +\item +\tcode{jsndr} be a sender obtained from \tcode{scope.join()}, and +\item +\exposid{op} be an operation state obtained from +connecting \tcode{jsndr} to a receiver. +\end{itemize} +During its lifetime \tcode{scope} goes through different states +which govern what operations are allowed and the result of these operations: + +\begin{itemize} +\item +\exposid{unused}: +a newly constructed object starts in the \exposid{unused} state. + +\item +\exposid{open}: +when \tcode{tkn.try_associate()} is called +while \tcode{scope} is in the \exposid{unused} state, +\tcode{scope} moves to the \exposid{open} state. + +\item +\exposid{open-and-joining}: +when the operation state \exposid{op} is started +while \tcode{scope} is in the \exposid{unused} or \exposid{open} state, +\tcode{scope} moves to the \exposid{open-and-joining} state. + +\item +\exposid{closed}: +when \tcode{scope.close()} is called +while \tcode{scope} is in the \exposid{open} state, +\tcode{scope} moves to the \exposid{closed} state. + +\item +\exposid{unused-and-closed}: +when \tcode{scope.close()} is called +while \tcode{scope} is in the \exposid{unused} state, +\tcode{scope} moves to the \exposid{unused-and-closed} state. + +\item +\exposid{closed-and-joining}: +when \tcode{scope.close()} is called +while \tcode{scope} is in the \exposid{open-and-joining} state or +the operation state \exposid{op} is started +while \tcode{scope} is in +the \exposid{closed} or \exposid{unused-and-closed} state, +\tcode{scope} moves to the \exposid{closed-and-joining} state. + +\item +\exposid{joined}: +when the count of assocations drops to zero +while \tcode{scope} is in +the \exposid{open-and-joining} or \exposid{closed-and-joining} state, +\tcode{scope} moves to the \exposid{joined} state. +\end{itemize} + +\pnum +\recommended +For \tcode{simple_counting_scope} and \tcode{counting_scope}, +implementations should store the state and the count of associations +in a single member of type \tcode{size_t}. + +\pnum +Subclause \ref{exec.counting.scopes} makes use of +the following exposition-only entities: + \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$@))); +struct @\exposid{scope-join-t}@ {}; // \expos + +enum @\exposid{scope-state-type}@ { // \expos + @\exposid{unused}@, // \expos + @\exposid{open}@, // \expos + @\exposid{closed}@, // \expos + @\exposid{open-and-joining}@, // \expos + @\exposid{closed-and-joining}@, // \expos + @\exposid{unused-and-closed}@, // \expos + @\exposid{joined}@, // \expos +}; +\end{codeblock} + +\pnum +The exposition-only class template \exposid{impls-for}\iref{exec.snd.general} +is specialized for \exposid{scope-join-t} as follows: + +\indexlibraryglobal{execution::\exposid{impls-for}<\exposid{scope-join-t}>}% +%%FIXME: The declarations of "receiver" below hide the \libconcept of the same name +\begin{codeblock} +namespace std::execution { + template<> + struct @\exposid{impls-for}@<@\exposid{scope-join-t}@> : @\exposid{default-impls}@ { + template + struct @\exposid{state}@ { // \expos + struct @\exposid{rcvr-t}@ { // \expos + using receiver_concept = receiver_t; + + Rcvr& @\exposid{rcvr}@; // \expos + + void set_value() && noexcept { + execution::set_value(std::move(@\exposid{rcvr}@)); + } + + template + void set_error(E&& e) && noexcept { + execution::set_error(std::move(@\exposid{rcvr}@), std::forward(e)); + } + + void set_stopped() && noexcept { + execution::set_stopped(std::move(@\exposid{rcvr}@)); + } + + decltype(auto) get_env() const noexcept { + return execution::get_env(@\exposid{rcvr}@); + } + }; + + using @\exposid{sched-sender}@ = // \expos + decltype(schedule(get_scheduler(get_env(declval())))); + using @\exposid{op-t}@ = // \expos + connect_result_t<@\exposid{sched-sender}@, @\exposid{rcvr-t}@>; + + Scope* @\exposid{scope}@; // \expos + Rcvr& @\exposid{receiver}@; // \expos + @\exposid{op-t}@ @\exposid{op}@; // \expos + + @\exposid{state}@(Scope* scope, Rcvr& rcvr) // \expos + noexcept(@\exposconcept{nothrow-callable}@) + : @\exposid{scope}@(scope), + @\exposid{receiver}@(rcvr), + @\exposid{op}@(connect(schedule(get_scheduler(get_env(rcvr))), @\exposid{rcvr-t}@(rcvr))) {} + + void @\exposid{complete}@() noexcept { // \expos + @\exposid{start}@(@\exposid{op}@); + } + + void @\exposid{complete-inline}@() noexcept { // \expos + set_value(std::move(@\exposid{receiver}@)); + } + }; + + static constexpr auto @\exposid{get-state}@ = // \expos + [](auto&& sender, Rcvr& receiver) + noexcept(is_nothrow_constructible_v<@\exposid{state}@, @\exposid{data-type}@, Rcvr&>) { + auto[_, self] = sender; + return @\exposid{state}@(self, receiver); + }; + + static constexpr auto start = + [](auto& s, auto&) noexcept { + if (s.@\exposid{scope}@->@\exposid{start-join-sender}@(s)) + s.@\exposid{complete-inline}@(); + }; + }; } \end{codeblock} -\item -The expression \tcode{start($o$)} is equivalent to: + +\rSec3[exec.scope.simple.counting]{Simple Counting Scope} + +\rSec4[exec.scope.simple.counting.general]{General} + +\indexlibraryglobal{execution::simple_counting_scope}% \begin{codeblock} -try { - @$o$@.@\exposid{loop}@->@\exposid{push-back}@(addressof(@$o$@)); -} catch(...) { - set_error(std::move(@\exposid{REC}@(@$o$@)), current_exception()); +namespace std::execution { + class simple_counting_scope { + public: + // \ref{exec.simple.counting.token}, token + struct token; + + static constexpr size_t max_associations = @\UNSP{\impldef{value of \tcode{std::execution::simple_counting_scope::max_associations}}}@; + + // \ref{exec.simple.counting.ctor}, constructor and destructor + simple_counting_scope() noexcept; + simple_counting_scope(simple_counting_scope&&) = delete; + ~simple_counting_scope(); + + // \ref{exec.simple.counting.mem}, members + token get_token() noexcept; + void close() noexcept; + @\libconcept{sender}@ auto join() noexcept; + + private: + size_t @\exposid{count}@; // \expos + @\exposid{scope-state-type}@ @\exposid{state}@; // \expos + + bool @\exposid{try-associate}@() noexcept; // \expos + void @\exposid{disassociate}@() noexcept; // \expos + template + bool @\exposid{start-join-sender}@(State& state) noexcept; // \expos + }; } \end{codeblock} -\end{itemize} -\rSec3[exec.run.loop.ctor]{Constructor and destructor} +\pnum +For purposes of determining the existence of a data race, +\tcode{get_token}, +\tcode{close}, \tcode{join}, +\exposid{try-associate}, +\exposid{disassociate}, and +\exposid{start-join-sender} +behave as atomic operations\iref{intro.multithread}. +These operations on a single object of +type simple_counting_scope appear to occur in a single total order. -\indexlibraryctor{run_loop}% +\rSec4[exec.simple.counting.ctor]{Constructor and Destructor} + +\indexlibraryctor{execution::simple_counting_scope}% \begin{itemdecl} -run_loop() noexcept; +simple_counting_scope() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \ensures -\exposid{count} is \tcode{0} and \exposid{state} is \exposid{starting}. +\exposid{count} is \tcode{0} and \exposid{state} is \exposid{unused}. \end{itemdescr} -\indexlibrarydtor{run_loop}% +\indexlibrarydtor{execution::simple_counting_scope}% \begin{itemdecl} -~run_loop(); +~simple_counting_scope(); \end{itemdecl} \begin{itemdescr} \pnum \effects -If \exposid{count} is not \tcode{0} or if \exposid{state} is \exposid{running}, +If \exposid{state} is not one of +\exposid{joined}, \exposid{unused}, or \exposid{unused-and-closed}, invokes \tcode{terminate}\iref{except.terminate}. Otherwise, has no effects. \end{itemdescr} -\rSec3[exec.run.loop.members]{Member functions} +\rSec4[exec.simple.counting.mem]{Members} +\indexlibrarymember{get_token}{execution::simple_counting_scope}% \begin{itemdecl} -@\exposid{run-loop-opstate-base}@* @\exposid{pop-front}@(); +token get_token() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\returns +An object \tcode{t} of type \tcode{simple_counting_scope::token} such that +\tcode{t.scope == this} is \tcode{true}. +\end{itemdescr} + +\indexlibrarymember{close}{execution::simple_counting_scope}% +\begin{itemdecl} +void close() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \effects -Blocks\iref{defns.block} until one of the following conditions is \tcode{true}: +If \exposid{state} is \begin{itemize} \item -\exposid{count} is \tcode{0} and \exposid{state} is \exposid{finishing}, -in which case \exposid{pop-front} sets \exposid{state} to \exposid{finished} -and returns \tcode{nullptr}; or +\exposid{unused}, then changes \exposid{state} to \exposid{unused-and-closed}; \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. +\exposid{open}, then changes \exposid{state} to \exposid{closed}; +\item +\exposid{open-and-joining}, +then changes \exposid{state} to \exposid{closed-and-joining}; +\item +otherwise, no effects. \end{itemize} + +\pnum +\ensures +Any subsequent call to \tcode{\exposid{try-associate}()} on \tcode{*this} +returns \tcode{false}. \end{itemdescr} +\indexlibrarymember{join}{execution::simple_counting_scope}% \begin{itemdecl} -void @\exposid{push-back}@(@\exposid{run-loop-opstate-base}@* item); +@\libconcept{sender}@ auto join() noexcept; \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}. +\returns +\exposid{make-sender}(\exposid{scope-join-t}(), this). \end{itemdescr} -\indexlibrarymember{get_scheduler}{run_loop}% +\indexlibrarymember{\exposid{try-associate}}{execution::simple_counting_scope}% \begin{itemdecl} -@\exposid{run-loop-scheduler}@ get_scheduler(); +bool @\exposid{try-associate}@() noexcept; \end{itemdecl} \begin{itemdescr} +\pnum +\effects +If \exposid{count} is equal to \tcode{max_associations}, then no effects. +Otherwise, if \exposid{state} is +\begin{itemize} +\item +\exposid{unused}, +then increments \exposid{count} and changes \exposid{state} to \exposid{open}; +\item +\exposid{open} or \exposid{open-and-joining}, +then increments \exposid{count}; +\item +otherwise, no effects. +\end{itemize} + \pnum \returns -An instance of \exposid{run-loop-scheduler} -that can be used to schedule work onto this \tcode{run_loop} instance. +\tcode{true} if \exposid{count} was incremented, \tcode{false} otherwise. \end{itemdescr} -\indexlibrarymember{run}{run_loop}% +\indexlibrarymember{\exposid{disassociate}}{execution::simple_counting_scope}% \begin{itemdecl} -void run(); +void @\exposid{disassociate}@() noexcept; \end{itemdecl} \begin{itemdescr} \pnum \expects -\exposid{state} is either \exposid{starting} or \exposid{finishing}. +\exposid{count} is greater than zero. \pnum \effects -If \exposid{state} is \exposid{starting}, -sets the \exposid{state} to \exposid{running}, -otherwise leaves \exposid{state} unchanged. -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. +Decrements \exposid{count}. +If \exposid{count} is zero after decrementing and +\exposid{state} is \exposid{open-and-joining} or \exposid{closed-and-joining}, +changes \exposid{state} to \exposid{joined} and +calls \exposid{complete}() on all objects registered with \tcode{*this}. +\begin{note} +Calling \tcode{\exposid{complete}()} on any registered object +can cause \tcode{*this} to be destroyed. +\end{note} \end{itemdescr} -\indexlibrarymember{finish}{run_loop}% +\indexlibrarymember{\exposid{start-join-sender}}{execution::simple_counting_scope}% \begin{itemdecl} -void finish(); +template + bool @\exposid{start-join-sender}@(State& st) noexcept; \end{itemdecl} \begin{itemdescr} -\pnum -\expects -\exposid{state} is either \exposid{starting} or \exposid{running}. - \pnum \effects -Changes \exposid{state} to \exposid{finishing}. - -\pnum -\sync -\tcode{finish} synchronizes with the \exposid{pop-front} operation -that returns \tcode{nullptr}. +If \exposid{state} is +\begin{itemize} +\item +\exposid{unused}, \exposid{unused-and-closed}, or \exposid{joined}, then +changes \exposid{state} to \exposid{joined} and returns \tcode{true}; +\item +\exposid{open} or \exposid{open-and-joining}, then +changes \exposid{state} to \exposid{open-and-joining}, +registers \tcode{st} with \tcode{*this} and returns \tcode{false}; +\item +\exposid{closed} or \exposid{closed-and-joining}, then +changes \exposid{state} to \exposid{closed-and-joining}, +registers \tcode{st} with \tcode{*this} and returns \tcode{false}. +\end{itemize} \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: +\rSec4[exec.simple.counting.token]{Token} +\indexlibraryglobal{execution::simple_counting_scope::token}% \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 + struct simple_counting_scope::token { + template<@\libconcept{sender}@ Sender> + Sender&& wrap(Sender&& snd) const noexcept; + bool try_associate() const noexcept; + void disassociate() const noexcept; - 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(); + private: + simple_counting_scope* @\exposid{scope}@; // \expos }; } \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} +\indexlibrarymember{wrap}{execution::simple_counting_scope::token}% +\begin{itemdecl} +template<@\libconcept{sender}@ Sender> + Sender&& wrap(Sender&& snd) const noexcept; +\end{itemdecl} +\begin{itemdescr} \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} +\returns +\tcode{std::forward(snd)}. +\end{itemdescr} +\indexlibrarymember{try_associate}{execution::simple_counting_scope::token}% \begin{itemdecl} -@\exposid{sender-awaitable}@(Sndr&& sndr, Promise& p); +bool try_associate() const noexcept; \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} +Equivalent to: \tcode{return \exposid{scope}->\exposid{try-associate}();} \end{itemdescr} +\indexlibrarymember{disassociate}{execution::simple_counting_scope::token}% \begin{itemdecl} -@\exposid{value-type}@ await_resume(); +void disassociate() const noexcept; \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} +Equivalent to \tcode{\exposid{scope}->\exposid{disassociate}()}. \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} +\rSec3[exec.scope.counting]{Counting Scope} -\rSec2[exec.with.awaitable.senders]{\tcode{execution::with_awaitable_senders}} +\indexlibraryglobal{execution::counting_scope}% +\indexlibrarymember{token}{execution::counting_scope}% +\begin{codeblock} +namespace std::execution { + class counting_scope { + public: + struct token { + template<@\libconcept{sender}@ Sender> + @\libconcept{sender}@ auto wrap(Sender&& snd) const noexcept(@\seebelow@); + bool try_associate() const noexcept; + void disassociate() const noexcept; -\pnum -\tcode{with_awaitable_senders}, -when used as the base class of a coroutine promise type, -makes senders awaitable in that coroutine type. + private: + counting_scope* @\exposid{scope}@; // \expos + }; -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} + static constexpr size_t max_associations = @\UNSP{\impldef{value of \tcode{std::execution::counting_scope::max_associations}}}@; -\begin{codeblock} -namespace std::execution { - template<@\exposconcept{class-type}@ Promise> - struct @\libglobal{with_awaitable_senders}@ { - template - requires (!@\libconcept{same_as}@) - void set_continuation(coroutine_handle h) noexcept; + counting_scope() noexcept; + counting_scope(counting_scope&&) = delete; + ~counting_scope(); - coroutine_handle<> @\libmember{continuation}{with_awaitable_senders}@() const noexcept { return @\exposid{continuation}@; } + token get_token() noexcept; + void close() noexcept; + @\libconcept{sender}@ auto join() noexcept; + void request_stop() noexcept; - coroutine_handle<> @\libmember{unhandled_stopped}{with_awaitable_senders}@() noexcept { - return @\exposid{stopped-handler}@(@\exposid{continuation}@.address()); - } + private: + size_t @\exposid{count}@; // \expos + @\exposid{scope-state-type}@ @\exposid{state}@; // \expos + inplace_stop_source @\exposid{s_source}@; // \expos - template - @\seebelow@ await_transform(Value&& value); + bool @\exposid{try-associate}@() noexcept; // \expos + void @\exposid{disassociate}@() noexcept; // \expos - 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}@; - }; + template + bool @\exposid{start-join-sender}@(State& state) noexcept; // \expos + }; } \end{codeblock} -\indexlibrarymember{set_continuation}{with_awaitable_senders}% +\pnum +\tcode{counting_scope} differs from \tcode{simple_counting_scope} by +adding support for cancellation. +Unless specified below, the semantics of members of \tcode{counting_scope} +are the same as the corresponding members of \tcode{simple_counting_scope}. + +\indexlibrarymember{get_token}{execution::counting_scope}% \begin{itemdecl} -template - requires (!@\libconcept{same_as}@) -void set_continuation(coroutine_handle h) noexcept; +token get_token() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\returns +An object \tcode{t} of type \tcode{counting_scope::token} such that +\tcode{t.\exposid{scope} == this} is \tcode{true}. +\end{itemdescr} + +\indexlibrarymember{request_stop}{execution::counting_scope}% +\begin{itemdecl} +void request_stop() 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} +Equivalent to \tcode{\exposid{s_source}.request_stop()}. + +\pnum +\remarks +Calls to \tcode{request_stop} do not introduce data races. \end{itemdescr} -\indexlibrarymember{await_transform}{with_awaitable_senders}% +\indexlibrarymember{wrap}{execution::counting_scope::token}% \begin{itemdecl} -template -@\exposid{call-result-t}@ await_transform(Value&& value); +template<@\libconcept{sender}@ Sender> + @\libconcept{sender}@ auto counting_scope::token::wrap(Sender&& snd) const + noexcept(is_nothrow_constructible_v, Sender>); \end{itemdecl} \begin{itemdescr} @@ -5669,6 +7142,7 @@ \effects Equivalent to: \begin{codeblock} -return as_awaitable(std::forward(value), static_cast(*this)); +return @\exposid{stop-when}@(std::forward(snd), + \exposid{scope}->\exposid{s_source}.get_token()); \end{codeblock} \end{itemdescr} diff --git a/source/support.tex b/source/support.tex index 95d674aafb..c2b3feccbe 100644 --- a/source/support.tex +++ b/source/support.tex @@ -645,6 +645,7 @@ #define @\defnlibxname{cpp_lib_contracts}@ 202502L // freestanding, also in \libheader{contracts} #define @\defnlibxname{cpp_lib_copyable_function}@ 202306L // also in \libheader{functional} #define @\defnlibxname{cpp_lib_coroutine}@ 201902L // freestanding, also in \libheader{coroutine} +#define @\defnlibxname{cpp_lib_counting_scope}@ 202506L // also in \libheader{execution} #define @\defnlibxname{cpp_lib_debugging}@ 202403L // freestanding, also in \libheader{debugging} #define @\defnlibxname{cpp_lib_destroying_delete}@ 201806L // freestanding, also in \libheader{new} #define @\defnlibxname{cpp_lib_enable_shared_from_this}@ 201603L // also in \libheader{memory}