@@ -122,175 +122,179 @@ namespace STDEXEC
122122 }
123123 }
124124 };
125+ } // namespace __spawn
125126
126- // ! @brief A sender consumer that eagerly starts a sender and ties its
127- // ! lifetime to an *async scope*.
128- // !
129- // ! @c spawn is the standard "fire-and-forget into a scope" consumer.
130- // ! You give it a sender, a @c scope_token (a handle to an async scope),
131- // ! and optionally an environment, and @c spawn:
132- // !
133- // ! 1. allocates an operation state on the heap (using an allocator
134- // ! queried from the environment or the sender's own environment),
135- // ! 2. tries to associate the resulting operation with the scope via
136- // ! <tt>token.try_associate()</tt>,
137- // ! 3. if the association succeeds, eagerly @c start s the operation,
138- // ! and on completion deallocates the state and releases the scope
139- // ! association.
140- // !
141- // ! If association fails (typically because the scope has already begun
142- // ! shutting down), @c spawn destroys the state and returns without
143- // ! starting the operation. The result of the sender, if any, is
144- // ! discarded — @c spawn returns @c void.
145- // !
146- // ! See [exec.spawn] in the C++26 working draft for the normative
147- // ! specification.
148- // !
149- // ! @code{.cpp}
150- // ! exec::async_scope scope;
151- // !
152- // ! stdexec::spawn(stdexec::just(42) | stdexec::then([](int x) {
153- // ! std::println("background work produced {}", x);
154- // ! }), scope.get_token());
155- // !
156- // ! // Later, before destroying scope:
157- // ! stdexec::sync_wait(scope.join());
158- // ! @endcode
127+ // ! @brief A sender consumer that eagerly starts a sender and ties its
128+ // ! lifetime to an *async scope*.
129+ // !
130+ // ! @c spawn is the standard "fire-and-forget into a scope" consumer.
131+ // ! You give it a sender, a @c scope_token (a handle to an async scope),
132+ // ! and optionally an environment, and @c spawn:
133+ // !
134+ // ! 1. allocates an operation state on the heap (using an allocator
135+ // ! queried from the environment or the sender's own environment),
136+ // ! 2. tries to associate the resulting operation with the scope via
137+ // ! <tt>token.try_associate()</tt>,
138+ // ! 3. if the association succeeds, eagerly @c start s the operation,
139+ // ! and on completion deallocates the state and releases the scope
140+ // ! association.
141+ // !
142+ // ! If association fails (typically because the scope has already begun
143+ // ! shutting down), @c spawn destroys the state and returns without
144+ // ! starting the operation. The result of the sender, if any, is
145+ // ! discarded — @c spawn returns @c void.
146+ // !
147+ // ! See [exec.spawn] in the C++26 working draft for the normative
148+ // ! specification.
149+ // !
150+ // ! @code{.cpp}
151+ // ! exec::async_scope scope;
152+ // !
153+ // ! stdexec::spawn(stdexec::just(42) | stdexec::then([](int x) {
154+ // ! std::println("background work produced {}", x);
155+ // ! }), scope.get_token());
156+ // !
157+ // ! // Later, before destroying scope:
158+ // ! stdexec::sync_wait(scope.join());
159+ // ! @endcode
160+ // !
161+ // ! **Completion requirements.**
162+ // !
163+ // ! The argument sender must not be able to complete with @c set_error
164+ // ! — @c spawn cannot deliver an error to a non-existent caller. The
165+ // ! @c requires clause enforces this with a
166+ // ! <tt>__never_sends<set_error_t, ...></tt> check; the diagnostic
167+ // ! overload says "spawn expects a sender that cannot fail" if the check
168+ // ! fires.
169+ // !
170+ // ! Successful and stopped completions are both accepted; their results
171+ // ! are discarded.
172+ // !
173+ // ! **Scope semantics.**
174+ // !
175+ // ! The scope is the *owner of lifetime* for the spawned operation.
176+ // ! Calling code is expected to eventually @c join() the scope (or
177+ // ! otherwise wait for all spawned work to drain) before destroying it
178+ // ! — typically once at program shutdown, or once per logical unit of
179+ // ! related background work.
180+ // !
181+ // ! @c spawn is the canonical fire-and-forget consumer for any work
182+ // ! that has a clear "owning context" (a request, a session, a worker).
183+ // ! For top-level work with no owning scope, use @c exec::start_detached
184+ // ! (an stdexec extension). For fire-and-forget work whose completion
185+ // ! you want to *observe* (without blocking), use
186+ // ! @c stdexec::spawn_future.
187+ // !
188+ // ! @see stdexec::spawn_future — like @c spawn, but returns a sender that completes
189+ // ! when the spawned work completes
190+ // ! @see exec::start_detached — scope-less fire-and-forget (extension)
191+ // ! @see stdexec::sync_wait — top-level synchronous wait that returns the result
192+ struct spawn_t
193+ {
194+ private:
195+ template <class _Sender , class _Token >
196+ using _wrapped_sender_t = decltype (__declval<_Token&>().wrap(__declval<_Sender>()));
197+
198+ template <class _Sender , class _Env >
199+ using __choose_senv_t = __result_of<__spawn_common::__choose_senv, _Env, env_of_t <_Sender>>;
200+
201+ template <class _Sender , class _Env >
202+ using _spawn_sndr_impl_t = __result_of<write_env, _Sender, __choose_senv_t <_Sender, _Env>>;
203+
204+ template <class _Sender , class _Token , class _Env >
205+ using _spawn_sndr_t = _spawn_sndr_impl_t <_wrapped_sender_t <_Sender, _Token>, _Env>;
206+
207+ public:
208+ // ! @brief Spawn @c __sndr into the scope identified by @c __tkn, using
209+ // ! a default (empty) environment.
159210 // !
160- // ! **Completion requirements.**
211+ // ! Equivalent to <tt>spawn(__sndr, __tkn, env<>{})</tt>.
161212 // !
162- // ! The argument sender must not be able to complete with @c set_error
163- // ! — @c spawn cannot deliver an error to a non-existent caller. The
164- // ! @c requires clause enforces this with a
165- // ! <tt>__never_sends<set_error_t, ...></tt> check; the diagnostic
166- // ! overload says "spawn expects a sender that cannot fail" if the check
167- // ! fires.
213+ // ! @tparam _Sender A sender type with no @c set_error_t completions.
214+ // ! @tparam _Token A type satisfying @c stdexec::scope_token.
215+ // ! @param __sndr The sender to launch.
216+ // ! @param __tkn The scope token identifying the owning scope.
217+ template <sender _Sender, scope_token _Token>
218+ void operator ()(_Sender&& __sndr, _Token __tkn) const
219+ {
220+ return (*this )(static_cast <_Sender&&>(__sndr), static_cast <_Token&&>(__tkn), env<>{});
221+ }
222+
223+ // Hidden from Doxygen: this diagnostic-only overload shares its signature
224+ // with the primary three-argument overload above (they differ only by a
225+ // constraint), which the Sphinx C++ domain cannot disambiguate.
226+ #if !defined(STDEXEC_DOXYGEN_INVOKED)
227+ // ! @brief Diagnostic overload — selected when the sender's completion
228+ // ! signatures include @c set_error_t. Emits a @c static_assert
229+ // ! explaining that @c spawn expects a sender that cannot fail.
168230 // !
169- // ! Successful and stopped completions are both accepted; their results
170- // ! are discarded.
231+ // ! Not normally called; the @c requires clause on the primary overload
232+ // ! steers compilation here on a constraint failure.
233+ template <sender _Sender, scope_token _Token, class _Env >
234+ void operator ()(_Sender&&, _Token, _Env&&) const
235+ {
236+ using _spawn_sndr_t = spawn_t ::_spawn_sndr_t <_Sender, _Token, _Env>;
237+ static_assert (sender_in<_spawn_sndr_t , _Env>
238+ && __never_sends<STDEXEC ::set_error_t , _spawn_sndr_t , _Env>,
239+ " spawn expects a sender that cannot fail" );
240+ }
241+ #endif // !defined(STDEXEC_DOXYGEN_INVOKED)
242+
243+ // ! @brief Spawn @c __sndr into the scope identified by @c __tkn, using
244+ // ! the allocator queried from @c __env.
171245 // !
172- // ! **Scope semantics.**
246+ // ! Allocates the operation state on the heap (using
247+ // ! <tt>stdexec::get_allocator(__env)</tt>, falling back to
248+ // ! @c std::allocator), associates with the scope via
249+ // ! <tt>__tkn.try_associate()</tt>, and on success @c start s the
250+ // ! operation. On completion the state is destroyed and deallocated.
173251 // !
174- // ! The scope is the *owner of lifetime* for the spawned operation.
175- // ! Calling code is expected to eventually @c join() the scope (or
176- // ! otherwise wait for all spawned work to drain) before destroying it
177- // ! — typically once at program shutdown, or once per logical unit of
178- // ! related background work.
252+ // ! @tparam _Sender A sender type with no @c set_error_t completions.
253+ // ! @tparam _Token A type satisfying @c stdexec::scope_token.
254+ // ! @tparam _Env An environment type; queried for an allocator.
179255 // !
180- // ! @c spawn is the canonical fire-and-forget consumer for any work
181- // ! that has a clear "owning context" (a request, a session, a worker).
182- // ! For top-level work with no owning scope, use @c exec::start_detached
183- // ! (an stdexec extension). For fire-and-forget work whose completion
184- // ! you want to *observe* (without blocking), use
185- // ! @c stdexec::spawn_future.
256+ // ! @param __sndr The sender to launch.
257+ // ! @param __tkn The scope token identifying the owning scope.
258+ // ! @param __env Environment used both for allocator lookup and as
259+ // ! the spawned operation's receiver environment.
186260 // !
187- // ! @see stdexec::spawn_future — like @c spawn, but returns a sender that completes
188- // ! when the spawned work completes
189- // ! @see exec::start_detached — scope-less fire-and-forget (extension)
190- // ! @see stdexec::sync_wait — top-level synchronous wait that returns the result
191- struct spawn_t
261+ // ! @pre @c __sndr must not be able to complete with @c set_error
262+ // ! (enforced by the @c requires clause).
263+ template <sender _Sender, scope_token _Token, class _Env >
264+ requires __never_sends< STDEXEC :: set_error_t , _spawn_sndr_t <_Sender, _Token, _Env>, _Env>
265+ void operator ()(_Sender&& __sndr, _Token __tkn, _Env&& __env) const
192266 {
193- private:
194- template <class _Sender , class _Token >
195- using _wrapped_sender_t = decltype (__declval<_Token&>().wrap(__declval<_Sender>()));
196-
197- template <class _Sender , class _Env >
198- using __choose_senv_t = __result_of<__spawn_common::__choose_senv, _Env, env_of_t <_Sender>>;
199-
200- template <class _Sender , class _Env >
201- using _spawn_sndr_impl_t = __result_of<write_env, _Sender, __choose_senv_t <_Sender, _Env>>;
202-
203- template <class _Sender , class _Token , class _Env >
204- using _spawn_sndr_t = _spawn_sndr_impl_t <_wrapped_sender_t <_Sender, _Token>, _Env>;
205-
206- public:
207- // ! @brief Spawn @c __sndr into the scope identified by @c __tkn, using
208- // ! a default (empty) environment.
209- // !
210- // ! Equivalent to <tt>spawn(__sndr, __tkn, env<>{})</tt>.
211- // !
212- // ! @tparam _Sender A sender type with no @c set_error_t completions.
213- // ! @tparam _Token A type satisfying @c stdexec::scope_token.
214- // ! @param __sndr The sender to launch.
215- // ! @param __tkn The scope token identifying the owning scope.
216- template <sender _Sender, scope_token _Token>
217- void operator ()(_Sender&& __sndr, _Token __tkn) const
218- {
219- return (*this )(static_cast <_Sender&&>(__sndr), static_cast <_Token&&>(__tkn), env<>{});
220- }
221-
222- // ! @brief Diagnostic overload — selected when the sender's completion
223- // ! signatures include @c set_error_t. Emits a @c static_assert
224- // ! explaining that @c spawn expects a sender that cannot fail.
225- // !
226- // ! Not normally called; the @c requires clause on the primary overload
227- // ! steers compilation here on a constraint failure.
228- template <sender _Sender, scope_token _Token, class _Env >
229- void operator ()(_Sender&&, _Token, _Env&&) const
230- {
231- using _spawn_sndr_t = spawn_t ::_spawn_sndr_t <_Sender, _Token, _Env>;
232- static_assert (sender_in<_spawn_sndr_t , _Env>
233- && __never_sends<STDEXEC ::set_error_t , _spawn_sndr_t , _Env>,
234- " spawn expects a sender that cannot fail" );
235- }
236-
237- // ! @brief Spawn @c __sndr into the scope identified by @c __tkn, using
238- // ! the allocator queried from @c __env.
239- // !
240- // ! Allocates the operation state on the heap (using
241- // ! <tt>stdexec::get_allocator(__env)</tt>, falling back to
242- // ! @c std::allocator), associates with the scope via
243- // ! <tt>__tkn.try_associate()</tt>, and on success @c start s the
244- // ! operation. On completion the state is destroyed and deallocated.
245- // !
246- // ! @tparam _Sender A sender type with no @c set_error_t completions.
247- // ! @tparam _Token A type satisfying @c stdexec::scope_token.
248- // ! @tparam _Env An environment type; queried for an allocator.
249- // !
250- // ! @param __sndr The sender to launch.
251- // ! @param __tkn The scope token identifying the owning scope.
252- // ! @param __env Environment used both for allocator lookup and as
253- // ! the spawned operation's receiver environment.
254- // !
255- // ! @pre @c __sndr must not be able to complete with @c set_error
256- // ! (enforced by the @c requires clause).
257- template <sender _Sender, scope_token _Token, class _Env >
258- requires __never_sends<STDEXEC ::set_error_t , _spawn_sndr_t <_Sender, _Token, _Env>, _Env>
259- void operator ()(_Sender&& __sndr, _Token __tkn, _Env&& __env) const
260- {
261- auto __wrapped_sender = __tkn.wrap (static_cast <_Sender&&>(__sndr));
262- auto __sndr_env = get_env (__wrapped_sender);
263-
264- auto __raw_alloc = __spawn_common::__choose_alloc (__env, __sndr_env);
265- using __raw_alloc_t = decltype (__raw_alloc);
267+ auto __wrapped_sender = __tkn.wrap (static_cast <_Sender&&>(__sndr));
268+ auto __sndr_env = get_env (__wrapped_sender);
266269
267- auto __sender_with_env = write_env ( std::move (__wrapped_sender),
268- __spawn_common::__choose_senv (__env, __sndr_env) );
270+ auto __raw_alloc = __spawn_common::__choose_alloc (__env, __sndr_env);
271+ using __raw_alloc_t = decltype (__raw_alloc );
269272
270- using __spawn_state_t = __spawn_state<__raw_alloc_t , _Token, decltype (__sender_with_env)>;
273+ auto __sender_with_env = write_env (std::move (__wrapped_sender),
274+ __spawn_common::__choose_senv (__env, __sndr_env));
271275
272- using __traits =
273- std::allocator_traits<__raw_alloc_t >::template rebind_traits<__spawn_state_t >;
274- typename __traits::allocator_type __alloc (__raw_alloc);
276+ using __spawn_state_t =
277+ __spawn::__spawn_state<__raw_alloc_t , _Token, decltype (__sender_with_env)>;
275278
276- auto * __op = __traits::allocate (__alloc, 1 );
279+ using __traits =
280+ std::allocator_traits<__raw_alloc_t >::template rebind_traits<__spawn_state_t >;
281+ typename __traits::allocator_type __alloc (__raw_alloc);
277282
278- __scope_guard __guard{[&]() noexcept { __traits::deallocate (__alloc, __op, 1 ); }} ;
283+ auto * __op = __traits::allocate (__alloc, 1 ) ;
279284
280- __traits::construct (__alloc,
281- __op,
282- __alloc,
283- std::move (__sender_with_env),
284- static_cast <_Token&&>(__tkn));
285+ __scope_guard __guard{[&]() noexcept { __traits::deallocate (__alloc, __op, 1 ); }};
285286
286- __guard.__dismiss ();
287+ __traits::construct (__alloc,
288+ __op,
289+ __alloc,
290+ std::move (__sender_with_env),
291+ static_cast <_Token&&>(__tkn));
287292
288- __op->__run ();
289- }
290- };
291- } // namespace __spawn
293+ __guard.__dismiss ();
292294
293- using __spawn::spawn_t ;
295+ __op->__run ();
296+ }
297+ };
294298
295299 // ! @brief The customization point object for the @c spawn sender consumer.
296300 // !
0 commit comments