diff --git a/3815.md b/3815.md index 8304ad4..8c12bd1 100644 --- a/3815.md +++ b/3815.md @@ -1,6 +1,6 @@ --- title: Add `scope_association` concept to P3149 -document: P3815R0 +document: P3815R1 date: today audience: - LEWG Library Evolution Working Group @@ -14,6 +14,9 @@ toc: true Changes ======= +## R1 +- Incorporate feedback from Ville to strengthen background and motivation section. + ## R0 - First revision @@ -36,12 +39,19 @@ concept scope_token = }; ``` -During the development of these sample implementations, it was observed that reintroducing the `scope_association` -concept from [@P3149R7] would yield several benefits: +In [@P3149R7], we also introduced the `scope_association` concept as an RAII handle for managing scope associations. +However, this facility was removed in [@P3149R9] due to concerns about the unconventional behavior of its copy +constructor. As there was no implementation experience with `scope_association` at the time of R9's approval, the +implications of this removal was not fully understood. + +Later implementation efforts demonstrated that reintroducing the `scope_association` concept from [@P3149R7] would yield +several benefits for sender/receiver library implementors: -- reduced binary size for both scopes and basis operations, +- reduced binary size for both scopes and basis operations -- i.e., the use of a boolean is no longer needed to track + successful associations in the op states of senders, - fewer move/copy operations, -- and a simplified implementation model that in turn facilitates easier customization. +- and a simplified implementation model that in turn facilitates easier customization -- i.e., library implementors do + not need to manually track successful associations to correctly handle disassociate calls. These improvements can be achieved without any impact on the user-facing APIs proposed in R11. @@ -63,7 +73,7 @@ struct @_spawn-state_@ : @_spawn-state-base_@ { @_spawn-receiver_@{this})), @_token_@(std::move(t)) {} - void run() noexcept { + void run() { if (@_token_@.try_associate()) @_op_@.start(); else @@ -109,6 +119,11 @@ struct @_spawn-state_@ : @_spawn-state-base_@ { ``` :::: +In the above example, a key difference when implementing _`spawn-state`_ with a `scope_association` concept is that +`t.try_associate()` is invoked in the constructor rather than in `run()`. This change simplifies exception handling +within _`spawn-state`_: if an exception occurs, only the allocation needs to be explicitly cleaned up. By contrast, if +`t.try_associate()` throws from `run()`, both destruction and deallocation needs to be explicitly handled. + ## execution::associate :::cmptable ### Before @@ -237,7 +252,7 @@ template ``` A type that models `scope_association` is an RAII handle that represents a possible association between a sender and an async scope. If the scope association contextually converts to true then the object is “engaged” and represents an -association; otherwise, the object is “disengaged” and represents the lack of an association. Scope associations are +association; otherwise, the object is "disengaged" and represents the lack of an association. Scope associations are movable and not copyable, and expose a `try_associate` member function with semantics identical to the `try_associate` member function on a type that models `scope_token`. @@ -337,12 +352,18 @@ struct @_associate-data_@ { // @_expositio @[unique_ptr<_wrap-sender_,\ decltype(\[\](auto*\ p)\ noexcept\ {\ destroy_at(p);\ })>;]{.add}@ explicit @_associate-data_@(Token t, Sender&& s) - : @_sndr_@(t.wrap(std::forward(s)))@[,]{.rm}[\ {]{.add}@ + : @_sndr_@(t.wrap(std::forward(s))), @[_token_(t)\ {]{.rm}@ - @[_sender-ref_\ guard{addressof(_sndr_)};]{.add}@ - - if (@[!_token_]{.rm}[_assoc_\ =\ t]{.add}@.try_associate()) - @[_sndr_.reset()]{.rm}[(void)guard.release()]{.add}@; + @[_assoc_(\[&\]\ {]{.add}@ + @[_sender-ref_\ guard{addressof(sndr)};]{.add}@ + @[auto\ assoc\ =\ t.try_associate();]{.add}@ + @[if\ (assoc)]{.add}@ + @[guard.release();]{.add}@ + @[return\ assoc;]{.add}@ + @[}())\ {]{.add}@ + + @[if\ (!_token_.try_associate())]{.rm}@ + @[_sndr_.reset();]{.rm}@ } @_associate-data_@(const @_associate-data_@& other) @@ -350,7 +371,8 @@ struct @_associate-data_@ { // @_expositio noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); @_associate-data_@(@_associate-data_@&& other) - noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); + noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>)@[;]{.rm}@ + @[\ \ :\ _associate-data_(std::move(other).release())\ {}]{.add}@ ~@_associate-data_@(); @@ -362,12 +384,12 @@ private: @[optional<_wrap-sender_>\ _sndr_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ @[Token\ _token_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ - @[_associate-data_(pair<_assoc-t_,\ _scope-ref_>\ parts);]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[_associate-data_(pair<_assoc-t_,\ _sender-ref_>\ parts);]{.add}@ @[//\ _exposition\ only_]{.add}@ - @[_assoc-t_\ _assoc_;]{.add}@ @[//\ _exposition\ only_]{.add}@ @[union\ {]{.add}@ @[_wrap-sender_\ _sndr_;]{.add}@ @[//\ _exposition\ only_]{.add}@ @[};]{.add}@ + @[_assoc-t_\ _assoc_;]{.add}@ @[//\ _exposition\ only_]{.add}@ }; template @@ -376,8 +398,8 @@ template } ``` -[3]{.pnum} For an _`associate-data`_ object `a`, [`a.@_sndr_@.has_value()` is]{.rm} [`a.@_assoc_@` contextually converts -to]{.add} `true` if and only if an association was successfully made and is owned by `a`. +[3]{.pnum} For an _`associate-data`_ object `a`, [`a.@_sndr_@.has_value()` is]{.rm} [`bool(a.@_assoc_@)` is]{.add} +`true` if and only if an association was successfully made and is owned by `a`. ``` @_associate-data_@(const @_associate-data_@& other) @@ -385,47 +407,43 @@ to]{.add} `true` if and only if an association was successfully made and is owne noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); ``` -[4]{.pnum} _Constraints:_ `copy_constructible<@_wrap-sender_@>` is `true`. +[4]{.pnum} _Constraints:_ [_`wrap-sender`_ models]{.add} `copy_constructible@[<_wrap-sender_>]{.rm}@`[ is `true`]{.rm}. [5]{.pnum} _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.]{.rm} [Initializes _`assoc`_ with -`other.@_assoc_@.try_associate()`. If _`assoc`_ contextually converts to `false`, no further effects; otherwise, -initializes _`sndr`_ with `other.@_sndr_@`.]{.add} +`other.@_assoc_@.try_associate()`. If `bool(@_assoc_@)` is `true` initializes _`sndr`_ with `other.@_sndr_@`.]{.add} ``` -@_associate-data_@(@_associate-data_@&& other) - noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); +@[_associate-data_(_associate-data_&&\ other)]{.rm}@ + @[noexcept(is_nothrow_move_constructible_v\<_wrap-sender_\>);]{.rm}@ ``` -[6]{.pnum} _Effects:_ [Initializes _`sndr`_ with `std::move(other.@_sndr_@)` and initializes _`token`_ with -`std::move(other.@_token_@)` and then calls `other.@_sndr_@.reset()`.]{.rm} [Equivalent to -`@_associate-data_@(std::move(other).release())`.]{.add} +[6]{.pnum} [_Effects:_ Initializes _`sndr`_ with `std::move(other.@_sndr_@)` and initializes _`token`_ with +`std::move(other.@_token_@)` and then calls `other.@_sndr_@.reset()`.]{.rm} [`@_associate-data_@(pair<@_assoc-t_@, @_sender-ref_@> parts);`]{.add} -[[7]{.pnum} _Effects:_ Initializes _`assoc`_ with `std::move(parts.first)`. If _`assoc`_ contextually converts to -`false`, no further effects; otherwise, initializes _`sndr`_ with `std::move(*parts.second)`.]{.add} +[[6]{.pnum} _Effects:_ Initializes _`assoc`_ with `std::move(parts.first)`. If `bool(@_assoc_@)` is `true` initializes +_`sndr`_ with `std::move(*parts.second)`.]{.add} `~@_associate-data_@();` -[8]{.pnum} _Effects:_ [If `@_sndr_@.has_value()` returns `false` then no effect; otherwise, invokes `@_sndr_@.reset()` -before invoking `@_token_@.disassociate()`.]{.rm} [If _`assoc`_ contextually converts to `false` then no effect; -otherwise, destroys _`sndr`_.]{.add} +[7]{.pnum} _Effects:_ [If `@_sndr_@.has_value()` returns `false` then no effect; otherwise, invokes `@_sndr_@.reset()` +before invoking `@_token_@.disassociate()`.]{.rm} [If `bool(@_assoc_@)` is `true` destroys _`sndr`_.]{.add} [`optional>`]{.rm}\ -[`pair<@_assoc-t_@, @_scope-ref_@>`]{.add}\ +[`pair<@_assoc-t_@, @_sender-ref_@>`]{.add}\ `@\ \ @release() && noexcept@[(is_nothrow_move_constructible_v<_wrap-sender_>)]{.rm}@;` -[9]{.pnum} _Effects:_ [If `@_sndr_@.has_value()` returns `false` then returns an `optional` that does not contain a +[8]{.pnum} _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` as if by:]{.rm} [`return optional(pair(@_token_@, std::move(*@_sndr_@)));`]{.rm} -[Constructs an object `u` of type _`scope-ref`_ that is initialized with `nullptr` if _`assoc`_ -contextually converts to `false` and with `addressof(@_sndr_@)` otherwise, then returns -`pair{std::move(@_assoc_@), std::move(u)}`.]{.add} +[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)}`.]{.add} [[9]{.pnum} _Postconditions:_ _`sndr`_ does not contain a value.]{.rm} @@ -447,8 +465,8 @@ the following lambda: @[using\ wrap_sender\ =\ decltype(dataParts->second);]{.rm}@ @[using\ associate_data_t\ =\ remove_cvref_t;]{.add}@ - @[using\ assoc_t\ =\ typename\ associate_data_t::_assoc-t_;]{.add}@ - @[using\ sender_ref_t\ =\ typename\ associate_data_t::_sender-ref_;]{.add}@ + @[using\ assoc_t\ =\ associate_data_t::_assoc-t_;]{.add}@ + @[using\ sender_ref_t\ =\ associate_data_t::_sender-ref_;]{.add}@ using op_t = connect_result_t<@[wrap_sender]{.rm}[typename\ sender_ref_t::element_type]{.add}@, Rcvr>; @@ -477,8 +495,8 @@ the following lambda: @[explicit\ op_state(pair\ parts,\ Rcvr&\ r)]{.add}@ @[:\ _assoc_(std::move(parts.first))\ {]{.add}@ @[if\ (_assoc_)]{.add}@ - @[::new\ (_voidify_(_op_))\ op_t{]{.add}@ - @[connect(std::move(*parts.second),\ std::move(r))};]{.add}@ + @[::new\ (_voidify_(_op_))\ op_t(]{.add}@ + @[connect(std::move(*parts.second),\ std::move(r)));]{.add}@ @[else]{.add}@ @[rcvr\ =\ addressof(r);]{.add}@ @[}]{.add}@ @@ -511,7 +529,7 @@ the following lambda: @[if\ (dataParts)]{.rm}@ @[return\ op_state{std::move(dataParts->first),\ std::move(dataParts->second),\ rcvr};]{.rm}@ @[else]{.rm}@ - @[\ \ ]{.rm}@return op_state{@[std::forward_like(data),\ ]{.add}@rcvr}; + @return op_state{@[std::forward_like\(data),\ ]{.add}@rcvr}; } ``` @@ -519,7 +537,8 @@ the following lambda: `@\ \ [is_nothrow_constructible_v,\ Sndr>\ &&]{.rm}@`\ `@\ \ [is_nothrow_move_constructible_v<_wrap-sender_>\ &&]{.rm}@`\ -`@\ \ [(is_rvalue_reference_v\ ||\ is_nothrow_constructible_v,\ Sndr>)\ &&]{.add}@`\ +`@\ \ [(is_same_v>\ ||]{.add}@`\ +`@\ \ [\ is_nothrow_constructible_v\,\ Sndr\>)\ &&]{.add}@`\ `@\ \ _nothrow-callable_@` where _`wrap-sender`_ is the type `remove_cvref_t<@_data-type_@>::@_wrap-sender_@.` @@ -532,7 +551,7 @@ To the subsection [exec.spawn.future]{.sref}, make the following changes: ``` namespace std::execution { - template + template struct @_spawn-future-state_@ // @_exposition only_@ : @_spawn-future-state-base_@>> { using @_sigs-t_@ = // @_exposition only_@ @@ -587,10 +606,10 @@ namespace std::execution { @[bool\ associated\ =\ this->_associated_;]{.rm}@ @[auto\ assoc\ =\ std::move(this->_assoc_);]{.add}@ @[{]{.rm}@ -@[\ \ ]{.rm}@auto alloc = std::move(this->@_alloc_@); + auto alloc = std::move(this->@_alloc_@); -@[\ \ ]{.rm}@allocator_traits::destroy(alloc, this); -@[\ \ ]{.rm}@allocator_traits::deallocate(alloc, this, 1); + allocator_traits<@_alloc-t_@>::destroy(alloc, this); + allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1); @[}]{.rm}@ @[if\ (associated)]{.rm}@ @@ -605,7 +624,7 @@ To the subsection [exec.spawn]{.sref}, make the following changes: ``` namespace std::execution { - template + template struct @_spawn-state_@ : @_spawn-state-base_@ { // @_exposition only_@ using @_op-t_@ = connect_result_t; // @_exposition only_@ @@ -639,7 +658,7 @@ with `connect(std::move(sndr), @_spawn-receiver_@(this))`, and _`assoc`_ with `t [7]{.pnum} _Effects:_ Equivalent to: ``` - if (@_token_@.try_associate()) + if (@[_token_.try_associate()]{.rm}[_assoc_]{.add}@) start(@_op_@); else @_[destroy]{.rm}[complete]{.add}_@(); @@ -673,26 +692,47 @@ with `connect(std::move(sndr), @_spawn-receiver_@(this))`, and _`assoc`_ with `t At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes -[[1]{.pnum} The `scope_assocation` concept defines the requirements on a type `Assoc` that, when engaged, owns an -association with an async scope.]{.add} +[1]{.pnum} [The `scope_assocation` concept defines the requirements on a type `Assoc`. An object of type `Assoc` is +_engaged_ if and only if it owns an association with an async scope, referred to as its _associated scope_.]{.add} ``` @[namespace\ std::execution\ {]{.add}@ @[template\ ]{.add}@ @[concept\ scope_association\ =]{.add}@ - @[movable\ &&]{.add}@ - @[default_initializable\ &&]{.add}@ + @[movable\\ &&]{.add}@ + @[is_nothrow_move_constructible_v\\ &&]{.add}@ + @[is_nothrow_move_assignable_v\\ &&]{.add}@ + @[default_initializable\\ &&]{.add}@ @[requires(const\ Assoc\ assoc)\ {]{.add}@ - @[{\ static_cast(assoc)\ }\ noexcept;]{.add}@ - @[{\ assoc.try_associate()\ }\ ->\ same_as;]{.add}@ + @[{\ static_cast\(assoc)\ }\ noexcept;]{.add}@ + @[{\ assoc.try_associate()\ }\ ->\ same_as\;]{.add}@ @[};]{.add}@ @[}]{.add}@ ``` -[2]{.pnum} The `scope_token` concept defines the requirements on a type `Token` that can be used -to create associations between senders and an async scope. - -[3]{.pnum} Let _`test-sender`_ and _`test-env`_ be unspecified types such that +[2]{.pnum} [A type `Assoc` models `scope_association` only if:]{.add} + +- [2.1]{.pnum} [a default constructed object of type `Assoc` is not engaged;]{.add} +- [2.2]{.pnum} [for an object `assoc` of type `Assoc`, `static_cast(assoc)`is `true` if and only if `assoc` is + engaged;]{.add} +- [2.3]{.pnum} [no two distinct objects of type `Assoc` own the same assocation;]{.add} +- [2.4]{.pnum} [for an object `assoc` of type `Assoc`, destroying `assoc` releases the assocation owned by `assoc`, if + any;]{.add} +- [2.5]{.pnum} [for an object `assoc` of type `Assoc`, after it is used as the source operand of a move constructor, the + `assoc` is not engaged;]{.add} +- [2.6]{.pnum} [for distinct objects `assoc1` and `assoc2` of type `Assoc`, after evaluating + `assoc1 = std::move(assoc2)`, the association owned by `assoc1`, if any, is released and `assoc2` is not engaged;]{.add} +- [2.7]{.pnum} [for an object `assoc` of type `Assoc` that is engaged, `assoc.try_associate()` either returns an object + that is not engaged or acquires a new association with `assoc`'s associated scope and returns an engaged object that + owns that association;]{.add} +- [2.8]{.pnum} [for an object `assoc` of type `Assoc` that is not engaged, `assoc.try_associate()` returns an object + that is not engaged.]{.add} + +[3]{.pnum} The `scope_token` concept defines the requirements on a type `Token` that can be used to create associations +between senders and an async scope. [Every object of type `Token` is associated with an async scope that is referred to +as its _associated scope_.]{.add} + +[4]{.pnum} Let _`test-sender`_ and _`test-env`_ be unspecified types such that `sender_in<@_test-sender_@, @_test-env_@>` is modeled. ``` @@ -709,8 +749,36 @@ namespace std::execution { } ``` +[5]{.pnum} A type `Token` models `scope_token` only if: + +- [5.1]{.pnum} no exceptions are thrown from copy construction, move construction, copy assignment, or move assignment + of objects of type Token; [and]{.rm} +- [5.2]{.pnum} [for an object `token` of type `Token`, `token.try_associate()` either returns an object that is not + engaged or acquires a new association with `token`'s associated scope and returns an engaged object that owns that + association; and]{.add} +- [5.3]{.pnum} given an lvalue token of type… + ## `execution::simple_counting_scope` and `execution::counting_scope` +Add the following to the end of paragraph three of [exec.counting.scopes.general]{.sref}: + +``` +@[template\ ]{.add}@ +@[struct\ _association-t_;]{.add}@ +``` + +Add the following as paragraph five of [exec.counting.scopes.general]{.sref}: + +[5]{.pnum} [`@_association-t_@` is a class template, specializations of which model `scope_association` and contain an +exposition-only member _`scope`_ of type `Scope*`. For a class type `Scope` and an object `assoc` of type +`@_association-t_@`:]{.add} + +- [5.1]{.pnum} [`assoc.@_scope_@` points to its associated scope,]{.add} +- [5.2]{.pnum} [`assoc` is engaged when `assoc.@_scope_@ != nullptr` is `true`,]{.add} +- [5.3]{.pnum} [if `assoc` is engaged then `assoc.try_associate()` is equivalent to + `assoc.@_scope_@->@_try-associate_@()`, and]{.add} +- [5.4]{.pnum} [the association owned by `assoc` is released by invoking `assoc.@_scope_@->@_disassociate_@()`.]{.add} + To the subsection [exec.scope.simple.counting.general]{.sref}, make the following change: ``` @@ -721,7 +789,7 @@ namespace std::execution { struct token; @[//\ [exec.simple.counting.assoc],\ assoc]{.add}@ - @[struct\ assoc;]{.add}@ + @[using\ _assoc-t_\ =\ _association-t_\;\ //\ _exposition\ only_]{.add}@ static constexpr size_t max_associations = @_implementation-defined_@; @@ -736,20 +804,20 @@ namespace std::execution { sender auto join() noexcept; private: - size_t @_count_@; // exposition only - @_scope-state-type_@ @_state_@; // exposition only + size_t @_count_@; // @_exposition only_@ + @_scope-state-type_@ @_state_@; // @_exposition only_@ - @[bool]{.rm}[assoc]{.add}@ @_try-associate_@() noexcept; // exposition only - void @_disassociate_@() noexcept; // exposition only - template - bool @_start-join-sender_@(State& state) noexcept; // exposition only + @[bool]{.rm}[_assoc-t_]{.add}@ @_try-associate_@() noexcept; // @_exposition only_@ + void @_disassociate_@() noexcept; // @_exposition only_@ + template + bool @_start-join-sender_@(State& state) noexcept; // @_exposition only_@ }; } ``` To the subsection [exec.simple.counting.mem]{.sref}, make the following changes: -`@[bool]{.rm}[assoc]{.add}@ @_try-associate_@() noexcept;` +`@[bool]{.rm}[_assoc-t_]{.add}@ @_try-associate_@() noexcept;` [5]{.pnum} _Effects:_ If _`count`_ is equal to `max_associations`, then no effects. Otherwise, if _`state`_ is @@ -757,23 +825,22 @@ To the subsection [exec.simple.counting.mem]{.sref}, make the following changes: - [5.2]{.pnum} _`open`_ or _`open-and-joining`_, then increments _`count`_; - [5.3]{.pnum} otherwise, no effects. -[6]{.pnum} _Returns:_ [`true` if _`count`_ was incremented, `false` otherwise.]{.rm} [An object `a` of type -`simple_counting_scope::assoc` such that `a.@_scope_@` is `this` if _`count`_ was incremented, `nullptr` -otherwise.]{.add} +[6]{.pnum} _Returns:_ [`true` if _`count`_ was incremented, `false` otherwise.]{.rm} [If _`count`_ was incremented, an +object of type _`assoc-t`_ that is engaged and associated with `*this`, and `@_assoc-t_@()` otherwise.]{.add} To the subsection [exec.simple.counting.token]{.sref}, make the following changes: ``` namespace std::execution { struct simple_counting_scope::token { - template + template Sender&& wrap(Sender&& snd) const noexcept; - @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; + @[bool]{.rm}[_assoc-t_]{.add}@ try_associate() const noexcept; @[void\ disassociate()\ const\ noexcept;]{.rm}@ private: - simple_counting_scope* @_scope_@; // exposition only + simple_counting_scope* @_scope_@; // @_exposition only_@ }; } ``` @@ -785,7 +852,7 @@ template [1]{.pnum} _Returns:_ `std::forward(snd)`. -`@[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept;` +`@[bool]{.rm}[_assoc-t_]{.add}@ try_associate() const noexcept;` [2]{.pnum} _Effects:_ Equivalent to: `return @_scope_@->@_try-associate_@();` @@ -793,66 +860,23 @@ template [[3]{.pnum} _Effects:_ Equivalent to `@_scope_@->@_disassociate_@()`.]{.rm} -Add the following new section immediately after [exec.simple.counting.token]{.sref}: - -[__Association [exec.simple.counting.assoc]__]{.add} - -``` -@[namespace\ std::execution\ {]{.add}@ - @[struct\ simple_counting_scope::assoc\ {]{.add}@ - @[explicit\ operator\ bool()\ const\ noexcept;]{.add}@ - - @[assoc\ try_associate()\ const\ noexcept;]{.add}@ - - @[private:]{.add}@ - @[using\ _handle_\ =\ //\ _exposition\ only_]{.add}@ - @[unique_ptr_disassociate_();]{.add}@ - @[})>;]{.add}@ - - @[_handle_\ _scope_;\ //\ _exposition\ only_]{.add}@ - @[};]{.add}@ -@[}]{.add}@ -``` - -[`explicit operator bool() const noexcept;`]{.add} - -[[1]{.pnum} _Returns:_ `@_scope_@ != nullptr`]{.add} - -[`assoc try_associate() const noexcept;`]{.add} - -[[2]{.pnum} _Returns:_ A default-initialized `assoc` if _`scope`_ is `nullptr`, `@_scope_@->@_try-associate_@()` -otherwise.]{.add} - To the subsection [exec.scope.counting]{.sref}, make the following changes: ``` namespace std::execution { class counting_scope { public: - @[struct\ assoc\ {]{.add}@ - @[explicit\ operator\ bool()\ const\ noexcept;]{.add}@ - - @[assoc\ try_associate()\ const\ noexcept;]{.add}@ - - @[private:]{.add}@ - @[using\ @_handle_@\ =\ //\ @_exposition\ only_@]{.add}@ - @[unique_ptr@_disassociate_@();]{.add}@ - @[})>;]{.add}@ - - @[@_handle_@ @_scope_@;\ //\ @_exposition\ only_@]{.add}@ - @[};]{.add}@ + @[using\ _assoc-t_\ =\ _association-t_\;]{.add}@ @[//\ _exposition\ only_]{.add}@ struct token { - template + template sender auto wrap(Sender&& snd) const noexcept(see below); - @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; + @[bool]{.rm}[_assoc-t_]{.add}@ try_associate() const noexcept; @[void\ disassociate()\ const\ noexcept;]{.rm}@ private: - counting_scope* @_scope_@; // exposition only + counting_scope* @_scope_@; // @_exposition only_@ }; static constexpr size_t max_associations = implementation-defined; @@ -867,15 +891,36 @@ namespace std::execution { void request_stop() noexcept; private: - size_t @_count_@; // exposition only - @_scope-state-type_@ @_state_@; // exposition only - inplace_stop_source @_s_source_@; // exposition only + size_t @_count_@; // @_exposition only_@ + @_scope-state-type_@ @_state_@; // @_exposition only_@ + inplace_stop_source @_s_source_@; // @_exposition only_@ - @[bool]{.rm}[assoc]{.add}@ @_try-associate_@() noexcept; // exposition only - void @_disassociate_@() noexcept; // exposition only + @[bool]{.rm}[_assoc-t_]{.add}@ @_try-associate_@() noexcept; // @_exposition only_@ + void @_disassociate_@() noexcept; // @_exposition only_@ - template - bool @_start-join-sender_@(State& state) noexcept; // exposition only + template + bool @_start-join-sender_@(State& state) noexcept; // @_exposition only_@ }; } ``` + +``` +@[_assoc-t_\ _try-associate_()\ noexcept;]{.add}@ +``` + +[5]{.pnum} [_Effects:_ If _`count`_ is equal to `max_associations`, then no effects. Otherwise, if _`state`_ is]{.add} + +- [5.1]{.pnum} [_`unused`_, then increments _`count`_ and changes _`state`_ to _`open`_;]{.add} +- [5.2]{.pnum} [_`open`_ or _`open-and-joining`_, then increments _`count`_;]{.add} +- [5.3]{.pnum} [otherwise, no effects.]{.add} + +[6]{.pnum} [_Returns:_ If _`count`_ was incremented, an object of type _`assoc-t`_ that is engaged and associated with +`*this`, and `@_assoc-t_@()` otherwise.]{.add} + +… + +``` +@[_assoc-t_\ counting_scope::token::try_associate()\ const\ noexcept;]{.add}@ +``` + +[8]{.pnum} [_Effects:_ Equivalent to: `return @_scope_@->@_try-associate_@();`]{.add}