Skip to content

Commit 3f8066e

Browse files
committed
Add examples (#122)
* added a few examples (some incomplete) * added an example using a timer and fixed an issue with sync_wait * restored cmake presets * clang-format * another attempt at clang-format
1 parent ae1ebe0 commit 3f8066e

File tree

7 files changed

+427
-0
lines changed

7 files changed

+427
-0
lines changed

docs/intro-examples.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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&lt;std::tuple&lt;_results_...&gt;&gt;</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>

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ list(
1212
stop_token
1313
stopping
1414
allocator
15+
intro-1-hello-world
16+
intro-2-hello-async
17+
intro-5-consumer
1518
doc-just
1619
doc-just_error
1720
doc-just_stopped

examples/intro-1-hello-world.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// examples/intro-1-hello-world.cpp -*-C++-*-
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <beman/execution26/execution.hpp>
5+
#include <iostream>
6+
#include <string>
7+
#include <tuple>
8+
9+
namespace ex = ::beman::execution26;
10+
using namespace std::string_literals;
11+
12+
// ----------------------------------------------------------------------------
13+
// Please see the explanation in docs/intro-examples.md for an explanation.
14+
15+
int main() {
16+
// clang-format off
17+
auto [result] = ex::sync_wait(
18+
ex::when_all(
19+
ex::just("hello, "s),
20+
ex::just("world"s)
21+
) | ex::then([](auto s1, auto s2) { return s1 + s2; })
22+
).value_or(std::tuple(""s)
23+
);
24+
// clang-format on
25+
26+
std::cout << result << '\n';
27+
}

examples/intro-2-hello-async.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// examples/intro-2-hello-async.cpp -*-C++-*-
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <beman/execution26/execution.hpp>
5+
#include "intro-timer.hpp"
6+
#include <chrono>
7+
#include <iostream>
8+
#include <string>
9+
#include <tuple>
10+
11+
namespace ex = ::beman::execution26;
12+
using namespace std::string_literals;
13+
using namespace std::chrono_literals;
14+
15+
// ----------------------------------------------------------------------------
16+
// Please see the explanation in docs/intro-examples.md for an explanation.
17+
18+
int main() {
19+
std::cout << std::unitbuf;
20+
intro::timer timer;
21+
22+
// clang-format off
23+
auto [result] = ex::sync_wait(
24+
ex::when_all(
25+
timer.run(),
26+
ex::when_all(
27+
timer.resume_after(3s)
28+
| ex::then([] { std::cout << "h\n"; return std::string("hello"); }),
29+
timer.resume_after(1s)
30+
| ex::then([] { std::cout << ",\n"; return std::string(", "); }),
31+
timer.resume_after(2s)
32+
| ex::then([] { std::cout << "w\n"; return std::string("world"); })
33+
) | ex::then([](auto s1, auto s2, auto s3) { return s1 + s2 + s3; })
34+
)
35+
).value_or(std::tuple(std::string("")));
36+
// clang-format on
37+
38+
std::cout << result << "\n";
39+
}

examples/intro-5-consumer.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// examples/intro-1-hello-world.cpp -*-C++-*-
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include <beman/execution26/execution.hpp>
5+
#include <beman/execution26/detail/suppress_push.hpp>
6+
#include <expected>
7+
#include <iostream>
8+
#include <string>
9+
#include <tuple>
10+
11+
namespace ex = ::beman::execution26;
12+
using namespace std::string_literals;
13+
14+
enum class success { one };
15+
enum class failure { fail_one };
16+
17+
struct expected_to_channel_t {
18+
template <typename Receiver>
19+
struct own_receiver {
20+
using receiver_concept = ex::receiver_t;
21+
Receiver* receiver;
22+
template <typename Value, typename Error>
23+
auto set_value(std::expected<Value, Error>&& exp) noexcept -> void {
24+
if (exp) {
25+
std::cout << "received an expected with value from child/upstream\n" << std::flush;
26+
ex::set_value(std::move(*receiver), exp.value_or(Value{}));
27+
std::cout << "set_value\n" << std::flush;
28+
} else {
29+
std::cout << "received an expected with error from child/upstream\n";
30+
ex::set_error(std::move(*receiver), exp.error_or(Error{}));
31+
}
32+
}
33+
template <typename Error>
34+
auto set_error(Error&& error) noexcept -> void {
35+
std::cout << "received an error from child/upstream";
36+
ex::set_error(std::move(*receiver), std::forward<Error>(error));
37+
}
38+
auto set_stopped() noexcept -> void {
39+
std::cout << "received an cancelletion from child/upstream";
40+
ex::set_stopped(std::move(*receiver));
41+
}
42+
};
43+
template <ex::sender CSender, ex::receiver Receiver>
44+
struct state {
45+
using operation_state_concept = ex::operation_state_t;
46+
using child_state_t = decltype(ex::connect(std::declval<CSender>(), std::declval<own_receiver<Receiver>>()));
47+
Receiver parent_receiver;
48+
child_state_t child_state;
49+
template <ex::sender S, ex::receiver R>
50+
state(S&& child_sender, R&& parent_receiver)
51+
: parent_receiver(std::forward<R>(parent_receiver)),
52+
child_state(ex::connect(std::forward<S>(child_sender), own_receiver<Receiver>{&this->parent_receiver})) {
53+
}
54+
void start() & noexcept {
55+
std::cout << "starting execpted_to_channel\n";
56+
ex::start(child_state);
57+
}
58+
};
59+
60+
template <ex::sender CSender>
61+
struct sender {
62+
using sender_concept = ex::sender_t;
63+
// value_types_of<CSender....> -> set_value_t(std::expected<T, E>)
64+
// -> completion_signatures<set_value_t(T), set_error_t(E)>
65+
// -> + error_type_of<CSender...>
66+
// -> + sends_stopped<CSender...> -> set_stopped_t()
67+
// -> unique
68+
using completion_signatures = ex::completion_signatures<ex::set_value_t(success), ex::set_error_t(failure)>;
69+
CSender child_sender;
70+
71+
template <ex::receiver Receiver>
72+
state<CSender, std::remove_cvref_t<Receiver>> connect(Receiver&& receiver) && {
73+
return {std::move(this->child_sender), std::forward<Receiver>(receiver)};
74+
}
75+
template <ex::receiver Receiver>
76+
state<CSender, std::remove_cvref_t<Receiver>> connect(Receiver&& receiver) const& {
77+
return {this->child_sender, std::forward<Receiver>(receiver)};
78+
}
79+
};
80+
81+
template <ex::sender CSender>
82+
sender<std::remove_cvref_t<CSender>> operator()(CSender&& child_sender) const {
83+
return {std::forward<CSender>(child_sender)};
84+
}
85+
auto operator()() const { return ex::detail::sender_adaptor{*this}; }
86+
};
87+
inline constexpr expected_to_channel_t expected_to_channel{};
88+
89+
int main() {
90+
ex::sync_wait(ex::just(std::expected<success, failure>(success::one)) | expected_to_channel() |
91+
ex::then([](success) noexcept { std::cout << "success\n"; }) |
92+
ex::upon_error([](failure) noexcept { std::cout << "fail\n"; }));
93+
}

0 commit comments

Comments
 (0)