Skip to content

Commit 777dc31

Browse files
authored
Merge pull request #2131 from ericniebler/fix-reference-docs
fix broken links to doxygen-generated reference docs
2 parents e8c349f + f2dc697 commit 777dc31

3 files changed

Lines changed: 337 additions & 301 deletions

File tree

docs/eelis_link_filter.pl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#=============================================================================
2+
# Copyright 2026 NVIDIA Corporation
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#=============================================================================
16+
#
17+
# Doxygen INPUT_FILTER. Turns references to C++ working-draft stable names of
18+
# the form [exec.xyz] into hyperlinks to the corresponding section on
19+
# https://eel.is/c++draft. The displayed text is left unchanged ([exec.xyz]),
20+
# so the convention documented in CONTRIBUTING-docs.md still reads naturally in
21+
# the source. Doxygen renders the emitted <a href> as a <ulink> in its XML,
22+
# which Breathe turns into an external hyperlink in the Sphinx output.
23+
#
24+
# Doxygen invokes this as `perl eelis_link_filter.pl <input-file>`, so the file
25+
# arrives on @ARGV and is streamed through <>. Everything that is not a stable
26+
# name is passed through verbatim.
27+
28+
while (<>) {
29+
s{\[(exec(?:\.[a-z0-9_]+)+)\]}{<a href="https://eel.is/c++draft/$1">[$1]</a>}g;
30+
print;
31+
}

include/stdexec/__detail/__spawn.hpp

Lines changed: 155 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)