From e4de0e51a251c4422c05cfecc5336daa0bca2e87 Mon Sep 17 00:00:00 2001 From: "Jessica Wong (Seattle)" Date: Sun, 24 Aug 2025 17:06:37 -0700 Subject: [PATCH 01/15] Add D3815R0 --- 3815.md | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 3815.md diff --git a/3815.md b/3815.md new file mode 100644 index 0000000..c7d964f --- /dev/null +++ b/3815.md @@ -0,0 +1,283 @@ +--- +title: Add `scope_association` concept to P3149 +document: D3815R0 +date: today +audience: + - LEWG Library Evolution Working Group +author: + - name: Ian Petersen + email: + - name: Jessica Wong + email: +toc: true +--- +Changes +======= + +## R0 +- First revision + + +Background and Motivation +========================= + +[@P3149R11] was approved for C++26 by WG21. The paper introduces two scope types, `simple_counting_scope` and `counting_scope`, along with several basis operations, including `associate`, `spawn`, and `spawn_future`. In R11, the example implementations of these facilities are expressed in terms of the `scope_token` concept: + +```cpp +template +concept scope_token = + copyable && + requires(const Token token) { + { token.try_associate() } -> same_as; + { token.disassociate() } noexcept -> same_as; + { token.wrap(declval()) } -> sender_in; + }; +``` + +During the development of these sample implementations, it was observed that reintroducing the `scope_association` concept from R7 would yield several benefits: +- reduced binary size for both scopes and basis operations, +- fewer move/copy operations, +- and a simplified implementation model that in turn facilitates easier customization. + +These improvements can be achieved without any impact on the user-facing APIs proposed in R11. + +Illustrated below are the R11 implementations of `spawn` and `associate` in contrast with the `scope_association` concept implementation. + +## spawn +:::cmptable +### Before +```cpp +template +struct spawn-state : spawn-state-base { + using op-t = + connect_result_t; + + spawn-state(Alloc a, Sender&& sndr, Token t) + : alloc(std::move(a)), + op(connect(std::move(sndr), + spawn-receiver{this})), + token(std::move(t)) {} + + void run() noexcept { + if (token.try_associate()) + op.start(); + else + destroy(); + } + + void complete() noexcept override { + auto t = std::move(token); + destroy(); + t.disassociate(); + } +}; +``` +### After +```cpp +template +struct spawn-state : spawn-state-base { + using op-t = + connect_result_t; + using assoc-t = remove_cvref_t().try_associate())>; + + spawn-state(Alloc a, Sender&& sndr, Token t) + : alloc(std::move(a)), + op(connect(std::move(sndr), + + spawn-receiver{this})) { + assoc = t.try_associate(); + } + + void run() noexcept { + if (assoc) + op.start(); + else + destroy(); + } + + void complete() noexcept override { + auto a = std::move(assoc); + destroy(); + } +}; +``` +:::: + +## associate +:::cmptable +### Before +```cpp +template +struct @_associate-data_@ { + explicit @_associate-data_@(Token t, Sender&& s) noexcept( + noexcept(t.wrap(std::forward(s))) && + noexcept(t.try_associate())) + : @_token_@(std::move(t)), + @_sndr_@(token.wrap(std::forward(s))) { + 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())) + requires copy_constructible<@_wrap-sender_@> + : token(other.token) { + if (other.sndr.has_value() && token.try_associate()) { + try { + sndr.emplace(*other.sndr); + } catch (...) { + token.disassociate(); + throw; + } + } + } + + @_associate-data_@(@_associate-data_@&& other) noexcept( + is_nothrow_move_constructible_v<@_wrap-sender_@>) + : @_sndr_@(std::move(other).@_sndr_@), + @_token_@(std::move(other).@_token_@) { + other.@_sndr_@.reset(); + } + + ~@_associate-data_@() { + @_sndr_@.reset(); + } + + optional> release() && noexcept( + is_nothrow_move_constructible_v<@_wrap-sender_@>) { + if (@_sndr_@) { + return optional{ + pair{std::move(@_token_@), std::move(*@_sndr_@)}}; + } else { + return nullopt; + } + } + +private: + optional<@_wrap-sender_@> @_sndr_@; + @_Token_@ @_token_@; +}; +``` +### After +```cpp +template +struct @_associate-data_@ { + explicit @_associate-data_@(Token t, Sender&& s) noexcept( + noexcept(t.wrap(std::forward(s))) && + noexcept(t.try_associate())) + : @_sndr_@(t.wrap(std::forward(s))) { + sender_ref guard{addressof(sndr)}; + @_assoc_@ = t.try_associate(); + if (@_assoc_@) { + (void)guard.release(); + } + } + + @_associate-data_@(const @_associate-data_@& other) noexcept( + is_nothrow_copy_constructible_v && + is_nothrow_copy_constructible_v) + requires copy_constructible + : @_assoc_@(other.@_assoc_@) { + if (@_assoc_@) { + construct_at(addressof(@_sndr_@), other.@_sndr_@); + } + } + + @_associate-data_@(@_associate-data_@&& other) noexcept( + is_nothrow_move_constructible_v) + : @_associate-data_@(std::move(other).release()) {} + + ~@_associate-data_@() { + if (@_assoc_@) { + destroy_at(addressof(@_sndr_@)); + } + } + + pair release() && noexcept { + @_wrap_sender_@* p = @_assoc_@ ? addressof(sndr) : nullptr; + return pair{std::move(@_assoc_@), sender_ref{p}}; + } + + private: + @_assoc_t_@ @_assoc_@; + union { + @_wrap_sender_@ @_sndr_@; + }; + + @_associate-data_@(pair<@_assoc_t_@, sender_ref> parts) + : @_assoc_@(std::move(parts.first)) { + if (@_assoc_@) { + construct_at(addressof(@_sndr_@), std::move(parts.second)); + } + } +}; +``` +:::: + +Proposal +======== + +```cpp +template + concept scope_assocation = + copyable && + default_initializable && + requires(Assoc assoc) { + { static_cast(assoc) } noexcept; + }; +``` +A `scope_association` type is an RAII handle type 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 copyable but, when copying an engaged association, the resulting copy may be disengaged because the underlying async scope may decline to create a new association. + +The following are the proposed changes to `scope_token`, `associate`, `spawn`, `spawn_future`, `simple_counting_scope`, and `counting_scope` with the adoption of `scope_association`. + +## execution::scope_token +The primary change to `scope_token` is to `try_associate()`, which will return a `scope_association` rather than a `bool`. + +```cpp +template + concept async_scope_token = + copyable && + requires(Token token) { + { token.try_associate() } + -> async_scope_association; + { token.wrap(declval()) } + -> sender_in; + }; +``` + +The `try_associate()` method on a token attempts to create a new association with the scope; `try_associate()` returns an engaged association when the association is successful, and it may either return a disengaged association or throw an exception to indicate failure. + +## execution::associate +With the application of the proposed changes, `nest`’s copy behavior becomes the following: + +Copying a nest-sender is possible if the sender it is wrapping is copyable but the copying process is a bit unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `nest()` is copyable then the resulting nest-sender is also copyable, with the following rules: + +- copying an unassociated nest-sender invariably produces a new unassociated nest-sender; and +- copying an associated nest-sender requires copying the _`nest-data`_ it contains and the _`nest-data`_ copy-constructor proceeds as follows: + + - copy the association from the source into the destination _`nest-data`_ + + - if the copied association is engaged then copy the wrapped sender from the source into the destination _`nest-data`_; the destination nest-sender is associated + - otherwise, the destination nest-sender is unassociated + +Furthermore, the _`operation-state`_’s destructor becomes the following: + +An _`operation-state`_ with its own association must invoke the association’s destructor as the last step of the _`operation-state`_’s destructor. + +## execution::spawn +The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an _`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the _`association`_ is invoked, replacing the previous mechanism of explicitly calling `token.disassociate()` on the local copy of the _`token`_. + +## execution::spawn_future +The changes to `spawn_future` reflect the same changes proposed in `spawn`. + +## execution::simple_counting_scope +The behavior of `simple_counting_scope` remains largely unchanged, with the primary difference being that the disassociation is handled by the destructor of the association returned from `token.try_associate()`. + +## execution::counting_scope +The changes to `counting_scope` reflect the same changes proposed in `simple_counting_scope`. + +Wording +======= From 14078e18a1b0c76318fcc6eba915a1cec7284829 Mon Sep 17 00:00:00 2001 From: "Jessica Wong (Seattle)" Date: Mon, 25 Aug 2025 23:47:19 -0700 Subject: [PATCH 02/15] Make scope_association movable, not copyable, and introduce try_associate. Also fix some typos and make naming consistent --- 3815.md | 98 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/3815.md b/3815.md index c7d964f..0b10c48 100644 --- a/3815.md +++ b/3815.md @@ -35,6 +35,7 @@ concept scope_token = ``` During the development of these sample implementations, it was observed that reintroducing the `scope_association` concept from R7 would yield several benefits: + - reduced binary size for both scopes and basis operations, - fewer move/copy operations, - and a simplified implementation model that in turn facilitates easier customization. @@ -43,30 +44,30 @@ These improvements can be achieved without any impact on the user-facing APIs pr Illustrated below are the R11 implementations of `spawn` and `associate` in contrast with the `scope_association` concept implementation. -## spawn +## execution::spawn :::cmptable ### Before ```cpp -template -struct spawn-state : spawn-state-base { - using op-t = - connect_result_t; +template +struct @_spawn-state_@ : @_spawn-state-base_@ { + using @_op-t_@ = + connect_result_t; - spawn-state(Alloc a, Sender&& sndr, Token t) - : alloc(std::move(a)), - op(connect(std::move(sndr), - spawn-receiver{this})), - token(std::move(t)) {} + @_spawn-state_@(Alloc a, Sender&& sndr, Token t) + : @_alloc_@(std::move(a)), + @_op_@(connect(std::move(sndr), + @_spawn-receiver_@{this})), + @_token_@(std::move(t)) {} void run() noexcept { - if (token.try_associate()) - op.start(); + if (@_token_@.try_associate()) + @_op_@.start(); else destroy(); } void complete() noexcept override { - auto t = std::move(token); + auto t = std::move(@_token_@); destroy(); t.disassociate(); } @@ -74,24 +75,24 @@ struct spawn-state : spawn-state-base { ``` ### After ```cpp -template -struct spawn-state : spawn-state-base { - using op-t = - connect_result_t; - using assoc-t = remove_cvref_t +struct @_spawn-state_@ : @_spawn-state-base_@ { + using @_op-t_@ = + connect_result_t; + using @_assoc-t_@ = remove_cvref_t().try_associate())>; - spawn-state(Alloc a, Sender&& sndr, Token t) - : alloc(std::move(a)), - op(connect(std::move(sndr), - - spawn-receiver{this})) { - assoc = t.try_associate(); + @_spawn-state_@(Alloc a, Sender&& sndr, Token t) + : @_alloc_@(std::move(a)), + @_op_@(connect( + std::move(sndr), + @_spawn-receiver_@{this})) { + @_assoc_@ = t.try_associate(); } void run() noexcept { if (assoc) - op.start(); + @_op_@.start(); else destroy(); } @@ -104,11 +105,11 @@ struct spawn-state : spawn-state-base { ``` :::: -## associate +## execution::associate :::cmptable ### Before ```cpp -template +template struct @_associate-data_@ { explicit @_associate-data_@(Token t, Sender&& s) noexcept( noexcept(t.wrap(std::forward(s))) && @@ -158,12 +159,12 @@ struct @_associate-data_@ { private: optional<@_wrap-sender_@> @_sndr_@; - @_Token_@ @_token_@; + Token @_token_@; }; ``` ### After ```cpp -template +template struct @_associate-data_@ { explicit @_associate-data_@(Token t, Sender&& s) noexcept( noexcept(t.wrap(std::forward(s))) && @@ -177,9 +178,9 @@ struct @_associate-data_@ { } @_associate-data_@(const @_associate-data_@& other) noexcept( - is_nothrow_copy_constructible_v && + is_nothrow_copy_constructible_v<@_wrap-sender_@> && is_nothrow_copy_constructible_v) - requires copy_constructible + requires copy_constructible<@_wrap-sender_@> : @_assoc_@(other.@_assoc_@) { if (@_assoc_@) { construct_at(addressof(@_sndr_@), other.@_sndr_@); @@ -187,7 +188,7 @@ struct @_associate-data_@ { } @_associate-data_@(@_associate-data_@&& other) noexcept( - is_nothrow_move_constructible_v) + is_nothrow_move_constructible_v<@_wrap-sender_@>) : @_associate-data_@(std::move(other).release()) {} ~@_associate-data_@() { @@ -197,17 +198,17 @@ struct @_associate-data_@ { } pair release() && noexcept { - @_wrap_sender_@* p = @_assoc_@ ? addressof(sndr) : nullptr; + @_wrap-sender_@* p = @_assoc_@ ? addressof(@_sndr_@) : nullptr; return pair{std::move(@_assoc_@), sender_ref{p}}; } private: - @_assoc_t_@ @_assoc_@; + assoc_t @_assoc_@; union { - @_wrap_sender_@ @_sndr_@; + @_wrap-sender_@ @_sndr_@; }; - @_associate-data_@(pair<@_assoc_t_@, sender_ref> parts) + @_associate-data_@(pair parts) : @_assoc_@(std::move(parts.first)) { if (@_assoc_@) { construct_at(addressof(@_sndr_@), std::move(parts.second)); @@ -223,13 +224,14 @@ Proposal ```cpp template concept scope_assocation = - copyable && + movable && default_initializable && requires(Assoc assoc) { { static_cast(assoc) } noexcept; + { assoc.try_associate() } -> scope_association; }; ``` -A `scope_association` type is an RAII handle type 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 copyable but, when copying an engaged association, the resulting copy may be disengaged because the underlying async scope may decline to create a new association. +A `scope_association` type is an RAII handle type 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 movable and not copyable, and expose a `try_associate()` member function with semantics identical to `token.try_associate()`. The following are the proposed changes to `scope_token`, `associate`, `spawn`, `spawn_future`, `simple_counting_scope`, and `counting_scope` with the adoption of `scope_association`. @@ -238,11 +240,11 @@ The primary change to `scope_token` is to `try_associate()`, which will return a ```cpp template - concept async_scope_token = + concept scope_token = copyable && requires(Token token) { { token.try_associate() } - -> async_scope_association; + -> scope_association; { token.wrap(declval()) } -> sender_in; }; @@ -251,24 +253,24 @@ template The `try_associate()` method on a token attempts to create a new association with the scope; `try_associate()` returns an engaged association when the association is successful, and it may either return a disengaged association or throw an exception to indicate failure. ## execution::associate -With the application of the proposed changes, `nest`’s copy behavior becomes the following: +With the application of the proposed changes, `associate`’s copy behavior becomes the following: -Copying a nest-sender is possible if the sender it is wrapping is copyable but the copying process is a bit unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `nest()` is copyable then the resulting nest-sender is also copyable, with the following rules: +Copying a associate-sender is possible if the sender it is wrapping is copyable but the copying process is a bit unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `associate()` is copyable then the resulting associate-sender is also copyable, with the following rules: -- copying an unassociated nest-sender invariably produces a new unassociated nest-sender; and -- copying an associated nest-sender requires copying the _`nest-data`_ it contains and the _`nest-data`_ copy-constructor proceeds as follows: +- copying an unassociated associate-sender invariably produces a new unassociated associate-sender; and +- copying an associated associate-sender requires copying the _`associate-data`_ it contains and the _`associate-data`_ copy-constructor proceeds as follows: - - copy the association from the source into the destination _`nest-data`_ + - The result of invoking the source's `@_association_@.try_associate()` will be passed to the destination _`associate-data`_. - - if the copied association is engaged then copy the wrapped sender from the source into the destination _`nest-data`_; the destination nest-sender is associated - - otherwise, the destination nest-sender is unassociated + - if the resulting association is engaged then copy the wrapped sender from the source into the destination _`associate-data`_; the destination associate-sender is associated + - otherwise, the destination associate-sender is unassociated Furthermore, the _`operation-state`_’s destructor becomes the following: An _`operation-state`_ with its own association must invoke the association’s destructor as the last step of the _`operation-state`_’s destructor. ## execution::spawn -The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an _`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the _`association`_ is invoked, replacing the previous mechanism of explicitly calling `token.disassociate()` on the local copy of the _`token`_. +The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an _`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the _`association`_ is invoked, replacing the previous mechanism of explicitly calling `@_token_@.disassociate()` on the local copy of the _`token`_. ## execution::spawn_future The changes to `spawn_future` reflect the same changes proposed in `spawn`. From 789832c7322e6e9801599707f8c5abd34a899606 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Tue, 26 Aug 2025 21:52:32 -0700 Subject: [PATCH 03/15] Fold text to 120 chars Sorry--I use `vim`. --- 3815.md | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/3815.md b/3815.md index 0b10c48..74b937d 100644 --- a/3815.md +++ b/3815.md @@ -21,7 +21,9 @@ Changes Background and Motivation ========================= -[@P3149R11] was approved for C++26 by WG21. The paper introduces two scope types, `simple_counting_scope` and `counting_scope`, along with several basis operations, including `associate`, `spawn`, and `spawn_future`. In R11, the example implementations of these facilities are expressed in terms of the `scope_token` concept: +[@P3149R11] was approved for C++26 by WG21. The paper introduces two scope types, `simple_counting_scope` and +`counting_scope`, along with several basis operations, including `associate`, `spawn`, and `spawn_future`. In R11, the +example implementations of these facilities are expressed in terms of the `scope_token` concept: ```cpp template @@ -34,7 +36,8 @@ concept scope_token = }; ``` -During the development of these sample implementations, it was observed that reintroducing the `scope_association` concept from R7 would yield several benefits: +During the development of these sample implementations, it was observed that reintroducing the `scope_association` +concept from R7 would yield several benefits: - reduced binary size for both scopes and basis operations, - fewer move/copy operations, @@ -42,7 +45,8 @@ During the development of these sample implementations, it was observed that rei These improvements can be achieved without any impact on the user-facing APIs proposed in R11. -Illustrated below are the R11 implementations of `spawn` and `associate` in contrast with the `scope_association` concept implementation. +Illustrated below are the R11 implementations of `spawn` and `associate` in contrast with the `scope_association` +concept implementation. ## execution::spawn :::cmptable @@ -231,12 +235,18 @@ template { assoc.try_associate() } -> scope_association; }; ``` -A `scope_association` type is an RAII handle type 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 movable and not copyable, and expose a `try_associate()` member function with semantics identical to `token.try_associate()`. +A `scope_association` type is an RAII handle type 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 +movable and not copyable, and expose a `try_associate()` member function with semantics identical to +`token.try_associate()`. -The following are the proposed changes to `scope_token`, `associate`, `spawn`, `spawn_future`, `simple_counting_scope`, and `counting_scope` with the adoption of `scope_association`. +The following are the proposed changes to `scope_token`, `associate`, `spawn`, `spawn_future`, `simple_counting_scope`, +and `counting_scope` with the adoption of `scope_association`. ## execution::scope_token -The primary change to `scope_token` is to `try_associate()`, which will return a `scope_association` rather than a `bool`. +The primary change to `scope_token` is to `try_associate()`, which will return a `scope_association` rather than a +`bool`. ```cpp template @@ -250,33 +260,45 @@ template }; ``` -The `try_associate()` method on a token attempts to create a new association with the scope; `try_associate()` returns an engaged association when the association is successful, and it may either return a disengaged association or throw an exception to indicate failure. +The `try_associate()` method on a token attempts to create a new association with the scope; `try_associate()` returns +an engaged association when the association is successful, and it may either return a disengaged association or throw +an exception to indicate failure. ## execution::associate With the application of the proposed changes, `associate`’s copy behavior becomes the following: -Copying a associate-sender is possible if the sender it is wrapping is copyable but the copying process is a bit unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `associate()` is copyable then the resulting associate-sender is also copyable, with the following rules: +Copying a associate-sender is possible if the sender it is wrapping is copyable but the copying process is a bit +unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `associate()` is copyable +then the resulting associate-sender is also copyable, with the following rules: - copying an unassociated associate-sender invariably produces a new unassociated associate-sender; and -- copying an associated associate-sender requires copying the _`associate-data`_ it contains and the _`associate-data`_ copy-constructor proceeds as follows: +- copying an associated associate-sender requires copying the _`associate-data`_ it contains and the _`associate-data`_ +copy-constructor proceeds as follows: - - The result of invoking the source's `@_association_@.try_associate()` will be passed to the destination _`associate-data`_. + - The result of invoking the source's `@_association_@.try_associate()` will be passed to the destination +_`associate-data`_. - - if the resulting association is engaged then copy the wrapped sender from the source into the destination _`associate-data`_; the destination associate-sender is associated + - if the resulting association is engaged then copy the wrapped sender from the source into the destination +_`associate-data`_; the destination associate-sender is associated - otherwise, the destination associate-sender is unassociated Furthermore, the _`operation-state`_’s destructor becomes the following: -An _`operation-state`_ with its own association must invoke the association’s destructor as the last step of the _`operation-state`_’s destructor. +An _`operation-state`_ with its own association must invoke the association’s destructor as the last step of the +_`operation-state`_’s destructor. ## execution::spawn -The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an _`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the _`association`_ is invoked, replacing the previous mechanism of explicitly calling `@_token_@.disassociate()` on the local copy of the _`token`_. +The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an +_`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the +_`association`_ is invoked, replacing the previous mechanism of explicitly calling `@_token_@.disassociate()` on the +local copy of the _`token`_. ## execution::spawn_future The changes to `spawn_future` reflect the same changes proposed in `spawn`. ## execution::simple_counting_scope -The behavior of `simple_counting_scope` remains largely unchanged, with the primary difference being that the disassociation is handled by the destructor of the association returned from `token.try_associate()`. +The behavior of `simple_counting_scope` remains largely unchanged, with the primary difference being that the +disassociation is handled by the destructor of the association returned from `token.try_associate()`. ## execution::counting_scope The changes to `counting_scope` reflect the same changes proposed in `simple_counting_scope`. From 3e46141810c89b7844b24bb64c372355c2a84dab Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Tue, 26 Aug 2025 22:11:03 -0700 Subject: [PATCH 04/15] Some suggested changes to the prose * A few wording tweaks * Change `scope_association` concept to require that `try_associate` return an `Assoc` rather than "anything that models `scope_association`"; it turns out that concepts can't be recursive * Some formatting tweaks --- 3815.md | 56 +++++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/3815.md b/3815.md index 74b937d..50976bb 100644 --- a/3815.md +++ b/3815.md @@ -37,7 +37,7 @@ concept scope_token = ``` During the development of these sample implementations, it was observed that reintroducing the `scope_association` -concept from R7 would yield several benefits: +concept from [@P3149R7] would yield several benefits: - reduced binary size for both scopes and basis operations, - fewer move/copy operations, @@ -227,49 +227,47 @@ Proposal ```cpp template - concept scope_assocation = + concept scope_association = movable && default_initializable && requires(Assoc assoc) { { static_cast(assoc) } noexcept; - { assoc.try_associate() } -> scope_association; + { assoc.try_associate() } -> same_as; }; ``` -A `scope_association` type is an RAII handle type 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 +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 -movable and not copyable, and expose a `try_associate()` member function with semantics identical to -`token.try_associate()`. +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`. The following are the proposed changes to `scope_token`, `associate`, `spawn`, `spawn_future`, `simple_counting_scope`, and `counting_scope` with the adoption of `scope_association`. -## execution::scope_token -The primary change to `scope_token` is to `try_associate()`, which will return a `scope_association` rather than a +## `execution::scope_token` +The primary change to `scope_token` is to `try_associate`, which will return a `scope_association` rather than a `bool`. ```cpp template - concept scope_token = - copyable && - requires(Token token) { - { token.try_associate() } - -> scope_association; - { token.wrap(declval()) } - -> sender_in; - }; + concept scope_token = + copyable && + requires(Token token) { + { token.try_associate() } -> scope_association; + { token.wrap(declval<@_test-sender_@>()) } -> sender_in<@_test-env_@>; + }; ``` -The `try_associate()` method on a token attempts to create a new association with the scope; `try_associate()` returns -an engaged association when the association is successful, and it may either return a disengaged association or throw -an exception to indicate failure. +The `try_associate` member function on a token attempts to create a new association with the scope; `try_associate` +returns an engaged association when the association is successful, and it may either return a disengaged association or +throw an exception to indicate failure. -## execution::associate -With the application of the proposed changes, `associate`’s copy behavior becomes the following: +## `execution::associate` +With the application of the proposed changes, the copy behavior of the associate-sender returned from `associate` +becomes the following: -Copying a associate-sender is possible if the sender it is wrapping is copyable but the copying process is a bit -unusual because of the `scope_association` it contains. If the sender, `snd`, provided to `associate()` is copyable -then the resulting associate-sender is also copyable, with the following rules: +If the sender, `snd`, provided to `associate()` is copyable then the resulting associate-sender is also copyable, with +the following rules: - copying an unassociated associate-sender invariably produces a new unassociated associate-sender; and - copying an associated associate-sender requires copying the _`associate-data`_ it contains and the _`associate-data`_ @@ -287,20 +285,20 @@ Furthermore, the _`operation-state`_’s destructor becomes the following: An _`operation-state`_ with its own association must invoke the association’s destructor as the last step of the _`operation-state`_’s destructor. -## execution::spawn +## `execution::spawn` The behavior of `spawn` remains largely unchanged, with the primary difference being that _`op_t`_ now holds an _`association`_ rather than a _`token`_. Upon completion of the _`operation-state`_, the destructor of the _`association`_ is invoked, replacing the previous mechanism of explicitly calling `@_token_@.disassociate()` on the local copy of the _`token`_. -## execution::spawn_future +## `execution::spawn_future` The changes to `spawn_future` reflect the same changes proposed in `spawn`. -## execution::simple_counting_scope +## `execution::simple_counting_scope` The behavior of `simple_counting_scope` remains largely unchanged, with the primary difference being that the disassociation is handled by the destructor of the association returned from `token.try_associate()`. -## execution::counting_scope +## `execution::counting_scope` The changes to `counting_scope` reflect the same changes proposed in `simple_counting_scope`. Wording From 5e4742748c4dda726d314f93fdd0a6bf01af5788 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Tue, 26 Aug 2025 23:06:08 -0700 Subject: [PATCH 05/15] Start wording Add wording for the changes to: * ``'s synopsis, * the first few paragraphs describing _`associate-data`_, and * the scope concepts. Still need to finish describing the changes to `associate`, and add all the changes for `spawn`, `spawn_future`, `simple_counting_scope`, and `counting_scope`. --- 3815.md | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/3815.md b/3815.md index 50976bb..610b788 100644 --- a/3815.md +++ b/3815.md @@ -303,3 +303,174 @@ The changes to `counting_scope` reflect the same changes proposed in `simple_cou Wording ======= + +## Header `` synopsis [execution.syn]{.sref} + +To the `` synopsis [execution.syn]{.sref}, make the following change: + +``` + // [exec.scope] + // [exec.scope.concepts], scope concepts +``` +::: add +``` + template + concept scope_association = @_see below_@; +``` +::: +``` + template + concept scope_token = @_see below_@; +``` + +## `execution::associate` + +To the subsection [exec.associate]{.sref}, make the following changes: + +[2]{.pnum} Let _`associate-data`_ be the following exposition-only class template: +``` +namespace std::execution { + +template +struct @_associate-data_@ { // @_exposition only_@ + using @_wrap-sender_@ = // @_exposition only_@ + remove_cvref_t().wrap(declval()))>; +``` +::: add +``` + using @_assoc-t_@ = // @_exposition only_@ + decltype(declval().try_associate()); + + using @_sender-ref_@ = // @_exposition only_@ + unique_ptr<@_wrap-sender_@, decltype([](auto* p) noexcept { destroy_at(p); })>; +``` +::: +``` + + explicit @_associate-data_@(Token t, Sender&& s) +``` +::: rm +``` + : @_sndr_@(t.wrap(std::forward(s))), + @_token_@(t) { + if (!@_token_@.try_associate()) + @_sndr_@.reset(); +``` +::: +::: add +``` + : @_sndr_@(t.wrap(std::forward(s))) { + @_sender-ref_@ guard{addressof(@_sndr_@)}; + + if (@_assoc_@ = t.try_associate()) + (void)guard.release(); +``` +::: +``` + } + + @_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_@(); + +``` +::: rm +``` + optional> release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); +``` +::: +::: add +``` + pair<@_assoc-t_@, @_sender-ref_@> release() && noexcept; +``` +::: +``` + +private: +``` +::: rm +``` + optional<@_wrap-sender_@> @_sndr_@; // @_exposition only_@ + Token @_token_@; // @_exposition only_@ +``` +::: +::: add +``` + @_assoc-t_@ @_assoc_@; // @_exposition only_@ + union { + @_wrap-sender_@ @_sndr_@; // @_exposition only_@ + }; +``` +::: +``` +}; + +template +@_associate-data_@(Token, Sender&&) -> @_associate-data_@; + +} +``` + +[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`. + +## Async scope concepts + +At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes + +::: add + +[1]{.pnum} The `scope_assocation` concept defines the requirements on a type `Assoc` that, when engaged, owns an +association with an async scope. + +```cpp +namespace std::execution { + +template +concept scope_association = + movable && + default_initializable && + requires(const Assoc assoc) { + { static_cast(assoc) } noexcept; + { assoc.try_associate() } -> same_as; + }; +} +``` + +::: + +[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 +`sender_in<@_test-sender_@, @_test-env_@>` is modeled. + +``` +namespace std::execution { + +template +concept scope_token = + copyable && + requires(const Token token) { +``` +::: rm +``` + { token.try_associate() } -> same_as; + { token.disassociate() } noexcept -> same_as; +``` +::: +::: add +``` + { token.try_associate() } -> scope_association; +``` +::: +``` + { token.wrap(declval<@_test-sender_@>()) } -> sender_in<@_test-env_@>; + }; + +} +``` From 7e3aa802f0912cb96fe46d31bc841ed8fceb7ec0 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Wed, 27 Aug 2025 07:29:16 -0700 Subject: [PATCH 06/15] Finish the wording of associate-data --- 3815.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/3815.md b/3815.md index 610b788..2c079ce 100644 --- a/3815.md +++ b/3815.md @@ -400,6 +400,8 @@ private: ::: ::: add ``` + @_associate-data_@(pair<@_assoc-t_@, @_scope-ref_@> parts); + @_assoc-t_@ @_assoc_@; // @_exposition only_@ union { @_wrap-sender_@ @_sndr_@; // @_exposition only_@ @@ -418,6 +420,71 @@ 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`. +```c++ +@_associate-data_@(const @_associate-data_@& other) + noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && + noexcept(other.@_token_@.try_associate())); +``` + +[4]{.pnum} _Constraints:_ `copy_constructible<@_wrap-sender_@>` 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. +::: +::: add +[5]{.pnum} _Effects:_ Initializes _`assoc`_ with `other.@_assoc_@.try_associate()`. If _`assoc`_ contextually converts +to `false`, no further effects; otherwise, invokes `construct_at(addressof(@_sndr_@), other.@_sndr_@)`. +::: + +`@_associate-data_@(@_associate-data_@&& other) 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()`. +::: +::: add +[6]{.pnum} _Effects:_ Equivalent to `@_associate-data_@(std::move(other).release())`. + +`@_associate-data_@(pair<@_assoc-t_@, @_sender-ref_@> parts);` + +[7]{.pnum} _Effects:_ Initializes _`assoc`_ with `std::move(parts.first)`. If _`assoc`_ contextually converts to +`false`, no further effects; otherwise, invokes `construct_at(addressof(@_sndr_@), std::move(*parts.second))`. +::: + +`~@_associate-data_@();` + +::: rm +[7]{.pnum} _Effects:_ If `@_sndr_@.has_value()` returns `false` then no effect; otherwise, invokes `@_sndr_@.reset()` +before invoking `@_token_@.disassociate()`. +::: +::: add +[8]{.pnum} _Effects:_ If _`assoc`_ contextually converts to `false` then no effect; otherwise, invokes +`destroy_at(addressof(@_sndr_@))`. +::: + +::: rm +`optional> release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>);` + +[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: + +```c++ +return optional(pair(@_token_@, std::move(*@_sndr_@))); +``` + +[9]{.pnum} _Postconditions:_ _`sndr`_ does not contain a value. +::: +::: add +`pair<@_assoc-t_@, @_scope-ref_@> release() && noexcept;` + +[9]{.pnum} _Effects:_ 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)}`. +::: + ## Async scope concepts At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes From 1dd916247b9e232ba036dc9cb77d35d7b28c4c7a Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Wed, 27 Aug 2025 20:14:37 -0700 Subject: [PATCH 07/15] Fix associate-data's copy-ctor noexcept clause Replace references to _`token`_ with _`assoc`_ when computing the `noexcept` clause for _`associate-data`_'s copy constructor. --- 3815.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/3815.md b/3815.md index 2c079ce..f0434af 100644 --- a/3815.md +++ b/3815.md @@ -371,7 +371,7 @@ struct @_associate-data_@ { // @_exposition only_@ @_associate-data_@(const @_associate-data_@& other) noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && - noexcept(other.@_token_@.try_associate())); + noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); @_associate-data_@(@_associate-data_@&& other) noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); @@ -420,10 +420,10 @@ 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`. -```c++ +``` @_associate-data_@(const @_associate-data_@& other) noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && - noexcept(other.@_token_@.try_associate())); + noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); ``` [4]{.pnum} _Constraints:_ `copy_constructible<@_wrap-sender_@>` is `true`. From 06133fe4ad89e111219ff2539af281af2ce884e3 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 29 Aug 2025 00:44:06 -0700 Subject: [PATCH 08/15] Finish associate, tweak formatting This finishes the wording for `associate`. Also, I noticed that the standard uses a two-space indent, not four, so I tweaked the formatting to better match the existing text in the latest draft. --- 3815.md | 262 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 208 insertions(+), 54 deletions(-) diff --git a/3815.md b/3815.md index f0434af..9ddc6d7 100644 --- a/3815.md +++ b/3815.md @@ -332,60 +332,62 @@ To the subsection [exec.associate]{.sref}, make the following changes: namespace std::execution { template -struct @_associate-data_@ { // @_exposition only_@ - using @_wrap-sender_@ = // @_exposition only_@ - remove_cvref_t().wrap(declval()))>; +struct @_associate-data_@ { // @_exposition only_@ + using @_wrap-sender_@ = // @_exposition only_@ + remove_cvref_t().wrap(declval()))>; ``` ::: add ``` - using @_assoc-t_@ = // @_exposition only_@ - decltype(declval().try_associate()); + using @_assoc-t_@ = // @_exposition only_@ + decltype(declval().try_associate()); - using @_sender-ref_@ = // @_exposition only_@ - unique_ptr<@_wrap-sender_@, decltype([](auto* p) noexcept { destroy_at(p); })>; + using @_sender-ref_@ = // @_exposition only_@ + unique_ptr<@_wrap-sender_@, decltype([](auto* p) noexcept { destroy_at(p); })>; ``` ::: ``` - explicit @_associate-data_@(Token t, Sender&& s) + explicit @_associate-data_@(Token t, Sender&& s) ``` ::: rm ``` - : @_sndr_@(t.wrap(std::forward(s))), - @_token_@(t) { - if (!@_token_@.try_associate()) - @_sndr_@.reset(); + : @_sndr_@(t.wrap(std::forward(s))), + @_token_@(t) { + if (!@_token_@.try_associate()) + @_sndr_@.reset(); ``` ::: ::: add ``` - : @_sndr_@(t.wrap(std::forward(s))) { - @_sender-ref_@ guard{addressof(@_sndr_@)}; + : @_sndr_@(t.wrap(std::forward(s))) { + @_sender-ref_@ guard{addressof(@_sndr_@)}; - if (@_assoc_@ = t.try_associate()) - (void)guard.release(); + if (@_assoc_@ = t.try_associate()) + (void)guard.release(); ``` ::: ``` - } + } - @_associate-data_@(const @_associate-data_@& other) - noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && - noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); + @_associate-data_@(const @_associate-data_@& other) + noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && + noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); - @_associate-data_@(@_associate-data_@&& other) noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); + @_associate-data_@(@_associate-data_@&& other) + noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); - ~@_associate-data_@(); + ~@_associate-data_@(); ``` ::: rm ``` - optional> release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); + optional> + release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); ``` ::: ::: add ``` - pair<@_assoc-t_@, @_sender-ref_@> release() && noexcept; + pair<@_assoc-t_@, @_sender-ref_@> release() && noexcept; ``` ::: ``` @@ -394,25 +396,25 @@ private: ``` ::: rm ``` - optional<@_wrap-sender_@> @_sndr_@; // @_exposition only_@ - Token @_token_@; // @_exposition only_@ + optional<@_wrap-sender_@> @_sndr_@; // @_exposition only_@ + Token @_token_@; // @_exposition only_@ ``` ::: ::: add ``` - @_associate-data_@(pair<@_assoc-t_@, @_scope-ref_@> parts); + @_associate-data_@(pair<@_assoc-t_@, @_scope-ref_@> parts); - @_assoc-t_@ @_assoc_@; // @_exposition only_@ - union { - @_wrap-sender_@ @_sndr_@; // @_exposition only_@ - }; + @_assoc-t_@ @_assoc_@; // @_exposition only_@ + union { + @_wrap-sender_@ @_sndr_@; // @_exposition only_@ + }; ``` ::: ``` }; template -@_associate-data_@(Token, Sender&&) -> @_associate-data_@; + @_associate-data_@(Token, Sender&&) -> @_associate-data_@; } ``` @@ -422,8 +424,8 @@ to]{.add} `true` if and only if an association was successfully made and is owne ``` @_associate-data_@(const @_associate-data_@& other) - noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && - noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); + noexcept(is_nothrow_copy_constructible_v<@_wrap-sender_@> && + noexcept(other.@[_token_]{.rm}[_assoc_]{.add}@.try_associate())); ``` [4]{.pnum} _Constraints:_ `copy_constructible<@_wrap-sender_@>` is `true`. @@ -436,10 +438,13 @@ returns `true`, calls `@_sndr_@.emplace(*other.@_sndr_@)` and, if that exits wit ::: ::: add [5]{.pnum} _Effects:_ Initializes _`assoc`_ with `other.@_assoc_@.try_associate()`. If _`assoc`_ contextually converts -to `false`, no further effects; otherwise, invokes `construct_at(addressof(@_sndr_@), other.@_sndr_@)`. +to `false`, no further effects; otherwise, initializes _`sndr`_ with `other.@_sndr_@`. ::: -`@_associate-data_@(@_associate-data_@&& other) noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>);` +``` +@_associate-data_@(@_associate-data_@&& other) + noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); +``` ::: rm [6]{.pnum} _Effects:_ Initializes _`sndr`_ with `std::move(other.@_sndr_@)` and initializes _`token`_ with @@ -451,7 +456,7 @@ to `false`, no further effects; otherwise, invokes `construct_at(addressof(@_snd `@_associate-data_@(pair<@_assoc-t_@, @_sender-ref_@> parts);` [7]{.pnum} _Effects:_ Initializes _`assoc`_ with `std::move(parts.first)`. If _`assoc`_ contextually converts to -`false`, no further effects; otherwise, invokes `construct_at(addressof(@_sndr_@), std::move(*parts.second))`. +`false`, no further effects; otherwise, initializes _`sndr`_ with `std::move(*parts.second)`. ::: `~@_associate-data_@();` @@ -461,12 +466,14 @@ to `false`, no further effects; otherwise, invokes `construct_at(addressof(@_snd before invoking `@_token_@.disassociate()`. ::: ::: add -[8]{.pnum} _Effects:_ If _`assoc`_ contextually converts to `false` then no effect; otherwise, invokes -`destroy_at(addressof(@_sndr_@))`. +[8]{.pnum} _Effects:_ If _`assoc`_ contextually converts to `false` then no effect; otherwise, destroys _`sndr`_. ::: ::: rm -`optional> release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>);` +``` +optional> + release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); +``` [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: @@ -485,6 +492,156 @@ contextually converts to `false` and with `addressof(@_sndr_@)` otherwise, then `pair{std::move(@_assoc_@), std::move(u)}`. ::: +[10]{.pnum} 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` does not satisfy `scope_token`, +then `associate(sndr, token)` is ill-formed. + +... + +[13]{.pnum} The member `@_impls-for_@::@_get-state_@` is initialized with a callable object equivalent to +the following lambda: +``` +[](Sndr&& sndr, Rcvr& rcvr) noexcept(@_see below_@) { + auto@[&&]{.add}@ [_, data] = std::forward(sndr); + +``` +::: rm +``` + auto dataParts = std::move(data).release(); + + using scope_token = decltype(dataParts->first); + using wrap_sender = decltype(dataParts->second); +``` +::: +::: add +``` + using associate_data_t = remove_cvref_t; + using assoc_t = typename associate_data_t::@_assoc-t_@; + using sender_ref_t = typename associate_data_t::@_sender-ref_@; +``` +::: +``` + using op_t = connect_result_t<@[wrap_sender]{.rm}[typename\ sender_ref_t::element_type]{.add}@, Rcvr>; + + struct op_state { + @[bool]{.rm}[assoc_t]{.add}@ @_assoc_[_iated_\ =\ false]{.rm}@; // @_exposition only_@ + union { + Rcvr* @_rcvr_@; // @_exposition only_@ +``` +::: rm +``` + struct { + scope_token @_token_@; // @_exposition only_@ + op_t @_op_@; // @_exposition only_@ + } @_assoc_@; // @_exposition only_@ +``` +::: +::: add +``` + op_t @_op_@; // @_exposition only_@ +``` +::: +``` + }; + +``` +::: rm +``` + explicit op_state(Rcvr& r) noexcept + : @_rcvr_@(addressof(r)) {} + + explicit op_state(scope_token tkn, wrap_sender&& sndr, Rcvr& r) try + : @_associated_@(true), + @_assoc_@(tkn, connect(std::move(sndr), std::move(r))) { + } + catch (...) { + tkn.disassociate(); + throw; + } +``` +::: +::: add +``` + explicit op_state(pair 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 + : op_state(associate_data_t(ad).release(), r) {} +``` +::: +``` + + op_state(op_state&&) = delete; + + ~op_state() { + if (@_assoc[iated]{.rm}_@)@[\ {]{.rm}@ + @[_assoc_.]{.rm}_op_@.~op_t(); +``` +::: rm +``` + @_assoc_@.@_token_@.disassociate(); + @_assoc_@.@_token_@.~scope_token(); + } +``` +::: +``` + } + + void @_run_@() noexcept { // @_exposition only_@ + if (@_assoc[iated]{.rm}_@) + start(@[_assoc_.]{.rm}_op_@); + else + set_stopped(std::move(*@_rcvr_@)); + } + }; + +``` +::: rm +``` + if (dataParts) + return op_state{std::move(dataParts->first), std::move(dataParts->second), rcvr}; + else + return op_state{rcvr}; +``` +::: +::: add +``` + return op_state{std::forward_like(data), rcvr}; +``` +::: +``` +} +``` + +[14]{.pnum} The expression in the `noexcept` clause of `@_impls-for_@::@_get-state_@` is + +::: rm +``` + is_nothrow_constructible_v, Sndr> && + is_nothrow_move_constructible_v<@_wrap-sender_@> && +``` +::: +::: add +``` + (is_rvalue_reference_v || is_nothrow_constructible_v, Sndr>) && +``` +::: +``` + @_nothrow-callable_@ +``` + +where _`wrap-sender`_ is the type `remove_cvref_t<@_data-type_@>::@_wrap-sender_@.` + ## Async scope concepts At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes @@ -496,15 +653,14 @@ association with an async scope. ```cpp namespace std::execution { - -template -concept scope_association = - movable && - default_initializable && - requires(const Assoc assoc) { + template + concept scope_association = + movable && + default_initializable && + requires(const Assoc assoc) { { static_cast(assoc) } noexcept; { assoc.try_associate() } -> same_as; - }; + }; } ``` @@ -518,11 +674,10 @@ to create associations between senders and an async scope. ``` namespace std::execution { - -template -concept scope_token = - copyable && - requires(const Token token) { + template + concept scope_token = + copyable && + requires(const Token token) { ``` ::: rm ``` @@ -537,7 +692,6 @@ concept scope_token = ::: ``` { token.wrap(declval<@_test-sender_@>()) } -> sender_in<@_test-env_@>; - }; - + }; } ``` From 7ef33391a6c288a16d270f193ef01992d2e58440 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 29 Aug 2025 01:37:39 -0700 Subject: [PATCH 09/15] Add wording for the scope types --- 3815.md | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/3815.md b/3815.md index 9ddc6d7..95ec69d 100644 --- a/3815.md +++ b/3815.md @@ -695,3 +695,214 @@ namespace std::execution { }; } ``` + +## `execution::simple_counting_scope` and `execution::counting_scope` + +To the subsection [exec.scope.simple.counting.general]{.sref}, make the following change: + +``` +namespace std::execution { + class simple_counting_scope { + public: + // [exec.simple.counting.token], token + struct token; + +``` +::: add +``` + // [exec.simple.counting.assoc], assoc + struct assoc; +``` +::: +``` + static constexpr size_t max_associations = @_implementation-defined_@; + + // [exec.simple.counting.ctor], constructor and destructor + simple_counting_scope() noexcept; + simple_counting_scope(simple_counting_scope&&) = delete; + ~simple_counting_scope(); + + // [exec.simple.counting.mem], members + token get_token() noexcept; + void close() noexcept; + sender auto join() noexcept; + + private: + 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 + }; +} +``` + +To the subsection [exec.simple.counting.mem]{.sref}, make the following changes: + +`@[bool]{.rm}[assoc]{.add}@ @_try-associate_@() noexcept;` + +[5]{.pnum} _Effects:_ If _`count`_ is equal to `max_associations`, then no effects. Otherwise, if _`state`_ is + +- [5.1]{.pnum} _`unused`_, then increments _`count`_ and changes _`state`_ to _`open`_; +- [5.2]{.pnum} _`open`_ or _`open-and-joining`_, then increments _`count`_; +- [5.3]{.pnum} otherwise, no effects. + +::: rm +[6]{.pnum} _Returns:_ `true` if _`count`_ was incremented, `false` otherwise. +::: +::: add +[6]{.pnum} _Returns:_ An object `a` of type `simple_counting_scope::assoc` such that `a.@_scope_@` is `this` if +_`count`_ was incremented, `nullptr` otherwise. +::: + + +To the subsection [exec.simple.counting.token]{.sref}, make the following changes: + +``` +namespace std::execution { + struct simple_counting_scope::token { + template + Sender&& wrap(Sender&& snd) const noexcept; + @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; +``` +::: rm +``` + void disassociate() const noexcept; +``` +::: +``` + + private: + simple_counting_scope* @_scope_@; // exposition only + }; +} +``` + +``` +template + Sender&& wrap(Sender&& snd) const noexcept; +``` + +[1]{.pnum} _Returns:_ `std::forward(snd)`. + +`@[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept;` + +[2]{.pnum} _Effects:_ Equivalent to: `return @_scope_@->@_try-associate_@();` + +::: rm +`void disassociate() const noexcept;` + +[3]{.pnum} _Effects:_ Equivalent to `@_scope_@->@_disassociate_@()`. +::: + +Add the following new section immediately after [exec.simple.counting.token]{.sref}: + +::: add +__Association [exec.simple.counting.assoc]__ + +``` +namespace std::execution { + struct simple_counting_scope::assoc { + assoc() noexcept = default; + + assoc(assoc&& other) noexcept; + + ~assoc() { + + explicit operator bool() const noexcept; + + assoc try_associate() const noexcept; + + private: + simple_counting_scope* @_scope_@ = nullptr; // @_exposition only_@ + }; +} +``` + +`assoc(assoc&& other) noexcept;` + +[1]{.pnum} _Effects:_ Initializes _`scope`_ with `exchange(other.@_scope_@, nullptr)`. + +`~assoc();` + +[2]{.pnum} _Effects:_ If _`scope`_ is `nullptr`, no effect; otherwise, invokes `@_scope_@->@_disassociate_@()`. + +`expliciti operator bool() const noexcept;` + +[3]{.pnum} _Returns:_ `false` if _`scope`_ is `nullptr`, `true` otherwise. + +`assoc try_associate() const noexcept;` + +[4]{.pnum} _Returns:_ A default-initialized `assoc` if _`scope`_ is `nullptr`, `@_scope_@->@_try-associate_@()` +otherwise. +::: + +To the subsection [exec.scope.counting]{.sref}, make the following changes: + +``` +namespace std::execution { + class counting_scope { + public: +``` +::: add +``` + struct assoc { + assoc() noexcept = default; + + assoc(assoc&& other) noexcept; + + ~assoc(); + + explicit operator bool() const noexcept; + + assoc try_associate() const noexcept; + + private: + counting_scope* @_scope_@ = nullptr; // @_exposition only_@ + }; + +``` +::: +``` + struct token { + template + sender auto wrap(Sender&& snd) const noexcept(see below); + @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; +``` +::: rm +``` + void disassociate() const noexcept; +``` +::: +``` + + private: + counting_scope* @_scope_@; // exposition only + }; + + static constexpr size_t max_associations = implementation-defined; + + counting_scope() noexcept; + counting_scope(counting_scope&&) = delete; + ~counting_scope(); + + token get_token() noexcept; + void close() noexcept; + sender auto join() noexcept; + void request_stop() noexcept; + + private: + 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 + + template + bool @_start-join-sender_@(State& state) noexcept; // exposition only + }; +} +``` From 0e224f6c7e9403e12c10b11c883046ce9427883a Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 29 Aug 2025 01:52:42 -0700 Subject: [PATCH 10/15] Fix assignability of the assocs I realized I'd missed `operator=` for the `assoc` types, and then realized I could fix it with clever use of `unique_ptr`. --- 3815.md | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/3815.md b/3815.md index 95ec69d..7f9f49a 100644 --- a/3815.md +++ b/3815.md @@ -805,37 +805,28 @@ __Association [exec.simple.counting.assoc]__ ``` namespace std::execution { struct simple_counting_scope::assoc { - assoc() noexcept = default; - - assoc(assoc&& other) noexcept; - - ~assoc() { - explicit operator bool() const noexcept; assoc try_associate() const noexcept; private: - simple_counting_scope* @_scope_@ = nullptr; // @_exposition only_@ + using @_handle_@ = // @_exposition only_@ + unique_ptr@_disassociate_@(); + })>; + + @_handle_@ @_scope_@; // @_exposition only_@ }; } ``` -`assoc(assoc&& other) noexcept;` - -[1]{.pnum} _Effects:_ Initializes _`scope`_ with `exchange(other.@_scope_@, nullptr)`. - -`~assoc();` +`explicit operator bool() const noexcept;` -[2]{.pnum} _Effects:_ If _`scope`_ is `nullptr`, no effect; otherwise, invokes `@_scope_@->@_disassociate_@()`. - -`expliciti operator bool() const noexcept;` - -[3]{.pnum} _Returns:_ `false` if _`scope`_ is `nullptr`, `true` otherwise. +[1]{.pnum} _Returns:_ `@_scope_@ != nullptr` `assoc try_associate() const noexcept;` -[4]{.pnum} _Returns:_ A default-initialized `assoc` if _`scope`_ is `nullptr`, `@_scope_@->@_try-associate_@()` +[2]{.pnum} _Returns:_ A default-initialized `assoc` if _`scope`_ is `nullptr`, `@_scope_@->@_try-associate_@()` otherwise. ::: @@ -849,18 +840,17 @@ namespace std::execution { ::: add ``` struct assoc { - assoc() noexcept = default; - - assoc(assoc&& other) noexcept; - - ~assoc(); - explicit operator bool() const noexcept; assoc try_associate() const noexcept; private: - counting_scope* @_scope_@ = nullptr; // @_exposition only_@ + using @_handle_@ = // @_exposition only_@ + unique_ptr@_disassociate_@(); + })>; + + @_handle_@ @_scope_@; // @_exposition only_@ }; ``` From 9addcc8e4aa4387df8df64deaf0dbdbad9415b2e Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 29 Aug 2025 23:07:35 -0700 Subject: [PATCH 11/15] Add wording for spawn --- 3815.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/3815.md b/3815.md index 7f9f49a..a2dc1bc 100644 --- a/3815.md +++ b/3815.md @@ -478,7 +478,7 @@ optional> [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: -```c++ +``` return optional(pair(@_token_@, std::move(*@_sndr_@))); ``` @@ -642,6 +642,100 @@ the following lambda: where _`wrap-sender`_ is the type `remove_cvref_t<@_data-type_@>::@_wrap-sender_@.` +## `execution::spawn` + +To the subsection [exec.spawn]{.sref}, make the following changes: + +[5]{.pnum} Let spawn-state be the exposition-only class template: + +``` +namespace std::execution { + template + struct @_spawn-state_@ : @_spawn-state-base_@ { // @_exposition only_@ + using @_op-t_@ = connect_result_t; // @_exposition only_@ + + @_spawn-state_@(Alloc alloc, Sender&& sndr, Token token); // @_exposition only_@ + void @_complete_@() noexcept override; // @_exposition only_@ + void @_run_()[\ noexcept]{.add}@; // @_exposition only_@ + + private: + using @_alloc-t_@ = // @_exposition only_@ + typename allocator_traits::template rebind_alloc<@_spawn-state_@>; +``` +::: add +``` + using @_assoc-t_@ = // @_exposition only_@ + remove_cvref_t().try_associate())>; +``` +::: +``` + + @_alloc-t_@ @_alloc_@; // @_exposition only_@ + @_op-t_@ @_op_@; // @_exposition only_@ + @[Token]{.rm}[_assoc-t_]{.add}\ _[token]{.rm}[assoc]{.add}_@; // @_exposition only_@ + +``` +::: rm +``` + void @_destroy_@() noexcept; // @_exposition only_@ +``` +::: +``` + }; +} +``` + +`@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token);` + +::: rm +[6]{.pnum} _Effects:_ Initializes _`alloc`_ with `alloc`, _`token`_ with `token`, and _`op`_ with: + +``` +connect(std::move(sndr), @_spawn-receiver_@(this)) +``` +::: +::: add +[6]{.pnum} _Effects:_ Initializes _`alloc`_ with `std::move(alloc)`, _`op`_ with +`connect(std::move(sndr), @_spawn-receiver_@(this))`, and _`assoc`_ with `token.try_associate()`. +::: + +`void @_run_()[\ noexcept]{.add}@;` + +[7]{.pnum} _Effects:_ Equivalent to: + +``` + if (@_token_@.try_associate()) + start(@_op_@); + else + @_[destroy]{.rm}[complete]{.add}_@(); +``` + +`void @_complete_@() noexcept override;` + +[8]{.pnum} _Effects:_ Equivalent to: + +::: rm +``` + auto token = std::move(this->@_token_@); + + @_destroy_@(); + + token.disassociate(); +``` + +`void @_destroy_@() noexcept;` + +[9]{.pnum} _Effects:_ Equivalent to: + +::: +``` + @[auto\ assoc\ =\ std::move(this->_assoc_);]{.add}@ + auto alloc = std::move(this->@_alloc_@); + + allocator_traits<@_alloc-t_@>::destroy(alloc, this); + allocator_traits<@_alloc-t_@>::deallocate(alloc, this, 1); +``` + ## Async scope concepts At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes @@ -651,7 +745,7 @@ At the beginning of subsection [exec.scope.concepts]{.sref}, make the following [1]{.pnum} The `scope_assocation` concept defines the requirements on a type `Assoc` that, when engaged, owns an association with an async scope. -```cpp +``` namespace std::execution { template concept scope_association = From d9f609683827709855a449db9ca589e01b20188a Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Fri, 29 Aug 2025 23:40:30 -0700 Subject: [PATCH 12/15] Add wording for spawn_future --- 3815.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/3815.md b/3815.md index a2dc1bc..394b6c8 100644 --- a/3815.md +++ b/3815.md @@ -642,6 +642,91 @@ the following lambda: where _`wrap-sender`_ is the type `remove_cvref_t<@_data-type_@>::@_wrap-sender_@.` +## `execution::spawn_future` + +To the subsection [exec.spawn.future]{.sref}, make the following changes: + +[7]{.pnum} Let _`spawn-future-state`_ be the exposition-only class template: + +``` +namespace std::execution { + template + struct @_spawn-future-state_@ // @_exposition only_@ + : @_spawn-future-state-base_@>> { + using @_sigs-t_@ = // @_exposition only_@ + completion_signatures_of_t<@_future-spawned-sender_@>; + using @_receiver-t_@ = // @_exposition only_@ + @_spawn-future-receiver_@<@_sigs-t_@>; + using @_op-t_@ = // @_exposition only_@ + connect_result_t<@_future-spawned-sender_@, @_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)),]{.rm}@ + @_assoc[iated]{.rm}_@(token.try_associate()) { + if (@[associated]{.rm}[_assoc_]{.add}@) + start(@_op_@); + else + set_stopped(@_receiver-t_@(this)); + } + + void @_complete_@() noexcept override; // @_exposition only_@ + void @_consume_@(receiver auto& rcvr) noexcept; // @_exposition only_@ + void @_abandon_@() noexcept; // @_exposition only_@ + + private: + using @_alloc-t_@ = // @_exposition only_@ + typename allocator_traits::template rebind_alloc<@_spawn-future-state_@>; +``` +::: add +``` + using @_assoc-t_@ = // @_exposition only_@ + remove_cvref_t().try_associate())>; +``` +::: +``` + + @_alloc-t_@ @_alloc_@; // @_exposition only_@ + @_ssource-t_@ @_ssource_@; // @_exposition only_@ + @_op-t_@ @_op_@; // @_exposition only_@ + @[Token]{.rm}[_assoc-t_]{.add}\ _[token]{.rm}[assoc]{.add}_@; // @_exposition only_@ +``` +::: rm +``` + bool @_associated_@; // @_exposition only_@ +``` +::: +``` + + void @_destroy_@() noexcept; // @_exposition only_@ + }; +} +``` + +... + +`void @_destroy_@() noexcept;` + +[12]{.pnum} _Effects:_ Equivalent to: + +``` +@[auto\ token\ =\ std::move(this->_token_);]{.rm}@ +@[bool\ associated\ =\ this->_associated_;]{.rm}@ +@[auto\ assoc\ =\ std::move(this->_assoc_);]{.add}@ +@[{]{.rm}@ +@[\ \ ]{.rm}@auto alloc = std::move(this->@_alloc_@); + +@[\ \ ]{.rm}@allocator_traits::destroy(alloc, this); +@[\ \ ]{.rm}@allocator_traits::deallocate(alloc, this, 1); +@[}]{.rm}@ + +@[if\ (associated)]{.rm}@ +@[\ \ token.disassociate();]{.rm}@ +``` + ## `execution::spawn` To the subsection [exec.spawn]{.sref}, make the following changes: From 07398450a596a4f89de09d0e3eb582b9e90c1e48 Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Sat, 30 Aug 2025 01:19:28 -0700 Subject: [PATCH 13/15] Switch from block to inline edit marks This diff changes from using the block-level edit markers like this: ``` ::: add // additions here ::: ``` to inline edit markers like this: ``` [/* additions here */]{.add} ``` because I think the resulting doc looks nicer. --- 3815.md | 526 ++++++++++++++++++-------------------------------------- 1 file changed, 165 insertions(+), 361 deletions(-) diff --git a/3815.md b/3815.md index 394b6c8..7c02aed 100644 --- a/3815.md +++ b/3815.md @@ -311,14 +311,9 @@ To the `` synopsis [execution.syn]{.sref}, make the following change: ``` // [exec.scope] // [exec.scope.concepts], scope concepts -``` -::: add -``` - template - concept scope_association = @_see below_@; -``` -::: -``` + @[template\ ]{.add}@ + @[concept\ scope_association\ =\ _see\ below_;]{.add}@ + template concept scope_token = @_see below_@; ``` @@ -335,38 +330,19 @@ template struct @_associate-data_@ { // @_exposition only_@ using @_wrap-sender_@ = // @_exposition only_@ remove_cvref_t().wrap(declval()))>; -``` -::: add -``` - using @_assoc-t_@ = // @_exposition only_@ - decltype(declval().try_associate()); + @[using\ _assoc-t_\ =]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[decltype(declval().try_associate());]{.add}@ - using @_sender-ref_@ = // @_exposition only_@ - unique_ptr<@_wrap-sender_@, decltype([](auto* p) noexcept { destroy_at(p); })>; -``` -::: -``` + @[using\ _sender-ref_\ =]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[unique_ptr<_wrap-sender_,\ decltype(\[\](auto*\ p)\ noexcept\ {\ destroy_at(p);\ })>;]{.add}@ explicit @_associate-data_@(Token t, Sender&& s) -``` -::: rm -``` - : @_sndr_@(t.wrap(std::forward(s))), - @_token_@(t) { - if (!@_token_@.try_associate()) - @_sndr_@.reset(); -``` -::: -::: add -``` - : @_sndr_@(t.wrap(std::forward(s))) { - @_sender-ref_@ guard{addressof(@_sndr_@)}; + : @_sndr_@(t.wrap(std::forward(s)))@[,]{.rm}[\ {]{.add}@ + @[_token_(t)\ {]{.rm}@ + @[_sender-ref_\ guard{addressof(_sndr_)};]{.add}@ - if (@_assoc_@ = t.try_associate()) - (void)guard.release(); -``` -::: -``` + if (@[!_token_]{.rm}[_assoc_\ =\ t]{.add}@.try_associate()) + @[_sndr_.reset()]{.rm}[(void)guard.release()]{.add}@; } @_associate-data_@(const @_associate-data_@& other) @@ -378,39 +354,20 @@ struct @_associate-data_@ { // @_expositio ~@_associate-data_@(); -``` -::: rm -``` - optional> - release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); -``` -::: -::: add -``` - pair<@_assoc-t_@, @_sender-ref_@> release() && noexcept; -``` -::: -``` + @[optional>]{.rm}@ + @[pair<_assoc-t_,\ _sender-ref_>]{.add}@ + release() && noexcept@[(is_nothrow_move_constructible_v<_wrap-sender_>)]{.rm}@; private: -``` -::: rm -``` - optional<@_wrap-sender_@> @_sndr_@; // @_exposition only_@ - Token @_token_@; // @_exposition only_@ -``` -::: -::: add -``` - @_associate-data_@(pair<@_assoc-t_@, @_scope-ref_@> parts); + @[optional<_wrap-sender_>\ _sndr_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ + @[Token\ _token_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ - @_assoc-t_@ @_assoc_@; // @_exposition only_@ - union { - @_wrap-sender_@ @_sndr_@; // @_exposition only_@ - }; -``` -::: -``` + @[_associate-data_(pair<_assoc-t_,\ _scope-ref_>\ parts);]{.add}@ @[//\ _exposition\ only_]{.add}@ + + @[_assoc-t_\ _assoc_;]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[union\ {]{.add}@ + @[_wrap-sender_\ _sndr_;]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[};]{.add}@ }; template @@ -430,67 +387,47 @@ to]{.add} `true` if and only if an association was successfully made and is owne [4]{.pnum} _Constraints:_ `copy_constructible<@_wrap-sender_@>` is `true`. -::: rm -[5]{.pnum} _Effects:_ Value-initializes _`sndr`_ and initializes _`token`_ with `other.@_token_@`. If +[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. -::: -::: add -[5]{.pnum} _Effects:_ Initializes _`assoc`_ with `other.@_assoc_@.try_associate()`. If _`assoc`_ contextually converts -to `false`, no further effects; otherwise, initializes _`sndr`_ with `other.@_sndr_@`. -::: +`@_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} ``` @_associate-data_@(@_associate-data_@&& other) 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()`. -::: -::: add -[6]{.pnum} _Effects:_ Equivalent to `@_associate-data_@(std::move(other).release())`. +[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} -`@_associate-data_@(pair<@_assoc-t_@, @_sender-ref_@> parts);` +[`@_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)`. -::: +[[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} `~@_associate-data_@();` -::: rm -[7]{.pnum} _Effects:_ If `@_sndr_@.has_value()` returns `false` then no effect; otherwise, invokes `@_sndr_@.reset()` -before invoking `@_token_@.disassociate()`. -::: -::: add -[8]{.pnum} _Effects:_ If _`assoc`_ contextually converts to `false` then no effect; otherwise, destroys _`sndr`_. -::: - -::: rm -``` -optional> - release() && noexcept(is_nothrow_move_constructible_v<@_wrap-sender_@>); -``` +[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} -[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: +[`optional>`]{.rm}\ +[`pair<@_assoc-t_@, @_scope-ref_@>`]{.add}\ +`@\ \ @release() && noexcept@[(is_nothrow_move_constructible_v<_wrap-sender_>)]{.rm}@;` -``` -return optional(pair(@_token_@, std::move(*@_sndr_@))); -``` +[9]{.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} -[9]{.pnum} _Postconditions:_ _`sndr`_ does not contain a value. -::: -::: add -`pair<@_assoc-t_@, @_scope-ref_@> release() && noexcept;` +[`return optional(pair(@_token_@, std::move(*@_sndr_@)));`]{.rm} -[9]{.pnum} _Effects:_ Constructs an object `u` of type _`scope-ref`_ that is initialized with `nullptr` if _`assoc`_ +[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)}`. -::: +`pair{std::move(@_assoc_@), std::move(u)}`.]{.add} + +[[9]{.pnum} _Postconditions:_ _`sndr`_ does not contain a value.]{.rm} [10]{.pnum} 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` does not satisfy `scope_token`, @@ -504,97 +441,63 @@ the following lambda: [](Sndr&& sndr, Rcvr& rcvr) noexcept(@_see below_@) { auto@[&&]{.add}@ [_, data] = std::forward(sndr); -``` -::: rm -``` - auto dataParts = std::move(data).release(); + @[auto\ dataParts\ =\ std::move(data).release();]{.rm}@ + + @[using\ scope_token\ =\ decltype(dataParts->first);]{.rm}@ + @[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 scope_token = decltype(dataParts->first); - using wrap_sender = decltype(dataParts->second); -``` -::: -::: add -``` - using associate_data_t = remove_cvref_t; - using assoc_t = typename associate_data_t::@_assoc-t_@; - using sender_ref_t = typename associate_data_t::@_sender-ref_@; -``` -::: -``` using op_t = connect_result_t<@[wrap_sender]{.rm}[typename\ sender_ref_t::element_type]{.add}@, Rcvr>; struct op_state { @[bool]{.rm}[assoc_t]{.add}@ @_assoc_[_iated_\ =\ false]{.rm}@; // @_exposition only_@ union { Rcvr* @_rcvr_@; // @_exposition only_@ -``` -::: rm -``` - struct { - scope_token @_token_@; // @_exposition only_@ - op_t @_op_@; // @_exposition only_@ - } @_assoc_@; // @_exposition only_@ -``` -::: -::: add -``` - op_t @_op_@; // @_exposition only_@ -``` -::: -``` + @[struct\ {]{.rm}@ + @[scope_token\ _token_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ + @[\ \ ]{.rm}@op_t @_op_@; // @_exposition only_@ + @[}\ _assoc_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ }; -``` -::: rm -``` - explicit op_state(Rcvr& r) noexcept - : @_rcvr_@(addressof(r)) {} - - explicit op_state(scope_token tkn, wrap_sender&& sndr, Rcvr& r) try - : @_associated_@(true), - @_assoc_@(tkn, connect(std::move(sndr), std::move(r))) { - } - catch (...) { - tkn.disassociate(); - throw; - } -``` -::: -::: add -``` - explicit op_state(pair 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 - : op_state(associate_data_t(ad).release(), r) {} -``` -::: -``` + @[explicit\ op_state(Rcvr&\ r)\ noexcept]{.rm}@ + @[:\ _rcvr_(addressof(r))\ {}]{.rm}@ + + @[explicit\ op_state(scope_token\ tkn,\ wrap_sender&&\ sndr,\ Rcvr&\ r)\ try]{.rm}@ + @[:\ _associated_(true),]{.rm}@ + @[_assoc_(tkn,\ connect(std::move(sndr),\ std::move(r)))\ {]{.rm}@ + @[}]{.rm}@ + @[catch\ (...)\ {]{.rm}@ + @[tkn.disassociate();]{.rm}@ + @[throw;]{.rm}@ + @[}]{.rm}@ + + @[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}@ + @[else]{.add}@ + @[rcvr\ =\ addressof(r);]{.add}@ + @[}]{.add}@ + + @[explicit\ op_state(associate_data_t&&\ ad,\ Rcvr&\ r)]{.add}@ + @[:\ op_state(std::move(ad).release(),\ r)\ {}]{.add}@ + + @[explicit\ op_state(const\ associate_data_t&\ ad,\ Rcvr&\ r)]{.add}@ + @[requires\ copy_constructible]{.add}@ + @[:\ op_state(associate_data_t(ad).release(),\ r)\ {}]{.add}@ op_state(op_state&&) = delete; ~op_state() { if (@_assoc[iated]{.rm}_@)@[\ {]{.rm}@ @[_assoc_.]{.rm}_op_@.~op_t(); -``` -::: rm -``` - @_assoc_@.@_token_@.disassociate(); - @_assoc_@.@_token_@.~scope_token(); - } -``` -::: -``` + @[_assoc_._token_.disassociate();]{.rm}@ + @[_assoc_._token_.~scope_token();]{.rm}@ + @[}]{.rm}@ } void @_run_@() noexcept { // @_exposition only_@ @@ -605,40 +508,19 @@ the following lambda: } }; -``` -::: rm -``` - if (dataParts) - return op_state{std::move(dataParts->first), std::move(dataParts->second), rcvr}; - else - return op_state{rcvr}; -``` -::: -::: add -``` - return op_state{std::forward_like(data), rcvr}; -``` -::: -``` + @[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}; } ``` [14]{.pnum} The expression in the `noexcept` clause of `@_impls-for_@::@_get-state_@` is -::: rm -``` - is_nothrow_constructible_v, Sndr> && - is_nothrow_move_constructible_v<@_wrap-sender_@> && -``` -::: -::: add -``` - (is_rvalue_reference_v || is_nothrow_constructible_v, Sndr>) && -``` -::: -``` - @_nothrow-callable_@ -``` +`@\ \ [is_nothrow_constructible_v,\ Sndr>\ &&]{.rm}@`\ +`@\ \ [is_nothrow_move_constructible_v<_wrap-sender_>\ &&]{.rm}@`\ +`@\ \ [(is_rvalue_reference_v\ ||\ is_nothrow_constructible_v,\ Sndr>)\ &&]{.add}@`\ +`@\ \ _nothrow-callable_@` where _`wrap-sender`_ is the type `remove_cvref_t<@_data-type_@>::@_wrap-sender_@.` @@ -680,26 +562,14 @@ namespace std::execution { private: using @_alloc-t_@ = // @_exposition only_@ typename allocator_traits::template rebind_alloc<@_spawn-future-state_@>; -``` -::: add -``` - using @_assoc-t_@ = // @_exposition only_@ - remove_cvref_t().try_associate())>; -``` -::: -``` + @[using\ _assoc-t_\ =]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[remove_cvref_t().try_associate())>;]{.add}@ @_alloc-t_@ @_alloc_@; // @_exposition only_@ @_ssource-t_@ @_ssource_@; // @_exposition only_@ @_op-t_@ @_op_@; // @_exposition only_@ @[Token]{.rm}[_assoc-t_]{.add}\ _[token]{.rm}[assoc]{.add}_@; // @_exposition only_@ -``` -::: rm -``` - bool @_associated_@; // @_exposition only_@ -``` -::: -``` + @[bool\ _associated_;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ void @_destroy_@() noexcept; // @_exposition only_@ }; @@ -746,43 +616,23 @@ namespace std::execution { private: using @_alloc-t_@ = // @_exposition only_@ typename allocator_traits::template rebind_alloc<@_spawn-state_@>; -``` -::: add -``` - using @_assoc-t_@ = // @_exposition only_@ - remove_cvref_t().try_associate())>; -``` -::: -``` + @[using\ _assoc-t_\ =]{.add}@ @[//\ _exposition\ only_]{.add}@ + @[remove_cvref_t().try_associate())>;]{.add}@ @_alloc-t_@ @_alloc_@; // @_exposition only_@ @_op-t_@ @_op_@; // @_exposition only_@ @[Token]{.rm}[_assoc-t_]{.add}\ _[token]{.rm}[assoc]{.add}_@; // @_exposition only_@ -``` -::: rm -``` - void @_destroy_@() noexcept; // @_exposition only_@ -``` -::: -``` + @[void\ _destroy_()\ noexcept;]{.rm}@ @[//\ _exposition\ only_]{.rm}@ }; } ``` `@_spawn-state_@(Alloc alloc, Sender&& sndr, Token token);` -::: rm -[6]{.pnum} _Effects:_ Initializes _`alloc`_ with `alloc`, _`token`_ with `token`, and _`op`_ with: - -``` -connect(std::move(sndr), @_spawn-receiver_@(this)) -``` -::: -::: add -[6]{.pnum} _Effects:_ Initializes _`alloc`_ with `std::move(alloc)`, _`op`_ with -`connect(std::move(sndr), @_spawn-receiver_@(this))`, and _`assoc`_ with `token.try_associate()`. -::: +[6]{.pnum} _Effects:_ [Initializes _`alloc`_ with `alloc`, _`token`_ with `token`, and _`op`_ with:\ +`@\ \ @connect(std::move(sndr), @_spawn-receiver_@(this))`]{.rm} [Initializes _`alloc`_ with `std::move(alloc)`, _`op`_ +with `connect(std::move(sndr), @_spawn-receiver_@(this))`, and _`assoc`_ with `token.try_associate()`.]{.add} `void @_run_()[\ noexcept]{.add}@;` @@ -799,20 +649,18 @@ connect(std::move(sndr), @_spawn-receiver_@(this)) [8]{.pnum} _Effects:_ Equivalent to: -::: rm ``` - auto token = std::move(this->@_token_@); + @[auto\ token\ =\ std::move(this->_token_);]{.rm}@ - @_destroy_@(); + @[_destroy_();]{.rm}@ - token.disassociate(); + @[token.disassociate();]{.rm}@ ``` -`void @_destroy_@() noexcept;` +[`void @_destroy_@() noexcept;`]{.rm} -[9]{.pnum} _Effects:_ Equivalent to: +[[9]{.pnum} _Effects:_ Equivalent to:]{.rm} -::: ``` @[auto\ assoc\ =\ std::move(this->_assoc_);]{.add}@ auto alloc = std::move(this->@_alloc_@); @@ -825,26 +673,22 @@ connect(std::move(sndr), @_spawn-receiver_@(this)) At the beginning of subsection [exec.scope.concepts]{.sref}, make the following changes -::: add - -[1]{.pnum} The `scope_assocation` concept defines the requirements on a type `Assoc` that, when engaged, owns an -association with an async scope. +[[1]{.pnum} The `scope_assocation` concept defines the requirements on a type `Assoc` that, when engaged, owns an +association with an async scope.]{.add} ``` -namespace std::execution { - template - concept scope_association = - movable && - default_initializable && - requires(const Assoc assoc) { - { static_cast(assoc) } noexcept; - { assoc.try_associate() } -> same_as; - }; -} +@[namespace\ std::execution\ {]{.add}@ + @[template\ ]{.add}@ + @[concept\ scope_association\ =]{.add}@ + @[movable\ &&]{.add}@ + @[default_initializable\ &&]{.add}@ + @[requires(const\ Assoc\ assoc)\ {]{.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. @@ -857,19 +701,9 @@ namespace std::execution { concept scope_token = copyable && requires(const Token token) { -``` -::: rm -``` - { token.try_associate() } -> same_as; - { token.disassociate() } noexcept -> same_as; -``` -::: -::: add -``` - { token.try_associate() } -> scope_association; -``` -::: -``` + @[{\ token.try_associate()\ }\ ->\ same_as;]{.rm}@ + @[{\ token.disassociate()\ }\ noexcept\ ->\ same_as;]{.rm}@ + @[{\ token.try_associate()\ }\ ->\ scope_association;]{.add}@ { token.wrap(declval<@_test-sender_@>()) } -> sender_in<@_test-env_@>; }; } @@ -886,14 +720,9 @@ namespace std::execution { // [exec.simple.counting.token], token struct token; -``` -::: add -``` - // [exec.simple.counting.assoc], assoc - struct assoc; -``` -::: -``` + @[//\ [exec.simple.counting.assoc],\ assoc]{.add}@ + @[struct\ assoc;]{.add}@ + static constexpr size_t max_associations = @_implementation-defined_@; // [exec.simple.counting.ctor], constructor and destructor @@ -928,14 +757,9 @@ 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. -::: rm -[6]{.pnum} _Returns:_ `true` if _`count`_ was incremented, `false` otherwise. -::: -::: add -[6]{.pnum} _Returns:_ An object `a` of type `simple_counting_scope::assoc` such that `a.@_scope_@` is `this` if -_`count`_ was incremented, `nullptr` otherwise. -::: - +[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} To the subsection [exec.simple.counting.token]{.sref}, make the following changes: @@ -945,13 +769,8 @@ namespace std::execution { template Sender&& wrap(Sender&& snd) const noexcept; @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; -``` -::: rm -``` - void disassociate() const noexcept; -``` -::: -``` + + @[void\ disassociate()\ const\ noexcept;]{.rm}@ private: simple_counting_scope* @_scope_@; // exposition only @@ -970,44 +789,40 @@ template [2]{.pnum} _Effects:_ Equivalent to: `return @_scope_@->@_try-associate_@();` -::: rm -`void disassociate() const noexcept;` +[`void disassociate() const noexcept;`]{.rm} -[3]{.pnum} _Effects:_ Equivalent to `@_scope_@->@_disassociate_@()`. -::: +[[3]{.pnum} _Effects:_ Equivalent to `@_scope_@->@_disassociate_@()`.]{.rm} Add the following new section immediately after [exec.simple.counting.token]{.sref}: -::: add -__Association [exec.simple.counting.assoc]__ +[__Association [exec.simple.counting.assoc]__]{.add} ``` -namespace std::execution { - struct simple_counting_scope::assoc { - explicit operator bool() const noexcept; +@[namespace\ std::execution\ {]{.add}@ + @[struct\ simple_counting_scope::assoc\ {]{.add}@ + @[explicit\ operator\ bool()\ const\ noexcept;]{.add}@ - assoc try_associate() const noexcept; + @[assoc\ try_associate()\ const\ noexcept;]{.add}@ - private: - using @_handle_@ = // @_exposition only_@ - unique_ptr@_disassociate_@(); - })>; + @[private:]{.add}@ + @[using\ _handle_\ =\ //\ _exposition\ only_]{.add}@ + @[unique_ptr_disassociate_();]{.add}@ + @[})>;]{.add}@ - @_handle_@ @_scope_@; // @_exposition only_@ - }; -} + @[_handle_\ _scope_;\ //\ _exposition\ only_]{.add}@ + @[};]{.add}@ +@[}]{.add}@ ``` -`explicit operator bool() const noexcept;` +[`explicit operator bool() const noexcept;`]{.add} -[1]{.pnum} _Returns:_ `@_scope_@ != nullptr` +[[1]{.pnum} _Returns:_ `@_scope_@ != nullptr`]{.add} -`assoc try_associate() const noexcept;` +[`assoc try_associate() const noexcept;`]{.add} -[2]{.pnum} _Returns:_ A default-initialized `assoc` if _`scope`_ is `nullptr`, `@_scope_@->@_try-associate_@()` -otherwise. -::: +[[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: @@ -1015,37 +830,26 @@ To the subsection [exec.scope.counting]{.sref}, make the following changes: namespace std::execution { class counting_scope { public: -``` -::: add -``` - struct assoc { - explicit operator bool() const noexcept; + @[struct\ assoc\ {]{.add}@ + @[explicit\ operator\ bool()\ const\ noexcept;]{.add}@ - assoc try_associate() const noexcept; + @[assoc\ try_associate()\ const\ noexcept;]{.add}@ - private: - using @_handle_@ = // @_exposition only_@ - unique_ptr@_disassociate_@(); - })>; + @[private:]{.add}@ + @[using\ @_handle_@\ =\ //\ @_exposition\ only_@]{.add}@ + @[unique_ptr@_disassociate_@();]{.add}@ + @[})>;]{.add}@ - @_handle_@ @_scope_@; // @_exposition only_@ - }; + @[@_handle_@ @_scope_@;\ //\ @_exposition\ only_@]{.add}@ + @[};]{.add}@ -``` -::: -``` struct token { template sender auto wrap(Sender&& snd) const noexcept(see below); @[bool]{.rm}[assoc]{.add}@ try_associate() const noexcept; -``` -::: rm -``` - void disassociate() const noexcept; -``` -::: -``` + + @[void\ disassociate()\ const\ noexcept;]{.rm}@ private: counting_scope* @_scope_@; // exposition only From f3b9fb57497d85b73b7c73ae0093471ed9dcfc3b Mon Sep 17 00:00:00 2001 From: Ian Petersen Date: Mon, 1 Sep 2025 15:36:47 -0700 Subject: [PATCH 14/15] Minor formatting change --- 3815.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3815.md b/3815.md index 7c02aed..180c3d5 100644 --- a/3815.md +++ b/3815.md @@ -487,7 +487,7 @@ the following lambda: @[:\ op_state(std::move(ad).release(),\ r)\ {}]{.add}@ @[explicit\ op_state(const\ associate_data_t&\ ad,\ Rcvr&\ r)]{.add}@ - @[requires\ copy_constructible]{.add}@ + @[requires\ copy_constructible\]{.add}@ @[:\ op_state(associate_data_t(ad).release(),\ r)\ {}]{.add}@ op_state(op_state&&) = delete; From c47dc622413671d704f51d11f56f81438aed78c6 Mon Sep 17 00:00:00 2001 From: "Jessica Wong (Seattle)" Date: Mon, 1 Sep 2025 15:41:53 -0700 Subject: [PATCH 15/15] Change D to P paper --- 3815.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3815.md b/3815.md index 180c3d5..8304ad4 100644 --- a/3815.md +++ b/3815.md @@ -1,6 +1,6 @@ --- title: Add `scope_association` concept to P3149 -document: D3815R0 +document: P3815R0 date: today audience: - LEWG Library Evolution Working Group