12 KiB
[exec.spawn.future]
33 Execution control library [exec]
33.9 Senders [exec.snd]
33.9.12 Sender adaptors [exec.adapt]
33.9.12.18 execution::spawn_future [exec.spawn.future]
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 withset_stopped if the input sender was not started.
The name spawn_future denotes a customization point object.
For subexpressions sndr, token, and env,
let Sndr be decltype((sndr)),
let Token be remove_cvref_t<decltype((token))>, and
let Env be remove_cvref_t<decltype((env))>.
If any ofsender,scope_token, orqueryable are not satisfied, the expression spawn_future(sndr, token, env) is ill-formed.
Let spawn-future-state-base be the exposition-only class template:
namespace std::execution {templatestruct spawn-future-state-base; // exposition onlytemplate<class... Sigs>struct spawn-future-state-base<completion_signatures<Sigs...>> { // exposition onlyusing variant-t = see below; // exposition only**variant-t result; // exposition onlyvirtual void complete() noexcept = 0; // exposition only};}
Let Sigs be the pack of arguments to the completion_signatures specialization provided as a parameter to the spawn-future-state-base class template.
Let as-tuple be an alias template that transforms a completion signature Tag(Args...) into the tuple specialization decayed-tuple<Tag, Args...>.
-
If is_nothrow_constructible_v<decay_t, Arg> is true for every type Arg in every parameter pack Args in every completion signature Tag(Args...) in Sigs thenvariant-t denotes the typevariant<monostate, tuple<set_stopped_t>, as-tuple...>, except with duplicate types removed.
-
Otherwisevariant-t denotes the typevariant<monostate, tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple...>, except with duplicate types removed.
Let spawn-future-receiver be the exposition-only class template:
namespace std::execution {templatestruct spawn-future-receiver { // exposition onlyusing receiver_concept = receiver_t; spawn-future-state-base* state; // exposition onlytemplate<class... T>void set_value(T&&... t) && noexcept {set-complete<set_value_t>(std::forward(t)...); }templatevoid set_error(E&& e) && noexcept {set-complete<set_error_t>(std::forward(e)); }void set_stopped() && noexcept {set-complete<set_stopped_t>(); }private:template<class CPO, class... T>void set-complete(T&&... t) noexcept { // exposition onlyconstexpr bool nothrow = (is_nothrow_constructible_v<decay_t, T> && ...); try {state->result.template emplace<decayed-tuple<CPO, T...>>(CPO{}, std::forward(t)...); }catch (...) {if constexpr (!nothrow) {using tuple_t = decayed-tuple<set_error_t, exception_ptr>; state->result.template emplace<tuple_t>(set_error_t{}, current_exception()); }}state->complete(); }};}
Let ssource-t be an unspecified type that models stoppable-source and let ssource be an lvalue of type ssource-t.
Let stoken-t be decltype(ssource.get_token()).
Let future-spawned-sender be the alias template:
template<sender Sender, class Env>using future-spawned-sender = // exposition onlydecltype(write_env(stop-when(declval(), declval<stoken-t>()), declval()));
Let spawn-future-state be the exposition-only class template:
namespace std::execution {template<class Alloc, scope_token Token, sender Sender, class Env>struct spawn-future-state // exposition only: spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>>> {using sigs-t = // exposition only completion_signatures_of_t<future-spawned-sender<Sender, Env>>; using receiver-t = // exposition only**spawn-future-receiver<sigs-t>; using op-t = // exposition only connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>; spawn-future-state(Alloc alloc, Sender&& sndr, Token token, Env env) // exposition only: alloc(std::move(alloc)), op(connect( write_env(stop-when(std::forward(sndr), ssource.get_token()), std::move(env)), receiver-t(this))), token(std::move(token)), associated(token.try_associate()) {if (associated) start(op); else set_stopped(receiver-t(this)); }void complete() noexcept override; // exposition onlyvoid consume(receiver auto& rcvr) noexcept; // exposition onlyvoid abandon() noexcept; // exposition onlyprivate:using alloc-t = // exposition onlytypename allocator_traits::template rebind_alloc<spawn-future-state>; alloc-t alloc; // exposition only**ssource-t ssource; // exposition only**op-t op; // exposition only Token token; // exposition onlybool associated; // exposition onlyvoid destroy() noexcept; // exposition only};}
For purposes of determining the existence of a data race,complete, consume, and abandon behave as atomic operations ([intro.multithread]).
These operations on a single object of a type that is a specialization of spawn-future-state appear to occur in a single total order.
void complete() noexcept;
Effects:
No effects if this invocation of complete happens before an invocation of consume or abandon on *this;
otherwise, if an invocation of consume on *this happens before this invocation of complete then there is a receiver, rcvr, registered and that receiver is completed as if by consume(rcvr);
otherwise,destroy is invoked.
void consume([receiver](exec.recv.concepts#concept:receiver "33.7.1 Receiver concepts [exec.recv.concepts]") auto& rcvr) noexcept;
Effects:
If this invocation of consume happens before an invocation of complete on this thenrcvr is registered to be completed whencomplete* is subsequently invoked on *this;
otherwise,rcvr is completed as if by:std::move(this->result).visit([&rcvr](auto&& tuple) noexcept {if constexpr (<remove_reference_t<decltype(tuple)>, monostate>) { apply([&rcvr](auto cpo, auto&&... vals) { cpo(std::move(rcvr), std::move(vals)...); }, std::move(tuple)); }});
void abandon() noexcept;
Effects:
If this invocation of abandon happens before an invocation of complete on *this then equivalent to:ssource.request_stop();
otherwise,destroy is invoked.
void destroy() noexcept;
Effects: Equivalent to:auto token = std::move(this->token);bool associated = this->associated;
{auto alloc = std::move(this->alloc);
allocator_traits<alloc-t>::destroy(alloc, this); allocator_traits<alloc-t>::deallocate(alloc, this, 1);}if (associated) token.disassociate();
The exposition-only class template impls-for ([exec.snd.expos]) is specialized for spawn_future_t as follows:
namespace std::execution {template<>struct impls-for<spawn_future_t> : default-impls {static constexpr auto start = see below; // exposition only};}
The member impls-for<spawn_future_t>::start is initialized with a callable object equivalent to the following lambda:[](auto& state, auto& rcvr) noexcept -> void { state->consume(rcvr);}
For the expression spawn_future(sndr, token, env) let new_sender be the expression token.wrap(sndr) and let alloc and senv be defined as follows:
if the expression get_allocator(env) is well-formed, thenalloc is the result of get_allocator(env) andsenv is the expression env;
otherwise, if the expression get_allocator(get_env(new_sender)) is well-formed, thenalloc is the result of get_allocator(get_env(new_sender)) andsenv is the expressionJOIN-ENV(prop(get_allocator, alloc), env);
otherwise,alloc is allocator() andsenv is the expression env.
The expression spawn_future(sndr, token, env) has the following effects:
-
Uses alloc to allocate and construct an object s of a type that is a specialization of spawn-future-state from alloc, token.wrap(sndr), token, and senv. If an exception is thrown then any constructed objects are destroyed and any allocated memory is deallocated.
-
Constructs an object u of a type that is a specialization of unique_ptr such that:
u.get() is equal to the address of s, and
u.get_deleter()(u.release()) is equivalent to u.release()->abandon().
The expression spawn_future(sndr, token) is expression-equivalent tospawn_future(sndr, token, execution::env<>()).