diff --git a/docs/eelis_link_filter.pl b/docs/eelis_link_filter.pl new file mode 100755 index 000000000..ac6e3bd6a --- /dev/null +++ b/docs/eelis_link_filter.pl @@ -0,0 +1,31 @@ +#============================================================================= +# Copyright 2026 NVIDIA Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= +# +# Doxygen INPUT_FILTER. Turns references to C++ working-draft stable names of +# the form [exec.xyz] into hyperlinks to the corresponding section on +# https://eel.is/c++draft. The displayed text is left unchanged ([exec.xyz]), +# so the convention documented in CONTRIBUTING-docs.md still reads naturally in +# the source. Doxygen renders the emitted as a in its XML, +# which Breathe turns into an external hyperlink in the Sphinx output. +# +# Doxygen invokes this as `perl eelis_link_filter.pl `, so the file +# arrives on @ARGV and is streamed through <>. Everything that is not a stable +# name is passed through verbatim. + +while (<>) { + s{\[(exec(?:\.[a-z0-9_]+)+)\]}{[$1]}g; + print; +} diff --git a/include/stdexec/__detail/__spawn.hpp b/include/stdexec/__detail/__spawn.hpp index 577296a80..237dd9fc0 100644 --- a/include/stdexec/__detail/__spawn.hpp +++ b/include/stdexec/__detail/__spawn.hpp @@ -122,175 +122,179 @@ namespace STDEXEC } } }; + } // namespace __spawn - //! @brief A sender consumer that eagerly starts a sender and ties its - //! lifetime to an *async scope*. - //! - //! @c spawn is the standard "fire-and-forget into a scope" consumer. - //! You give it a sender, a @c scope_token (a handle to an async scope), - //! and optionally an environment, and @c spawn: - //! - //! 1. allocates an operation state on the heap (using an allocator - //! queried from the environment or the sender's own environment), - //! 2. tries to associate the resulting operation with the scope via - //! token.try_associate(), - //! 3. if the association succeeds, eagerly @c start s the operation, - //! and on completion deallocates the state and releases the scope - //! association. - //! - //! If association fails (typically because the scope has already begun - //! shutting down), @c spawn destroys the state and returns without - //! starting the operation. The result of the sender, if any, is - //! discarded — @c spawn returns @c void. - //! - //! See [exec.spawn] in the C++26 working draft for the normative - //! specification. - //! - //! @code{.cpp} - //! exec::async_scope scope; - //! - //! stdexec::spawn(stdexec::just(42) | stdexec::then([](int x) { - //! std::println("background work produced {}", x); - //! }), scope.get_token()); - //! - //! // Later, before destroying scope: - //! stdexec::sync_wait(scope.join()); - //! @endcode + //! @brief A sender consumer that eagerly starts a sender and ties its + //! lifetime to an *async scope*. + //! + //! @c spawn is the standard "fire-and-forget into a scope" consumer. + //! You give it a sender, a @c scope_token (a handle to an async scope), + //! and optionally an environment, and @c spawn: + //! + //! 1. allocates an operation state on the heap (using an allocator + //! queried from the environment or the sender's own environment), + //! 2. tries to associate the resulting operation with the scope via + //! token.try_associate(), + //! 3. if the association succeeds, eagerly @c start s the operation, + //! and on completion deallocates the state and releases the scope + //! association. + //! + //! If association fails (typically because the scope has already begun + //! shutting down), @c spawn destroys the state and returns without + //! starting the operation. The result of the sender, if any, is + //! discarded — @c spawn returns @c void. + //! + //! See [exec.spawn] in the C++26 working draft for the normative + //! specification. + //! + //! @code{.cpp} + //! exec::async_scope scope; + //! + //! stdexec::spawn(stdexec::just(42) | stdexec::then([](int x) { + //! std::println("background work produced {}", x); + //! }), scope.get_token()); + //! + //! // Later, before destroying scope: + //! stdexec::sync_wait(scope.join()); + //! @endcode + //! + //! **Completion requirements.** + //! + //! The argument sender must not be able to complete with @c set_error + //! — @c spawn cannot deliver an error to a non-existent caller. The + //! @c requires clause enforces this with a + //! __never_sends check; the diagnostic + //! overload says "spawn expects a sender that cannot fail" if the check + //! fires. + //! + //! Successful and stopped completions are both accepted; their results + //! are discarded. + //! + //! **Scope semantics.** + //! + //! The scope is the *owner of lifetime* for the spawned operation. + //! Calling code is expected to eventually @c join() the scope (or + //! otherwise wait for all spawned work to drain) before destroying it + //! — typically once at program shutdown, or once per logical unit of + //! related background work. + //! + //! @c spawn is the canonical fire-and-forget consumer for any work + //! that has a clear "owning context" (a request, a session, a worker). + //! For top-level work with no owning scope, use @c exec::start_detached + //! (an stdexec extension). For fire-and-forget work whose completion + //! you want to *observe* (without blocking), use + //! @c stdexec::spawn_future. + //! + //! @see stdexec::spawn_future — like @c spawn, but returns a sender that completes + //! when the spawned work completes + //! @see exec::start_detached — scope-less fire-and-forget (extension) + //! @see stdexec::sync_wait — top-level synchronous wait that returns the result + struct spawn_t + { + private: + template + using _wrapped_sender_t = decltype(__declval<_Token&>().wrap(__declval<_Sender>())); + + template + using __choose_senv_t = __result_of<__spawn_common::__choose_senv, _Env, env_of_t<_Sender>>; + + template + using _spawn_sndr_impl_t = __result_of>; + + template + using _spawn_sndr_t = _spawn_sndr_impl_t<_wrapped_sender_t<_Sender, _Token>, _Env>; + + public: + //! @brief Spawn @c __sndr into the scope identified by @c __tkn, using + //! a default (empty) environment. //! - //! **Completion requirements.** + //! Equivalent to spawn(__sndr, __tkn, env<>{}). //! - //! The argument sender must not be able to complete with @c set_error - //! — @c spawn cannot deliver an error to a non-existent caller. The - //! @c requires clause enforces this with a - //! __never_sends check; the diagnostic - //! overload says "spawn expects a sender that cannot fail" if the check - //! fires. + //! @tparam _Sender A sender type with no @c set_error_t completions. + //! @tparam _Token A type satisfying @c stdexec::scope_token. + //! @param __sndr The sender to launch. + //! @param __tkn The scope token identifying the owning scope. + template + void operator()(_Sender&& __sndr, _Token __tkn) const + { + return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{}); + } + + // Hidden from Doxygen: this diagnostic-only overload shares its signature + // with the primary three-argument overload above (they differ only by a + // constraint), which the Sphinx C++ domain cannot disambiguate. +#if !defined(STDEXEC_DOXYGEN_INVOKED) + //! @brief Diagnostic overload — selected when the sender's completion + //! signatures include @c set_error_t. Emits a @c static_assert + //! explaining that @c spawn expects a sender that cannot fail. //! - //! Successful and stopped completions are both accepted; their results - //! are discarded. + //! Not normally called; the @c requires clause on the primary overload + //! steers compilation here on a constraint failure. + template + void operator()(_Sender&&, _Token, _Env&&) const + { + using _spawn_sndr_t = spawn_t::_spawn_sndr_t<_Sender, _Token, _Env>; + static_assert(sender_in<_spawn_sndr_t, _Env> + && __never_sends, + "spawn expects a sender that cannot fail"); + } +#endif // !defined(STDEXEC_DOXYGEN_INVOKED) + + //! @brief Spawn @c __sndr into the scope identified by @c __tkn, using + //! the allocator queried from @c __env. //! - //! **Scope semantics.** + //! Allocates the operation state on the heap (using + //! stdexec::get_allocator(__env), falling back to + //! @c std::allocator), associates with the scope via + //! __tkn.try_associate(), and on success @c start s the + //! operation. On completion the state is destroyed and deallocated. //! - //! The scope is the *owner of lifetime* for the spawned operation. - //! Calling code is expected to eventually @c join() the scope (or - //! otherwise wait for all spawned work to drain) before destroying it - //! — typically once at program shutdown, or once per logical unit of - //! related background work. + //! @tparam _Sender A sender type with no @c set_error_t completions. + //! @tparam _Token A type satisfying @c stdexec::scope_token. + //! @tparam _Env An environment type; queried for an allocator. //! - //! @c spawn is the canonical fire-and-forget consumer for any work - //! that has a clear "owning context" (a request, a session, a worker). - //! For top-level work with no owning scope, use @c exec::start_detached - //! (an stdexec extension). For fire-and-forget work whose completion - //! you want to *observe* (without blocking), use - //! @c stdexec::spawn_future. + //! @param __sndr The sender to launch. + //! @param __tkn The scope token identifying the owning scope. + //! @param __env Environment used both for allocator lookup and as + //! the spawned operation's receiver environment. //! - //! @see stdexec::spawn_future — like @c spawn, but returns a sender that completes - //! when the spawned work completes - //! @see exec::start_detached — scope-less fire-and-forget (extension) - //! @see stdexec::sync_wait — top-level synchronous wait that returns the result - struct spawn_t + //! @pre @c __sndr must not be able to complete with @c set_error + //! (enforced by the @c requires clause). + template + requires __never_sends, _Env> + void operator()(_Sender&& __sndr, _Token __tkn, _Env&& __env) const { - private: - template - using _wrapped_sender_t = decltype(__declval<_Token&>().wrap(__declval<_Sender>())); - - template - using __choose_senv_t = __result_of<__spawn_common::__choose_senv, _Env, env_of_t<_Sender>>; - - template - using _spawn_sndr_impl_t = __result_of>; - - template - using _spawn_sndr_t = _spawn_sndr_impl_t<_wrapped_sender_t<_Sender, _Token>, _Env>; - - public: - //! @brief Spawn @c __sndr into the scope identified by @c __tkn, using - //! a default (empty) environment. - //! - //! Equivalent to spawn(__sndr, __tkn, env<>{}). - //! - //! @tparam _Sender A sender type with no @c set_error_t completions. - //! @tparam _Token A type satisfying @c stdexec::scope_token. - //! @param __sndr The sender to launch. - //! @param __tkn The scope token identifying the owning scope. - template - void operator()(_Sender&& __sndr, _Token __tkn) const - { - return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{}); - } - - //! @brief Diagnostic overload — selected when the sender's completion - //! signatures include @c set_error_t. Emits a @c static_assert - //! explaining that @c spawn expects a sender that cannot fail. - //! - //! Not normally called; the @c requires clause on the primary overload - //! steers compilation here on a constraint failure. - template - void operator()(_Sender&&, _Token, _Env&&) const - { - using _spawn_sndr_t = spawn_t::_spawn_sndr_t<_Sender, _Token, _Env>; - static_assert(sender_in<_spawn_sndr_t, _Env> - && __never_sends, - "spawn expects a sender that cannot fail"); - } - - //! @brief Spawn @c __sndr into the scope identified by @c __tkn, using - //! the allocator queried from @c __env. - //! - //! Allocates the operation state on the heap (using - //! stdexec::get_allocator(__env), falling back to - //! @c std::allocator), associates with the scope via - //! __tkn.try_associate(), and on success @c start s the - //! operation. On completion the state is destroyed and deallocated. - //! - //! @tparam _Sender A sender type with no @c set_error_t completions. - //! @tparam _Token A type satisfying @c stdexec::scope_token. - //! @tparam _Env An environment type; queried for an allocator. - //! - //! @param __sndr The sender to launch. - //! @param __tkn The scope token identifying the owning scope. - //! @param __env Environment used both for allocator lookup and as - //! the spawned operation's receiver environment. - //! - //! @pre @c __sndr must not be able to complete with @c set_error - //! (enforced by the @c requires clause). - template - requires __never_sends, _Env> - void operator()(_Sender&& __sndr, _Token __tkn, _Env&& __env) const - { - auto __wrapped_sender = __tkn.wrap(static_cast<_Sender&&>(__sndr)); - auto __sndr_env = get_env(__wrapped_sender); - - auto __raw_alloc = __spawn_common::__choose_alloc(__env, __sndr_env); - using __raw_alloc_t = decltype(__raw_alloc); + auto __wrapped_sender = __tkn.wrap(static_cast<_Sender&&>(__sndr)); + auto __sndr_env = get_env(__wrapped_sender); - auto __sender_with_env = write_env(std::move(__wrapped_sender), - __spawn_common::__choose_senv(__env, __sndr_env)); + auto __raw_alloc = __spawn_common::__choose_alloc(__env, __sndr_env); + using __raw_alloc_t = decltype(__raw_alloc); - using __spawn_state_t = __spawn_state<__raw_alloc_t, _Token, decltype(__sender_with_env)>; + auto __sender_with_env = write_env(std::move(__wrapped_sender), + __spawn_common::__choose_senv(__env, __sndr_env)); - using __traits = - std::allocator_traits<__raw_alloc_t>::template rebind_traits<__spawn_state_t>; - typename __traits::allocator_type __alloc(__raw_alloc); + using __spawn_state_t = + __spawn::__spawn_state<__raw_alloc_t, _Token, decltype(__sender_with_env)>; - auto* __op = __traits::allocate(__alloc, 1); + using __traits = + std::allocator_traits<__raw_alloc_t>::template rebind_traits<__spawn_state_t>; + typename __traits::allocator_type __alloc(__raw_alloc); - __scope_guard __guard{[&]() noexcept { __traits::deallocate(__alloc, __op, 1); }}; + auto* __op = __traits::allocate(__alloc, 1); - __traits::construct(__alloc, - __op, - __alloc, - std::move(__sender_with_env), - static_cast<_Token&&>(__tkn)); + __scope_guard __guard{[&]() noexcept { __traits::deallocate(__alloc, __op, 1); }}; - __guard.__dismiss(); + __traits::construct(__alloc, + __op, + __alloc, + std::move(__sender_with_env), + static_cast<_Token&&>(__tkn)); - __op->__run(); - } - }; - } // namespace __spawn + __guard.__dismiss(); - using __spawn::spawn_t; + __op->__run(); + } + }; //! @brief The customization point object for the @c spawn sender consumer. //! diff --git a/include/stdexec/__detail/__spawn_future.hpp b/include/stdexec/__detail/__spawn_future.hpp index 6f234c0f9..b2f671e5b 100644 --- a/include/stdexec/__detail/__spawn_future.hpp +++ b/include/stdexec/__detail/__spawn_future.hpp @@ -588,155 +588,6 @@ namespace STDEXEC } }; - //! @brief A sender consumer that eagerly starts a sender into an async - //! scope *and* returns a sender that completes when the spawned - //! work completes. - //! - //! @c spawn_future combines @ref spawn_t's "fire and forget into a - //! scope" semantics with an *observation channel*. Where @c spawn - //! returns @c void and discards the result of the spawned sender, - //! @c spawn_future returns a sender that, when connected and started, - //! delivers whatever completion the spawned operation produced — - //! value, error, or stopped. - //! - //! Like @c spawn, @c spawn_future eagerly starts the input sender at - //! the moment it is called. The returned sender is *not* a re-runnable - //! handle to that work; it is a one-shot observer of the already-running - //! operation. If the scope refuses to associate the operation (because - //! it has already begun shutting down, for example), the returned - //! sender completes via @c set_stopped without ever running the input - //! sender. - //! - //! See [exec.spawn.future] in the C++26 working draft for the - //! normative specification. - //! - //! @code{.cpp} - //! exec::async_scope scope; - //! - //! auto future = stdexec::spawn_future( - //! stdexec::just(42) | stdexec::then([](int x) { return x * 2; }), - //! scope.get_token()); - //! - //! // Do something else in parallel ... - //! - //! auto [v] = stdexec::sync_wait(std::move(future)).value(); - //! // v == 84; the spawned work was running while we did other things. - //! - //! stdexec::sync_wait(scope.join()); - //! @endcode - //! - //! **Eager vs. lazy.** - //! - //! Unlike most senders (which are *lazy* — they do nothing until - //! connected and started), the work that @c spawn_future observes is - //! *eager*: it starts at the call to @c spawn_future, not at @c start - //! of the returned sender. Connecting and starting the returned sender - //! is what you do to *observe* the result; it does not control when - //! the work runs. This makes @c spawn_future the natural way to fan - //! out concurrent work and later collect each result individually. - //! - //! **Why a scope?** - //! - //! As with @c spawn, the scope is the owner of lifetime for the - //! spawned operation. Without one, eager start would have no - //! defensible cleanup story at program shutdown. If you want to - //! observe a result and don't have a scope, you almost always want - //! @c sync_wait or a coroutine `co_await` over the original sender - //! instead — both are lazy. - //! - //! @see stdexec::spawn — like @c spawn_future but discards the result - //! @see exec::start_detached — scope-less fire-and-forget (extension) - //! @see stdexec::sync_wait — top-level synchronous wait that returns the result - //! @see stdexec::when_all — combine multiple senders concurrently (lazy) - struct spawn_future_t - { - //! @brief Spawn @c __sndr into the scope identified by @c __tkn, - //! eagerly start it, and return a sender that completes when - //! the spawned operation completes. - //! - //! Equivalent to spawn_future(__sndr, __tkn, env<>{}). - //! - //! @tparam _Sender A type satisfying @c stdexec::sender. - //! @tparam _Token A type satisfying @c stdexec::scope_token. - //! - //! @param __sndr The sender to launch. Eagerly started. - //! @param __tkn The scope token identifying the owning scope. - //! - //! @returns A sender that, when connected and started, completes with - //! the result of the eagerly-started @c __sndr (value, error, - //! or stopped), or with @c set_stopped if association with - //! the scope failed. - template - auto operator()(_Sender&& __sndr, _Token&& __tkn) const -> __well_formed_sender auto - { - return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{}); - } - - //! @brief Spawn @c __sndr into the scope identified by @c __tkn, - //! using the allocator queried from @c __env, and return a - //! sender that observes its completion. - //! - //! @tparam _Sender A type satisfying @c stdexec::sender. - //! @tparam _Token A type satisfying @c stdexec::scope_token. - //! @tparam _Env An environment type; queried for an allocator. - //! - //! @param __sndr The sender to launch. - //! @param __tkn The scope token identifying the owning scope. - //! @param __env Environment used both for allocator lookup and as - //! the spawned operation's receiver environment. - //! - //! @returns A sender observing the spawned operation's completion - //! (or @c set_stopped if scope-association failed). - template - auto - operator()(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const -> __well_formed_sender auto - { - return __impl(__tkn.wrap(static_cast<_Sender&&>(__sndr)), - static_cast<_Token&&>(__tkn), - static_cast<_Env&&>(__env)); - } - - private: - template - auto __impl(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const - { - using __alloc_t = decltype(__spawn_common::__choose_alloc(__env, STDEXEC::get_env(__sndr))); - using __senv_t = decltype(__spawn_common::__choose_senv(__env, STDEXEC::get_env(__sndr))); - - using __spawn_future_state_t = - __spawn_future_state<__alloc_t, std::remove_cvref_t<_Token>, _Sender, __senv_t>; - - using __traits = - std::allocator_traits<__alloc_t>::template rebind_traits<__spawn_future_state_t>; - typename __traits::allocator_type __alloc( - __spawn_common::__choose_alloc(__env, STDEXEC::get_env(__sndr))); - - auto* __op = __traits::allocate(__alloc, 1); - - __scope_guard __guard{[&]() noexcept { __traits::deallocate(__alloc, __op, 1); }}; - - __traits::construct(__alloc, - __op, - __alloc, - static_cast<_Sender&&>(__sndr), - static_cast<_Token&&>(__tkn), - __spawn_common::__choose_senv(__env, STDEXEC::get_env(__sndr))); - - __guard.__dismiss(); - - struct __abandoner - { - void operator()(__spawn_future_state_t* __p) const noexcept - { - __p->__abandon(); - } - }; - - return __make_sexpr( - std::unique_ptr<__spawn_future_state_t, __abandoner>(__op)); - } - }; - template struct __future_operation_base { @@ -865,7 +716,157 @@ namespace STDEXEC }; } // namespace __spawn_future - using __spawn_future::spawn_future_t; + //! @brief A sender consumer that eagerly starts a sender into an async + //! scope *and* returns a sender that completes when the spawned + //! work completes. + //! + //! @c spawn_future combines @ref spawn_t's "fire and forget into a + //! scope" semantics with an *observation channel*. Where @c spawn + //! returns @c void and discards the result of the spawned sender, + //! @c spawn_future returns a sender that, when connected and started, + //! delivers whatever completion the spawned operation produced — + //! value, error, or stopped. + //! + //! Like @c spawn, @c spawn_future eagerly starts the input sender at + //! the moment it is called. The returned sender is *not* a re-runnable + //! handle to that work; it is a one-shot observer of the already-running + //! operation. If the scope refuses to associate the operation (because + //! it has already begun shutting down, for example), the returned + //! sender completes via @c set_stopped without ever running the input + //! sender. + //! + //! See [exec.spawn.future] in the C++26 working draft for the + //! normative specification. + //! + //! @code{.cpp} + //! exec::async_scope scope; + //! + //! auto future = stdexec::spawn_future( + //! stdexec::just(42) | stdexec::then([](int x) { return x * 2; }), + //! scope.get_token()); + //! + //! // Do something else in parallel ... + //! + //! auto [v] = stdexec::sync_wait(std::move(future)).value(); + //! // v == 84; the spawned work was running while we did other things. + //! + //! stdexec::sync_wait(scope.join()); + //! @endcode + //! + //! **Eager vs. lazy.** + //! + //! Unlike most senders (which are *lazy* — they do nothing until + //! connected and started), the work that @c spawn_future observes is + //! *eager*: it starts at the call to @c spawn_future, not at @c start + //! of the returned sender. Connecting and starting the returned sender + //! is what you do to *observe* the result; it does not control when + //! the work runs. This makes @c spawn_future the natural way to fan + //! out concurrent work and later collect each result individually. + //! + //! **Why a scope?** + //! + //! As with @c spawn, the scope is the owner of lifetime for the + //! spawned operation. Without one, eager start would have no + //! defensible cleanup story at program shutdown. If you want to + //! observe a result and don't have a scope, you almost always want + //! @c sync_wait or a coroutine `co_await` over the original sender + //! instead — both are lazy. + //! + //! @see stdexec::spawn — like @c spawn_future but discards the result + //! @see exec::start_detached — scope-less fire-and-forget (extension) + //! @see stdexec::sync_wait — top-level synchronous wait that returns the result + //! @see stdexec::when_all — combine multiple senders concurrently (lazy) + struct spawn_future_t + { + //! @brief Spawn @c __sndr into the scope identified by @c __tkn, + //! eagerly start it, and return a sender that completes when + //! the spawned operation completes. + //! + //! Equivalent to spawn_future(__sndr, __tkn, env<>{}). + //! + //! @tparam _Sender A type satisfying @c stdexec::sender. + //! @tparam _Token A type satisfying @c stdexec::scope_token. + //! + //! @param __sndr The sender to launch. Eagerly started. + //! @param __tkn The scope token identifying the owning scope. + //! + //! @returns A sender that, when connected and started, completes with + //! the result of the eagerly-started @c __sndr (value, error, + //! or stopped), or with @c set_stopped if association with + //! the scope failed. + template + auto operator()(_Sender&& __sndr, _Token&& __tkn) const -> __well_formed_sender auto + { + return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{}); + } + + //! @brief Spawn @c __sndr into the scope identified by @c __tkn, + //! using the allocator queried from @c __env, and return a + //! sender that observes its completion. + //! + //! @tparam _Sender A type satisfying @c stdexec::sender. + //! @tparam _Token A type satisfying @c stdexec::scope_token. + //! @tparam _Env An environment type; queried for an allocator. + //! + //! @param __sndr The sender to launch. + //! @param __tkn The scope token identifying the owning scope. + //! @param __env Environment used both for allocator lookup and as + //! the spawned operation's receiver environment. + //! + //! @returns A sender observing the spawned operation's completion + //! (or @c set_stopped if scope-association failed). + template + auto + operator()(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const -> __well_formed_sender auto + { + return __impl(__tkn.wrap(static_cast<_Sender&&>(__sndr)), + static_cast<_Token&&>(__tkn), + static_cast<_Env&&>(__env)); + } + + private: + template + auto __impl(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const + { + using __alloc_t = decltype(__spawn_common::__choose_alloc(__env, STDEXEC::get_env(__sndr))); + using __senv_t = decltype(__spawn_common::__choose_senv(__env, STDEXEC::get_env(__sndr))); + + using __spawn_future_state_t = + __spawn_future::__spawn_future_state<__alloc_t, + std::remove_cvref_t<_Token>, + _Sender, + __senv_t>; + + using __traits = + std::allocator_traits<__alloc_t>::template rebind_traits<__spawn_future_state_t>; + typename __traits::allocator_type __alloc( + __spawn_common::__choose_alloc(__env, STDEXEC::get_env(__sndr))); + + auto* __op = __traits::allocate(__alloc, 1); + + __scope_guard __guard{[&]() noexcept { __traits::deallocate(__alloc, __op, 1); }}; + + __traits::construct(__alloc, + __op, + __alloc, + static_cast<_Sender&&>(__sndr), + static_cast<_Token&&>(__tkn), + __spawn_common::__choose_senv(__env, STDEXEC::get_env(__sndr))); + + __guard.__dismiss(); + + struct __abandoner + { + void operator()(__spawn_future_state_t* __p) const noexcept + { + __p->__abandon(); + } + }; + + return __make_sexpr( + std::unique_ptr<__spawn_future_state_t, __abandoner>(__op)); + } + }; //! @brief The customization point object for the @c spawn_future sender consumer. //!