33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.12 Sender adaptors [exec.adapt]

33.9.12.16 std​::​execution​::​associate [exec.associate]

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.
Let associate-data be the following exposition-only class template:
namespace std::execution { template<scope_token Token, sender Sender> struct associate-data { // exposition only using wrap-sender = // exposition only remove_cvref_t<decltype(declval<Token&>().wrap(declval<Sender>()))>; explicit associate-data(Token t, Sender&& s) : sndr(t.wrap(std::forward<Sender>(s))), token(t) { if (!token.try_associate()) sndr.reset(); } associate-data(const associate-data& other) noexcept(is_nothrow_copy_constructible_v<wrap-sender> && noexcept(other.token.try_associate())); associate-data(associate-data&& other) noexcept(is_nothrow_move_constructible_v<wrap-sender>); ~associate-data(); optional<pair<Token, wrap-sender>> release() && noexcept(is_nothrow_move_constructible_v<wrap-sender>); private: optional<wrap-sender> sndr; // exposition only Token token; // exposition only }; template<scope_token Token, sender Sender> associate-data(Token, Sender&&) -> associate-data<Token, Sender>; }
For an associate-data object a, a.sndr.has_value() is true if and only if an association was successfully made and is owned by a.
associate-data(const associate-data& other) noexcept(is_nothrow_copy_constructible_v<wrap-sender> && noexcept(other.token.try_associate()));
Constraints: copy_constructible<wrap-sender> is true.
Effects: Value-initializes sndr and initializes token with other.token.
If other.sndr.has_value() is false, no further effects; otherwise, calls token.try_associate() and, if that returns true, calls sndr.emplace(*other.sndr) and, if that exits with an exception, calls token.disassociate() before propagating the exception.
associate-data(associate-data&& other) noexcept(is_nothrow_move_constructible_v<wrap-sender>);
Effects: Initializes sndr with std​::​move(other.sndr) and initializes token with std​::​move(other.token) and then calls other.sndr.reset().
~associate-data();
Effects: If sndr.has_value() returns false then no effect; otherwise, invokes sndr.reset() before invoking token.disassociate().
optional<pair<Token, wrap-sender>> release() && noexcept(is_nothrow_move_constructible_v<wrap-sender>);
Effects: If sndr.has_value() returns false then returns an optional that does not contain a value; otherwise returns an optional containing a value of type pair<Token, wrap-sender> as if by: return optional(pair(token, std::move(*sndr)));
Postconditions: sndr does not contain a value.
The name associate denotes a pipeable sender adaptor object.
For subexpressions sndr and token:
  • If decltype((sndr)) does not satisfy sender, or remove_cvref_t<decltype((token))> does not satisfy scope_token, then associate(sndr, token) is ill-formed.
  • Otherwise, the expression associate(sndr, token) is expression-equivalent to: transform_sender(get-domain-early(sndr), make-sender(associate, associate-data(token, sndr))) except that sndr is evaluated only once.
The exposition-only class template impls-for ([exec.snd.general]) is specialized for associate_t as follows: namespace std::execution { template<> struct impls-for<associate_t> : default-impls { static constexpr auto get-state = see below; // exposition only static constexpr auto start = see below; // exposition only template<class Sndr, class... Env> static consteval void check-types() { // exposition only using associate_data_t = remove_cvref_t<data-type<Sndr>>; using child_type_t = typename associate_data_t::wrap-sender; (void)get_completion_signatures<child_type_t, FWD-ENV-T(Env)...>(); } }; }
The member impls-for<associate_t>​::​get-state is initialized with a callable object equivalent to the following lambda:
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below) { auto [_, data] = std::forward<Sndr>(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<wrap_sender, Rcvr>; struct op_state { bool associated = false; // exposition only union { Rcvr* rcvr; // exposition only struct { scope_tkn token; // exposition only op_t op; // exposition only } assoc; // exposition only }; explicit op_state(Rcvr& r) noexcept : rcvr(addressof(r)) {} explicit op_state(scope_tkn tkn, wrap_sender&& sndr, Rcvr& r) try : associated(true), assoc(tkn, connect(std::move(sndr), std::move(r))) { } catch (...) { tkn.disassociate(); throw; } op_state(op_state&&) = delete; ~op_state() { if (associated) { assoc.op.~op_t(); assoc.token.disassociate(); assoc.token.~scope_tkn(); } } void run() noexcept { // exposition only if (associated) start(assoc.op); else set_stopped(std::move(*rcvr)); } }; if (dataParts) return op_state{std::move(dataParts->first), std::move(dataParts->second), rcvr}; else return op_state{rcvr}; }
The expression in the noexcept clause of impls-for<associate_t>​::​get-state is is_nothrow_constructible_v<remove_cvref_t<Sndr>, Sndr> && is_nothrow_move_constructible_v<wrap-sender> && nothrow-callable<connect_t, wrap-sender, Rcvr> where wrap-sender is the type remove_cvref_t<data-type<Sndr>>​::​wrap-sender.
The member impls-for<associate_t>​::​start is initialized with a callable object equivalent to the following lambda: [](auto& state, auto&) noexcept -> void { state.run(); }
The evaluation of associate(sndr, token) may cause side effects observable via token's associated async scope object.