#
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.
2
#
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>; }
3
#
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();
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.

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant