|
| 1 | +<!-- SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception --> |
| 2 | + |
| 3 | +# Introduction by Example |
| 4 | + |
| 5 | +This page provides a series of examples showing how to use the |
| 6 | +`std::execution` components. |
| 7 | + |
| 8 | +<details> |
| 9 | +<summary>`"Hello, world"` - synchronous using asynchronous components</summary> |
| 10 | + |
| 11 | +Code: [`examples/intro-1-hello-world.cpp`]() |
| 12 | + |
| 13 | +The first example is a very complicated way to a version of `"Hello, |
| 14 | +world"`: it uses components for dealing with asynchronous work to |
| 15 | +synchronously produce the result. The intention is to show a basic |
| 16 | +use of some involved components to build up a feeling of how things |
| 17 | +work. |
| 18 | + |
| 19 | +The componentes for `std::execution` are declared in the header |
| 20 | +`<execution>`. This particular implementation implements the |
| 21 | +cmponents in namespace `beman::execution26` declared in the header |
| 22 | +`<beman/execution26/execution.hpp>`: |
| 23 | + |
| 24 | +```c++ |
| 25 | +#include <beman/execution26/execution.hpp> |
| 26 | +#include <iostream> |
| 27 | +#include <string> |
| 28 | +#include <tuple> |
| 29 | + |
| 30 | +namespace ex = ::beman::execution26; |
| 31 | +using namespace std::string_literals; |
| 32 | +``` |
| 33 | +
|
| 34 | +Most of these declarations should be familiar. The namespace alias |
| 35 | +`ex` is used to support easy migration to a different implementation, |
| 36 | +in particular the standard name `std::execution` once it becomes |
| 37 | +available with standard library implementations. The other examples |
| 38 | +will have a similar start which is only mentioned in the explanation |
| 39 | +to point out unusual parts like the use of custom components. |
| 40 | +
|
| 41 | +All interesting work happens in the `main` function: |
| 42 | +
|
| 43 | +```c++ |
| 44 | +int main() |
| 45 | +{ |
| 46 | + auto[result] = ex::sync_wait( |
| 47 | + ex::when_all( |
| 48 | + ex::just("hello, "s), |
| 49 | + ex::just("world"s) |
| 50 | + ) |
| 51 | + | ex::then([](auto s1, auto s2){ return s1 + s2; }) |
| 52 | + ).value_or(std::tuple(""s)); |
| 53 | +
|
| 54 | + std::cout << result << '\n'; |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +This code code be simpler even when using components from |
| 59 | +`std::execution`. Showing a few more components is intended to |
| 60 | +better reflect how an asynchronous program might look like. This |
| 61 | +examples uses a _sender factory_ (`ex::just`), two _sender adaptors_ |
| 62 | +(`ex::when_all` and `ex::then`), and finally a _sender consumer_ |
| 63 | +(`ex::sync_wait`) to build up work and to execute it. The idea of |
| 64 | +a _sender_ is that it represents work which can be composed with |
| 65 | +algorithms into a unit of work which is eventually executed. |
| 66 | + |
| 67 | +Each work item can complete asynchronously at some later time, i.e., |
| 68 | +calling it like a function and using a returned value isn't really |
| 69 | +an option. Instead, when the work is started it does whatever is |
| 70 | +needed to get the work completed and get a _completion signal_ |
| 71 | +delivered. Delivering a completion signal consists of calling a |
| 72 | +function on a suitable objects. The important part is that once |
| 73 | +work is started it always delivers exactly one completion signal |
| 74 | +which can indicate success, failure, or cancellation. Later examples |
| 75 | +for creating senders will go into more details about the cancellation |
| 76 | +signals. |
| 77 | +
|
| 78 | +The components used in this example do all of that synchronously: |
| 79 | +
|
| 80 | +- `ex::just("string"s)` completes immediately when started with |
| 81 | + successful completion which includes the string passed as |
| 82 | + argument. |
| 83 | +- <code>ex::when_all(_sender1_, _sender2_)</code> starts the senders |
| 84 | + passed as arguments. When all of the senders complete, it |
| 85 | + produces its own completion. In the case of success all the |
| 86 | + received values are passed to the completion signal. In case |
| 87 | + of an error all outstanding work is cancelled and the first |
| 88 | + error becomes `when_all`'s completion signal once all children |
| 89 | + have completed. Similarly, in case of cancellation all children |
| 90 | + get cancelled and once all complete `when_all` produces a |
| 91 | + cancellation signal. In the example the two children each produces |
| 92 | + one string as completion signal and `when_all` produces these two |
| 93 | + strings as its completion signal. |
| 94 | +- <code>_sender_ | ex::then(_fun_)</code> is equivalent to using |
| 95 | + <code>ex::then(_sender_, _fun_)</code>. The `ex::then` calls |
| 96 | + the function <code>_fun_</code> with its child sender completes |
| 97 | + successful. The arguments to <code>_fun_</code> are the values |
| 98 | + received from the child completion signal. In the example, the |
| 99 | + child is `when_all(...)` and it produces two strings which are |
| 100 | + passed to <code>_fun_</code>. The completion signal of `ex::then` |
| 101 | + is successful with the value returned from the call to |
| 102 | + <code>_fun_</code> (which may `void`) if the call returns |
| 103 | + normally. If an exception is thrown `ex::then` completes with |
| 104 | + an `std::exception_ptr` to the exception thrown. In the example |
| 105 | + the completion is just a concatenation of the two strings. |
| 106 | +- <code>sync_wait(_sender_)</code> starts its argument and then |
| 107 | + blocks until the work completes although the thread calling |
| 108 | + `sync_wait` may contribute to the completion of the work. The |
| 109 | + function returns a an |
| 110 | + <code>std::optional<std::tuple<_results_...>></code>>. |
| 111 | + If the child sender completes successfully the values from the |
| 112 | + child's completion signal become the elements of the tuple. If |
| 113 | + the child completes with an error, the error is thrown as an |
| 114 | + exception. Otherwise, if the work gets cancelled, an empty |
| 115 | + `std::optional<...>` is returned. In the example, the child |
| 116 | + sends a string which gets wrapped into a `std::tuple` which in |
| 117 | + turn gets wrapped into an `std::optional`. Thus, the somewhat |
| 118 | + round-about way to get the result: first using |
| 119 | + `value_or(std::tuple(""s))` to get the value from the `std::optional` |
| 120 | + which is then decomposed from the `std::tuple` using structured |
| 121 | + bindings. |
| 122 | + |
| 123 | +</details> |
| 124 | + |
| 125 | +<details> |
| 126 | +<summary>`"Hello, async"` - a simple asynchronous example</summary> |
| 127 | + |
| 128 | +Code: [`examples/intro-2-hello-async.cpp`]() |
| 129 | + |
| 130 | +</details> |
0 commit comments