Skip to content

Commit 0a113ea

Browse files
committed
added a few examples (some incomplete)
1 parent 6f00555 commit 0a113ea

File tree

6 files changed

+417
-1
lines changed

6 files changed

+417
-1
lines changed

CMakePresets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"patch": 0
77
},
88
"include": [
9-
"cmake/CMake${hostSystemName}Presets.json"
9+
"cmake/CMakeDarwinPresets.json"
1010
],
1111
"buildPresets": [
1212
{

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
)
1619

1720
foreach(EXAMPLE ${EXAMPLES})

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: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 <iostream>
6+
#include <chrono>
7+
#include <string>
8+
#include <tuple>
9+
10+
namespace ex = ::beman::execution26;
11+
using namespace std::string_literals;
12+
using namespace std::chrono_literals;
13+
14+
#include <queue>
15+
#include <tuple>
16+
#include <poll.h>
17+
18+
namespace demo {
19+
struct timer {
20+
struct state_base {
21+
virtual void complete() = 0;
22+
};
23+
template <typename Receiver>
24+
struct state : state_base {
25+
using operation_state_concept = ex::operation_state_t;
26+
timer* self;
27+
std::remove_cvref_t<Receiver> receiver;
28+
std::chrono::milliseconds ms;
29+
30+
template <typename R>
31+
state(timer* self, R&& receiver, std::chrono::milliseconds ms)
32+
: state_base(), self(self), receiver(std::forward<R>(receiver)), ms(ms) {}
33+
void start() & noexcept { self->add(ms, this); }
34+
void complete() override { ex::set_value(std::move(receiver)); }
35+
};
36+
struct sender {
37+
using sender_concept = ex::sender_t;
38+
using completion_signatures = ex::completion_signatures<ex::set_value_t()>;
39+
timer* self;
40+
std::chrono::milliseconds ms;
41+
template <typename R>
42+
state<R> connect(R&& r) {
43+
return state<R>(self, std::forward<R>(r), ms);
44+
}
45+
};
46+
using time_point = std::chrono::system_clock::time_point;
47+
using value_type = std::tuple<time_point, state_base*>;
48+
std::priority_queue<value_type, std::vector<value_type>, std::greater<>> outstanding;
49+
50+
void add(std::chrono::milliseconds ms, state_base* base) {
51+
outstanding.emplace(std::chrono::system_clock::now() + ms, base);
52+
}
53+
bool run_one() {
54+
if (outstanding.empty())
55+
return false;
56+
auto [time, base] = outstanding.top();
57+
outstanding.pop();
58+
auto now{std::chrono::system_clock::now()};
59+
if (now < time) {
60+
auto dur{time - now};
61+
auto ms{std::chrono::duration_cast<std::chrono::milliseconds>(dur)};
62+
poll(nullptr, {}, int(ms.count()));
63+
}
64+
base->complete();
65+
return true;
66+
}
67+
68+
template <typename Receiver>
69+
struct run_state
70+
{
71+
using operation_state_concept = ex::operation_state_t;
72+
using scheduler = decltype(ex::get_delegation_scheduler(ex::get_env(std::declval<Receiver&>())));
73+
74+
timer* self;
75+
Receiver receiver;
76+
void start() & noexcept {
77+
std::cout << "run start\n";
78+
}
79+
};
80+
struct run_sender
81+
{
82+
using sender_concept = ex::sender_t;
83+
using completion_signatures = ex::completion_signatures<ex::set_value_t()>;
84+
85+
timer* self;
86+
87+
template <typename Receiver>
88+
run_state<std::remove_cvref_t<Receiver>> connect(Receiver&& receiver)
89+
{
90+
return { self, std::forward<Receiver>(receiver) };
91+
}
92+
};
93+
94+
auto run() {
95+
#if 0
96+
return run_sender{this};
97+
#else
98+
return ex::then(ex::just(), [this] {
99+
while (this->run_one())
100+
;
101+
});
102+
#endif
103+
}
104+
105+
template <typename T>
106+
auto resume_after(std::chrono::duration<T> d) {
107+
auto ms(std::chrono::duration_cast<std::chrono::milliseconds>(d));
108+
return sender{this, ms};
109+
}
110+
};
111+
} // namespace demo
112+
113+
// ----------------------------------------------------------------------------
114+
// Please see the explanation in docs/intro-examples.md for an explanation.
115+
116+
struct receiver {
117+
using receiver_concept = ex::receiver_t;
118+
void set_value(auto&&...) noexcept {}
119+
void set_error(auto&&) noexcept {}
120+
void set_stopped() noexcept {}
121+
};
122+
static_assert(ex::receiver<receiver>);
123+
124+
int main() {
125+
std::cout << std::unitbuf;
126+
demo::timer timer;
127+
128+
// clang-format off
129+
auto [result] = ex::sync_wait(
130+
ex::when_all(
131+
ex::when_all(
132+
timer.resume_after(3s)
133+
| ex::then([] { std::cout << "h\n"; return std::string("hello"); }),
134+
timer.resume_after(1s)
135+
| ex::then([] { std::cout << ",\n"; return std::string(", "); }),
136+
timer.resume_after(2s)
137+
| ex::then([] { std::cout << "w\n"; return std::string("world"); })
138+
) | ex::then([](auto s1, auto s2, auto s3) { return s1 + s2 + s3; }),
139+
timer.run()
140+
)
141+
).value_or(std::tuple(std::string("")));
142+
// clang-format on
143+
144+
std::cout << result << "\n";
145+
}

0 commit comments

Comments
 (0)