Skip to content

Commit a54614d

Browse files
committed
some high-level user docs
1 parent 9154664 commit a54614d

8 files changed

Lines changed: 369 additions & 27 deletions

File tree

docs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html)
6060

6161
file(GLOB_RECURSE
6262
STDEXEC_DOCUMENTATION_SOURCE_FILES
63+
CONFIGURE_DEPENDS
6364
${SPHINX_SOURCE}/*.rst ${SPHINX_SOURCE}/*.md)
6465

6566
# Only regenerate Sphinx when:

docs/source/conf.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
# -- Project information -----------------------------------------------------
2323
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
2424

25+
import os
26+
2527
project = 'stdexec'
2628
copyright = '2025, NVIDIA Corporation'
2729
author = 'NVIDIA Corporation'
@@ -75,5 +77,6 @@
7577

7678
highlight_language = "cpp"
7779

78-
# def setup(app):
79-
# app.add_css_file("params.css")
80+
def setup(app):
81+
print(os.path.join(app.srcdir, "style.css"))
82+
app.add_css_file(os.path.join(app.srcdir, "style.css"))

docs/source/index.rst

Lines changed: 310 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,339 @@
11
.. =============================================================================
2-
.. Copyright 2023 NVIDIA Corporation
3-
..
2+
.. Copyright 2025 NVIDIA Corporation
3+
..
44
.. Licensed under the Apache License, Version 2.0 (the "License");
55
.. you may not use this file except in compliance with the License.
66
.. You may obtain a copy of the License at
7-
..
7+
..
88
.. http://www.apache.org/licenses/LICENSE-2.0
9-
..
9+
..
1010
.. Unless required by applicable law or agreed to in writing, software
1111
.. distributed under the License is distributed on an "AS IS" BASIS,
1212
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
.. See the License for the specific language governing permissions and
1414
.. limitations under the License.
1515
.. =============================================================================
1616
17+
.. highlight:: cpp
18+
:linenothreshold: 5
19+
1720
Welcome to ``stdexec``
1821
======================
1922

20-
``stdexec`` is the reference implementation for `P2300 <https://wg21.link/P2300>`_, the
21-
``std::execution`` proposal to the C++ standard library which will bring to C++26 a standard
22-
async programming model.
23+
``stdexec`` is the reference implementation for `C++26's asynchronous execution framework
24+
<https://eel.is/c++draft/exec>`_, ``std::execution``. It provides a modern,
25+
composable, and efficient abstraction for asynchronous programming in C++.
26+
27+
Sender/Receiver Abstraction in C++26
28+
====================================
29+
30+
The **sender/receiver abstraction** is a foundational model for asynchronous programming in
31+
modern C++, proposed for standardization in **C++26** (originally targeted for C++23). It aims
32+
to unify and modernize asynchronous workflows across the C++ ecosystem.
33+
34+
🔧 Motivation
35+
--------------
36+
37+
C++'s legacy async mechanisms — ``std::async``, futures, coroutines — have several limitations:
38+
39+
- Inflexible and hard to compose.
40+
41+
- Inefficient or heap-heavy.
42+
43+
- Difficult to customize or extend.
44+
45+
- Incompatible across libraries (e.g., Boost, Folly).
46+
47+
The sender/receiver abstraction offers:
48+
49+
- Composable async operations.
50+
51+
- Customizable schedulers and execution strategies.
52+
53+
- Clean cancellation handling.
54+
55+
- Coroutine integration.
56+
57+
- Zero-cost abstractions.
58+
59+
🧱 Core Concepts
60+
----------------
61+
62+
1. Scheduler
63+
^^^^^^^^^^^^
64+
65+
A **scheduler** is an object that provides a way to schedule work. They are lightweight
66+
handles to what is often a heavy-weight and immovable **execution context**. Execution
67+
contexts are where work actually happens, and they can be anything that can execute code.
68+
Examples of execution contexts:
69+
70+
- Thread pools.
71+
- Event loops.
72+
- GPUs.
73+
- An I/O subsystem.
74+
- Any other execution model.
75+
76+
.. code-block:: cpp
77+
78+
auto sched = exec::get_parallel_scheduler(); // Obtain the default system scheduler
79+
auto sndr = stdexec::schedule(sched); // Create a sender from the scheduler
80+
81+
The sender you get back from ``stdexec::schedule`` is a factory for operations that,
82+
when started, will immediately call ``set_value()`` on the receiver the operation was
83+
constructed with. And crucially, it does so from the context of the scheduler. Work
84+
is executed on a context by chaining continuations to one of these senders, and passing
85+
it to one of the algorithms that starts work, like ``stdexec::sync_wait``.
86+
87+
2. Sender
88+
^^^^^^^^^
89+
90+
A **sender** is an object that describes an asynchronous computation that may happen
91+
later. It can do nothing on its own, but when connected to a receiver, it returns an
92+
**operation state** that can start the work described by the sender.
93+
94+
``stdexec`` provides a set of generic **sender algorithms** that can be used to chain
95+
operations together. There are also algorithms, like ``stdexec::sync_wait``, that can
96+
be used to launch the sender. The sender algorithms take care of connecting the sender
97+
to a receiver and managing the lifetime the operation state.
98+
99+
- Produces values (or errors) asynchronously.
100+
101+
- Can be requested to stop early.
102+
103+
- Supports composition.
104+
105+
- Is lazy (does nothing until connected and started).
106+
107+
.. code-block:: cpp
108+
109+
auto sndr = stdexec::just(42); // Sender that yields 42 immediately
110+
auto [result] = stdexec::sync_wait(sndr).value(); // Start the work & wait for the result
111+
assert(result.value() == 42);
112+
113+
3. Receiver
114+
^^^^^^^^^^^
115+
116+
A **receiver** is an object that consumes the result of a sender. It defines three
117+
member functions that handle completion:
118+
119+
- ``.set_value(args...)``: Called on success.
120+
121+
- ``.set_error(err)``: Called on error.
122+
123+
- ``.set_stopped()``: Called if the operation is canceled.
124+
125+
.. code-block:: cpp
126+
127+
struct MyReceiver {
128+
using receiver_concept = stdexec::receiver_t;
129+
130+
void set_value(int v) noexcept { /* success */ }
131+
void set_error(std::exception_ptr e) noexcept { /* error */ }
132+
void set_stopped() noexcept { /* cancellation */ }
133+
};
134+
135+
As a user, you typically won't deal with receivers directly.
136+
They are an implementation detail of sender algorithms.
137+
138+
4. Operation State
139+
^^^^^^^^^^^^^^^^^^
140+
141+
Connecting a sender to a receiver yields an **operation state**, which:
142+
143+
- Represents the in-progress computation.
144+
145+
- Is started explicitly via ``.start()``.
146+
147+
.. code-block:: cpp
148+
149+
auto op = stdexec::connect(sndr, MyReceiver{}); // Connect sender to receiver
150+
stdexec::start(op); // Start the operation
151+
152+
Operation states are immovable, and once started, they must be kept alive until the
153+
operation completes. As a user though, you typically will not manage them directly.
154+
They are handled by the sender algorithms.
155+
156+
5. Environments
157+
^^^^^^^^^^^^^^^^^^
23158

