33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.12 Sender adaptors [exec.adapt]

33.9.12.16 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>()))>; using assoc-t = decltype(declval<Token&>().try_associate()); // exposition only using sender-ref = // exposition only unique_ptr<wrap-sender, decltype([](auto* p) noexcept { destroy_at(p); })>; explicit associate-data(Token t, Sender&& s) : sndr(t.wrap(std::forward<Sender>(s))), assoc([&] { sender-ref guard{addressof(sndr)}; auto assoc = t.try_associate(); if (assoc) { guard.release(); } return assoc; }()) {} associate-data(const associate-data& other) noexcept(is_nothrow_copy_constructible_v<wrap-sender> && noexcept(other.assoc.try_associate())); associate-data(associate-data&& other) noexcept(is_nothrow_move_constructible_v<wrap-sender>) : associate-data(std::move(other).release()) {} ~associate-data(); pair<assoc-t, sender-ref> release() && noexcept; private: associate-data(pair<assoc-t, sender-ref> parts); // exposition only union { wrap-sender sndr; // exposition only }; assoc-t assoc; // exposition only }; template<scope_token Token, sender Sender> associate-data(Token, Sender&&) -> associate-data<Token, Sender>; }
For an associate-data object a, bool(a.assoc) 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.assoc.try_associate()));
Constraints: wrap-sender models copy_constructible.
Effects: Initializes assoc with other.assoc.try_associate().
If bool(assoc) is true, initializes sndr with other.sndr.
associate-data(pair<assoc-t, sender-ref> parts);
Effects: Initializes assoc with std​::​move(parts.first).
If bool(assoc) is true, initializes sndr with std​::​move(*parts.second).
~associate-data();
Effects: If bool(assoc) is true, destroys sndr.
pair<assoc-t, sender-ref> release() && noexcept;
Effects: Constructs an object u of type sender-ref that is initialized with addressof(sndr) if bool(assoc) is true and with nullptr otherwise, then returns pair{std​::​move(assoc), std​::​​move(u)}.
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.expos]) 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 = 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); using associate_data_t = remove_cvref_t<decltype(data)>; using assoc_t = associate_data_t::assoc-t; using sender_ref_t = associate_data_t::sender-ref; using op_t = connect_result_t<typename sender_ref_t::element_type, Rcvr>; struct op_state { assoc_t assoc; // exposition only union { Rcvr* rcvr; // exposition only op_t op; // exposition only }; explicit op_state(pair<assoc_t, sender_ref_t> parts, Rcvr& r) : assoc(std::move(parts.first)) { if (assoc) { ::new (voidify(op)) op_t(connect(std::move(*parts.second), std::move(r))); } else { rcvr = addressof(r); } } explicit op_state(associate_data_t&& ad, Rcvr& r) : op_state(std::move(ad).release(), r) {} explicit op_state(const associate_data_t& ad, Rcvr& r) requires copy_constructible<associate_data_t> : op_state(associate_data_t(ad).release(), r) {} op_state(op_state&&) = delete; ~op_state() { if (assoc) { op.~op_t(); } } void run() noexcept { // exposition only if (assoc) { start(op); } else { set_stopped(std::move(*rcvr)); } } }; return op_state{std::forward_like<Sndr>(data), rcvr}; }
The expression in the noexcept clause of impls-for<associate_t>​::​get-state is (is_same_v<Sndr, remove_cvref_t<Sndr>> || is_nothrow_constructible_v<remove_cvref_t<Sndr>, Sndr>) && 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.