24-
It provides:
159+
Environments are a key concept in the sender/receiver model. An **environment** is an
160+
unordered collection of key/value pairs, queryable at runtime via tag types. Every
161+
receiver has a (possibly empty) environment that can be obtained by passing the receiver
162+
to ``stdexec::get_env()``.
25163

26-
* TODO
164+
Environments provide a way to pass contextual information like stop tokens, allocators, or
165+
schedulers to asynchronous operations. That information is then used by the operation to
166+
customize its behavior.
167+
168+
🧮 Composition via Algorithms
169+
-----------------------------
170+
171+
One benefit of the lazy evaluation of senders is that it makes it possible to create
172+
reusable algorithms that can be composed together. ``stdexec`` provides a rich set of
173+
algorithms that can be used to build complex asynchronous workflows.
174+
175+
A **sender factory algorithm** creates a sender that can be used to start an operation.
176+
Below are some of the key sender factory algorithms provided by ``stdexec``:
177+
178+
.. list-table:: Sender Factory Algorithms
179+
180+
* - **CPO**
181+
- **Description**
182+
* - ``stdexec::schedule``
183+
- Obtains a sender from a scheduler.
184+
* - ``stdexec::just``
185+
- Creates a sender that will immediately complete with a set of values.
186+
* - ``stdexec::read_env``
187+
- Reads a value from the receiver's environment and completes with it.
188+
189+
A **sender adaptor algorithm** takes an existing sender (or several senders) and
190+
transforms it into a new sender with additional behavior. Below are some key sender
191+
adaptor algorithms. Check the :ref:`Reference` section for additional algorithms.
192+
193+
.. list-table:: Sender Adaptor Algorithms
194+
:class: tight-table
195+
196+
* - **CPO**
197+
- **Description**
198+
* - ``stdexec::then``
199+
- Applies a function to the value from a sender.
200+
* - ``stdexec::starts_on``
201+
- Executes an async operation on the specified scheduler.
202+
* - ``stdexec::continues_on``
203+
- Executes an async operation on the current scheduler and then transfers
204+
execution to the specified scheduler.
205+
* - ``stdexec::on``
206+
- Executes an async operation on a different scheduler and then transitions
207+
back to the original scheduler.
208+
* - ``stdexec::when_all``
209+
- Combines multiple senders, making it possible to execute them in parallel.
210+
* - ``stdexec::let_value``
211+
- Executes an async operation dynamically based on the results of a specified
212+
sender.
213+
* - ``stdexec::write_env``
214+
- Writes a value to the receiver's environment, allowing it to be used by
215+
child operations.
216+
217+
A **sender consumer algorithm** takes a sender connects it to a receiver and starts the
218+
resulting operation. Here are some key sender consumer algorithms:
219+
220+
.. list-table:: Sender Consumer Algorithms
221+
222+
* - **CPO**
223+
- **Description**
224+
* - ``stdexec::sync_wait``
225+
- Blocks the calling thread until the sender completes and returns the result.
226+
* - ``stdexec::start_detached``
227+
- Starts the operation without waiting for it to complete.
228+
229+
Sender algorithms are defined in terms of **core customization points**. Below are the
230+
core customization points that define how senders and receivers interact:
231+
232+
.. list-table:: Core customization points
233+
234+
* - **CPO**
235+
- **Description**
236+
* - ``stdexec::connect``
237+
- Connects a sender to a receiver resulting in an operation state.
238+
* - ``stdexec::start``
239+
- Starts the operation.
240+
* - ``stdexec::set_value``
241+
- Called by the operation state to deliver a value to the receiver.
242+
* - ``stdexec::set_error``
243+
- Called by the operation state to deliver an error to the receiver.
244+
* - ``stdexec::set_stopped``
245+
- Called by the operation state to indicate that the operation was stopped.
246+
* - ``stdexec::get_env``
247+
- Retrieves the environment from a receiver.
248+
249+
Here is an example of using sender algorithms to create a simple async pipeline:
250+
251+
.. code-block:: cpp
252+
253+
// Create a sender that produces a value:
254+
auto pipeline = stdexec::just(42)
255+
// Transform the value using `then` on the specified scheduler:
256+
| stdexec::on(some_scheduler, stdexec::then([](int i) { return i * 2; }))
257+
// Further transform the value using `then` on the starting scheduler:
258+
| stdexec::then([](int i) { return i + 1; });
259+
// Finally, wait for the result:
260+
auto [result] = stdexec::sync_wait(std::move(pipeline)).value();
261+
262+
263+
🔄 Coroutine Integration
264+
------------------------
265+
266+
Senders can be ``co_await``-ed in coroutines if they model ``awaitable_sender``. Any sender
267+
that can complete successfully in exactly one way is an awaitable sender.
268+
269+
.. code-block:: cpp
270+
271+
auto my_task() -> exec::task<int> {
272+
int x = co_await some_sender();
273+
co_return x + 1;
274+
}
275+
276+
If a sender that is being ``co_await``-ed completes with an error, the coroutine will
277+
throw an exception. If it completes with a stop, the coroutine will be canceled. That is,
278+
the coroutine will never be resumed; rather, it and its calling coroutines will be
279+
destroyed.
280+
281+
In addition, all awaitable types can be used as senders, allowing them to be composed with
282+
sender algorithms.
283+
284+
This allows ergonomic, coroutine-based async programming with sender semantics under the
285+
hood.
286+
287+
🚦 Standardization Status (as of 2025)
288+
--------------------------------------
289+
290+
- The core sender/receiver model has been accepted into the C++ standard for C++26.
291+
292+
- Additional facilities have also been accepted such as
293+
294+
* a system scheduler,
295+
* a sender-aware coroutine task type,
296+
* an async scope for spawning tasks dynamically.
297+
298+
- Interop with networking is being explored for C++29.
299+
300+
- Widely prototyped and tested in libraries and production settings.
301+
302+
🚀 Benefits
303+
-----------
304+
305+
- ✅ Zero-cost abstractions: No heap allocations or runtime overhead.
306+
307+
- ✅ Composable: Express async pipelines clearly.
308+
309+
- ✅ Customizable: Plug in your own schedulers, tokens, adapters.
310+
311+
- ✅ Coroutine-friendly: Clean ``co_await`` support.
312+
313+
- ✅ Unified async model: Works for I/O, compute, UI, etc.
314+
315+
🔚 Summary
316+
-----------
317+
318+
The sender/receiver abstraction:
319+
320+
- Brings modern, composable async programming to C++.
321+
322+
- Serves as a foundation for future concurrency features.
323+
324+
- Enables high-performance, coroutine-friendly workflows.
325+
326+
- Is set to become the standard async model in C++26.
27327

28328
.. toctree::
29329
:maxdepth: 2
30330
:caption: Contents:
31331

32-
reference/index
33-
34332
user/index
35333

36334
developer/index
37335

38-
.. .. toctree: :
39-
.. :maxdepth: 1
40-
41-
.. versions
336+
reference/index
42337

43338
Indices and tables
44339
------------------

0 commit comments

Comments
 (0)