From d05116b9b03b0f835b20fef4412df096a8c7c988 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dietmar=20K=C3=BChl?=
Date: Sun, 21 Dec 2025 23:27:05 +0100
Subject: [PATCH 1/9] added a basic timer
---
Makefile | 4 +-
examples/CMakeLists.txt | 1 +
examples/tst-basic-timer.cpp | 53 +++++++++
examples/tst-repeat_effect_until.hpp | 92 ++++++++++++++++
examples/tst.hpp | 158 +++++++++++++++++++++++++++
5 files changed, 306 insertions(+), 2 deletions(-)
create mode 100644 examples/tst-basic-timer.cpp
create mode 100644 examples/tst-repeat_effect_until.hpp
create mode 100644 examples/tst.hpp
diff --git a/Makefile b/Makefile
index b956d026..b43055a4 100644
--- a/Makefile
+++ b/Makefile
@@ -56,8 +56,8 @@ ifeq (${hostSystemName},Darwin)
export GCC_DIR:=$(shell realpath ${GCC_PREFIX})
export CMAKE_CXX_STDLIB_MODULES_JSON=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json
- export CXX:=g++-15
- export CXXFLAGS:=-stdlib=libstdc++
+ # export CXX:=g++-15
+ # export CXXFLAGS:=-stdlib=libstdc++
export GCOV="gcov"
else ifeq (${hostSystemName},Linux)
export LLVM_DIR=/usr/lib/llvm-20
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index baa1019e..757a96ff 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -5,6 +5,7 @@
list(
APPEND EXAMPLES
+ tst-basic-timer
stackoverflow
inspect
playground
diff --git a/examples/tst-basic-timer.cpp b/examples/tst-basic-timer.cpp
new file mode 100644
index 00000000..f0ed3af3
--- /dev/null
+++ b/examples/tst-basic-timer.cpp
@@ -0,0 +1,53 @@
+// examples/tst-basic-timer.cpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// ----------------------------------------------------------------------------
+
+#include
+#include
+#include "tst.hpp"
+namespace ex = beman::execution;
+
+// ----------------------------------------------------------------------------
+
+struct receiver {
+ using receiver_concept = ex::receiver_t;
+
+ auto set_value() && noexcept {
+ std::cout << "timer done\n";
+ }
+ auto set_error(const std::exception_ptr&) && noexcept {
+ std::cout << "timer error\n";
+ }
+ auto set_stopped() && noexcept {
+ std::cout << "timer stopped\n";
+ }
+};
+
+std::ostream& fmt_now(std::ostream& os) {
+ return os << std::chrono::system_clock::now();
+}
+
+auto main() -> int {
+ std::size_t count{3};
+ ex::sync_wait(
+ tst::repeat_effect_until(
+ ex::just() | ex::then([]() noexcept { std::cout << "effect\n"; }),
+ [&count]() noexcept { return --count == 0u; })
+ );
+
+ tst::timer timer{};
+ std::cout << fmt_now << ": start\n";
+ ex::sync_wait(
+ ex::when_all(
+ tst::resume_after(timer.get_token(), std::chrono::milliseconds(1000))
+ | ex::then([]() noexcept { std::cout << fmt_now << ": 1000ms timer fired\n"; }),
+ tst::resume_after(timer.get_token(), std::chrono::milliseconds(500))
+ | ex::then([]() noexcept { std::cout << fmt_now << ": 500ms timer fired\n"; }),
+ tst::resume_after(timer.get_token(), std::chrono::milliseconds(1500))
+ | ex::then([]() noexcept { std::cout << fmt_now << ": 1500ms timer fired\n"; }),
+ timer.when_done() | ex::then([]() noexcept { std::cout << fmt_now << ": timer done\n"; }),
+ ex::just()
+ )
+ );
+
+}
diff --git a/examples/tst-repeat_effect_until.hpp b/examples/tst-repeat_effect_until.hpp
new file mode 100644
index 00000000..ce56e5cc
--- /dev/null
+++ b/examples/tst-repeat_effect_until.hpp
@@ -0,0 +1,92 @@
+// examples/tst-repeat_effect_until.hpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// ----------------------------------------------------------------------------
+
+#ifndef INCLUDED_EXAMPLES_TST_REPEAT_EFFECT_UNTIL
+#define INCLUDED_EXAMPLES_TST_REPEAT_EFFECT_UNTIL
+
+#include
+#include
+#include
+#include
+
+// ----------------------------------------------------------------------------
+
+namespace tst {
+ namespace ex = beman::execution;
+
+ template
+ struct connector {
+ decltype(ex::connect(::std::declval(), ::std::declval())) op;
+ connector(auto sndr, auto rcvr)
+ : op(ex::connect(::std::move(sndr), ::std::move(rcvr))) {}
+
+ auto start() & noexcept -> void { ex::start(this->op); }
+ };
+
+ inline constexpr struct repeat_effect_unilt_t {
+ template
+ struct sender {
+ using sender_concept = ex::sender_t;
+ using completion_signatures
+ = ex::completion_signatures;
+
+ template
+ struct state {
+ using operation_state_concept = ex::operation_state_t;
+ struct own_receiver {
+ using receiver_concept = ex::receiver_t;
+ state* s;
+ auto set_value() && noexcept -> void {
+ static_assert(ex::receiver);
+ this->s->next();
+ }
+ auto set_error(std::exception_ptr error) && noexcept -> void {
+ ex::set_error(::std::move(this->s->receiver), std::move(error));
+ }
+ auto set_stopped() && noexcept -> void {
+ ex::set_stopped(::std::move(this->s->receiver));
+ }
+ };
+
+ std::remove_cvref_t child;
+ std::remove_cvref_t fun;
+ std::remove_cvref_t receiver;
+ std::optional, own_receiver>> child_op;
+
+ auto start() & noexcept -> void {
+ static_assert(ex::operation_state);
+ this->run_one();
+ }
+ auto run_one() & noexcept -> void {
+ this->child_op.emplace(this->child, own_receiver{this});
+ this->child_op->start();
+ }
+ auto next() & noexcept -> void {
+ if (this->fun()) {
+ ex::set_value(::std::move(this->receiver));
+ } else {
+ this->run_one();
+ }
+ }
+ };
+
+ std::remove_cvref_t child;
+ std::remove_cvref_t fun;
+
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ static_assert(ex::sender);
+ return state(this->child, this->fun, ::std::forward(receiver));
+ }
+ };
+ template
+ auto operator()(Child&& child, Pred&& pred) const -> sender {
+ return {::std::forward(child), ::std::forward(pred)};
+ }
+ } repeat_effect_until{};
+}
+
+// ----------------------------------------------------------------------------
+
+#endif
diff --git a/examples/tst.hpp b/examples/tst.hpp
new file mode 100644
index 00000000..cf20b95a
--- /dev/null
+++ b/examples/tst.hpp
@@ -0,0 +1,158 @@
+// examples/tst.hpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// ----------------------------------------------------------------------------
+
+#ifndef INCLUDED_EXAMPLES_TST
+#define INCLUDED_EXAMPLES_TST
+
+#include "tst-repeat_effect_until.hpp"
+#include
+#include
+#include
+#include
+
+// ----------------------------------------------------------------------------
+
+namespace tst {
+ namespace ex = beman::execution;
+
+ class timer;
+
+ inline constexpr struct resume_after_t {
+ auto operator()(auto token, std::chrono::milliseconds duration) const noexcept {
+ return token.resume_after(duration);
+ }
+ } resume_after{};
+}
+
+// ----------------------------------------------------------------------------
+
+class tst::timer {
+public:
+ class when_done_sender {
+ public:
+ using sender_concept = beman::execution::sender_t;
+ using completion_signatures =
+ beman::execution::completion_signatures;
+ template
+ struct state {
+ using operation_state_concept = beman::execution::operation_state_t;
+ using scheduler_t = decltype(ex::get_scheduler(ex::get_env(std::declval())));
+ struct execute {
+ state* s{};
+ auto operator()() noexcept -> void {
+ this->s->await_one();
+ }
+ };
+ struct pred {
+ state* s{};
+ auto operator()() noexcept -> bool { return this->s->object->queue.empty(); }
+ };
+ using inner_sender = decltype(tst::repeat_effect_until(ex::schedule(std::declval()) | ex::then(execute()), pred()));
+ tst::timer* object;
+ tst::connector inner_op;
+
+ explicit state(tst::timer* obj, Receiver&& rcvr) noexcept
+ : object(obj)
+ , inner_op(tst::repeat_effect_until(
+ ex::schedule(ex::get_scheduler(ex::get_env(rcvr))) | ex::then(execute{this}),
+ pred{this}),
+ std::forward(rcvr))
+ {
+ static_assert(beman::execution::operation_state);
+ }
+
+ auto start() & noexcept -> void {
+ this->inner_op.start();
+ }
+ auto await_one() & noexcept -> void {
+ this->object->await_one();
+ }
+ };
+ explicit when_done_sender(tst::timer* obj) noexcept: object(obj) {
+ static_assert(beman::execution::sender);
+ }
+
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ return state(this->object, ::std::forward(receiver));
+ }
+ private:
+ tst::timer* object;
+ };
+ struct base {
+ virtual auto complete() noexcept -> void = 0;
+ };
+ struct resume_after_sender
+ {
+ using sender_concept = beman::execution::sender_t;
+ using completion_signatures =
+ beman::execution::completion_signatures;
+ template
+ struct state: base {
+ using operation_state_concept = ex::operation_state_t;
+ tst::timer* object;
+ std::chrono::milliseconds duration;
+ std::remove_cvref_t receiver;
+ state(tst::timer* obj,
+ std::chrono::milliseconds dur,
+ Receiver&& rcvr) noexcept
+ : object(obj)
+ , duration(dur)
+ , receiver(std::forward(rcvr)) {
+ static_assert(beman::execution::operation_state);
+ }
+ auto start() & noexcept -> void {
+ this->object->add_timer(this->duration, this);
+ }
+ auto complete() noexcept -> void override {
+ beman::execution::set_value(std::move(this->receiver));
+ }
+ };
+ tst::timer* object;
+ std::chrono::milliseconds duration;
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ static_assert(ex::sender);
+ return state(this->object, this->duration, std::forward(receiver));
+ }
+ };
+ class token {
+ public:
+ explicit token(timer* obj) noexcept: object(obj) {}
+ auto resume_after(std::chrono::milliseconds duration) noexcept -> resume_after_sender {
+ static_assert(ex::sender);
+ return resume_after_sender{this->object, duration};
+ }
+
+ private:
+ timer* object;
+ };
+
+ auto get_token() noexcept -> token { return token(this); }
+ auto when_done() noexcept -> when_done_sender { return when_done_sender(this); }
+
+private:
+ auto await_one() -> void {
+ if (not this->queue.empty()) {
+ auto[time, node] = this->queue.top();
+ this->queue.pop();
+ ::std::this_thread::sleep_until(time);
+ node->complete();
+ }
+ }
+ auto add_timer(std::chrono::milliseconds duration, base* node) noexcept -> void {
+ auto time_point = std::chrono::system_clock::now() + duration;
+ this->queue.emplace(time_point, node);
+ }
+ std::priority_queue,
+ std::vector>,
+ std::greater<>>
+ queue;
+};
+
+// ----------------------------------------------------------------------------
+
+#endif
From eaa28158998bbac6ec74dc8799457bdfa79fc21f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dietmar=20K=C3=BChl?=
Date: Fri, 2 Jan 2026 00:32:31 +0100
Subject: [PATCH 2/9] added the start of a tutorial page and some supporting
process
---
.gitignore | 1 +
CMakeLists.txt | 1 +
bin/mk-doc.py | 69 ++++
docs/code/CMakeLists.txt | 22 ++
{examples => docs/code}/tst-basic-timer.cpp | 0
.../code}/tst-repeat_effect_until.hpp | 0
docs/code/tst-sync_wait.hpp | 51 +++
{examples => docs/code}/tst.hpp | 1 +
docs/overview.md | 2 +-
docs/tutorial.mds | 353 ++++++++++++++++++
examples/CMakeLists.txt | 1 -
11 files changed, 499 insertions(+), 2 deletions(-)
create mode 100755 bin/mk-doc.py
create mode 100644 docs/code/CMakeLists.txt
rename {examples => docs/code}/tst-basic-timer.cpp (100%)
rename {examples => docs/code}/tst-repeat_effect_until.hpp (100%)
create mode 100644 docs/code/tst-sync_wait.hpp
rename {examples => docs/code}/tst.hpp (99%)
create mode 100644 docs/tutorial.mds
diff --git a/.gitignore b/.gitignore
index a376cafa..08376b2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,4 @@ CMakeFiles/
*.log
docs/html
docs/latex
+docs/code/*.cpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e01b3774..409e197c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -105,6 +105,7 @@ endif()
if(BEMAN_EXECUTION_BUILD_EXAMPLES)
add_subdirectory(examples)
+ add_subdirectory(docs/code)
endif()
if(NOT BEMAN_EXECUTION_ENABLE_INSTALL OR CMAKE_SKIP_INSTALL_RULES)
diff --git a/bin/mk-doc.py b/bin/mk-doc.py
new file mode 100755
index 00000000..5727d230
--- /dev/null
+++ b/bin/mk-doc.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python3
+
+import re
+import sys
+
+prestart_re = re.compile("\s*")
+preend_re = re.compile("\s*")
+append_re = re.compile("\s*APPEND EXAMPLES\s*")
+paren_re = re.compile("\)")
+
+def update_cmake(list):
+ text = ""
+ skipping = False
+ with open("docs/code/CMakeLists.txt") as cmake:
+ for line in cmake:
+ if skipping:
+ if paren_re.match(line):
+ skipping = False
+ text += line
+ else:
+ text += line
+ if append_re.match(line):
+ text += "\n".join(list) + "\n"
+ skipping = True
+
+ with open("docs/code/CMakeLists.txt", "w") as cmake:
+ cmake.write(text)
+
+def transform(text, output):
+ capturing = False
+ code = ""
+ name = ""
+ names = {}
+ for line in text:
+ match = prestart_re.match(line)
+ if match:
+ print(f"example: {match.group(1)}")
+ name = match.group(1)
+ code = ""
+ output.write(f"")
+ capturing = True
+ elif capturing and preend_re.match(line):
+ if name in names:
+ print(f"skipping code with duplicate name '{name}'")
+ else:
+ names[name] = code
+ with open("docs/code/" + name + ".cpp", "w") as codeout:
+ codeout.write(code)
+ capturing = False
+ output.write(line)
+ else:
+ if capturing:
+ code += line
+ output.write(f"{line}")
+ update_cmake(names.keys())
+
+def process(root):
+ print(f"processing {root}")
+ text = []
+ with open(root + ".mds") as input:
+ text = input.readlines()
+ with open(root + ".mdt", "w") as output:
+ transform(text, output)
+
+sys.argv.pop(0)
+for file in sys.argv:
+ match = re.match("(.*)\.mds$", file)
+ if match:
+ process(match.group(1))
diff --git a/docs/code/CMakeLists.txt b/docs/code/CMakeLists.txt
new file mode 100644
index 00000000..d4c6a306
--- /dev/null
+++ b/docs/code/CMakeLists.txt
@@ -0,0 +1,22 @@
+# gersemi: off
+# examples/CMakeLists.txt -*-makefile-*-
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# gersemi: on
+
+list(
+ APPEND EXAMPLES
+)
+
+if(BEMAN_USE_MODULES)
+ list(APPEND EXAMPLES modules) # modules.cpp
+endif()
+
+foreach(EXAMPLE ${EXAMPLES})
+ set(EXAMPLE_TARGET ${TARGET_PREFIX}.examples.${EXAMPLE})
+ add_executable(${EXAMPLE_TARGET})
+ target_sources(${EXAMPLE_TARGET} PRIVATE ${EXAMPLE}.cpp)
+ target_link_libraries(
+ ${EXAMPLE_TARGET}
+ PRIVATE ${TARGET_NAMESPACE}::${TARGET_NAME}
+ )
+endforeach()
diff --git a/examples/tst-basic-timer.cpp b/docs/code/tst-basic-timer.cpp
similarity index 100%
rename from examples/tst-basic-timer.cpp
rename to docs/code/tst-basic-timer.cpp
diff --git a/examples/tst-repeat_effect_until.hpp b/docs/code/tst-repeat_effect_until.hpp
similarity index 100%
rename from examples/tst-repeat_effect_until.hpp
rename to docs/code/tst-repeat_effect_until.hpp
diff --git a/docs/code/tst-sync_wait.hpp b/docs/code/tst-sync_wait.hpp
new file mode 100644
index 00000000..358b968e
--- /dev/null
+++ b/docs/code/tst-sync_wait.hpp
@@ -0,0 +1,51 @@
+// docs/code/tst-sync_wait.hpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// ----------------------------------------------------------------------------
+
+#ifndef INCLUDED_DOCS_CODE_TST_SYNC_WAIT
+#define INCLUDED_DOCS_CODE_TST_SYNC_WAIT
+
+#include
+#include
+#include
+
+// ----------------------------------------------------------------------------
+
+namespace tst {
+ template
+ struct sync_wait_sender {
+ template struct is_set_value: std::false_type {};
+ template struct is_set_value: std::true_type {};
+ template struct contains_set_value;
+ template struct contains_set_value>: std::bool_constant<(... || is_set_value::value)> {
+ };
+
+ template ::value> struct add_set_value { using type = T; };
+ template struct add_set_value, false> {
+ using type = beman::execution::completion_signatures<
+ beman::execution::set_value_t(), S...
+ >;
+ };
+ using sender_concept = beman::execution::sender_t;
+ template
+ constexpr auto get_completion_signatures(Env const& e) noexcept {
+ using orig = decltype(beman::execution::get_completion_signatures(std::declval(), e));
+ return typename add_set_value::type{};
+ }
+ std::remove_cvref_t inner;
+ template
+ auto connect(Rcvr&& rcvr) && {
+ return beman::execution::connect(
+ std::move(this->inner),
+ std::forward(rcvr));
+ }
+ };
+ template
+ auto sync_wait(Sender&& sndr) {
+ return beman::execution::sync_wait(sync_wait_sender{std::forward(sndr)});
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+#endif
diff --git a/examples/tst.hpp b/docs/code/tst.hpp
similarity index 99%
rename from examples/tst.hpp
rename to docs/code/tst.hpp
index cf20b95a..f4a8cd70 100644
--- a/examples/tst.hpp
+++ b/docs/code/tst.hpp
@@ -6,6 +6,7 @@
#define INCLUDED_EXAMPLES_TST
#include "tst-repeat_effect_until.hpp"
+#include "tst-sync_wait.hpp"
#include
#include
#include
diff --git a/docs/overview.md b/docs/overview.md
index 0f53a851..53eae7f0 100644
--- a/docs/overview.md
+++ b/docs/overview.md
@@ -667,7 +667,7 @@ The expression into_variant(sender) creates a sender which t
let_value(upstream, fun) -> sender
-`on`
+on(_sched_, _sndr_)
schedule_from(scheduler, sender) -> sender
diff --git a/docs/tutorial.mds b/docs/tutorial.mds
new file mode 100644
index 00000000..9b43209e
--- /dev/null
+++ b/docs/tutorial.mds
@@ -0,0 +1,353 @@
+# Terse Sender Tutorial
+
+This document introduces the use of [`std::execution`](https://wg21.link/exec), the C++ standard library interface to asynchronous operations. The intent is to provide a fast way to get do something using the interfaces.
+
+## Document Conventions
+
+ Additional details are hidden and are accessible by clicking on lines like this.
+
+ It was pointed out for similar documents that useful information was surprisingly available. On the other hand the intent is to avoid clutter which isn't necessary but can be helpful.
+
+
+
+The examples shown won't be complete code and omit, e.g., headers/`import` statements, namespace aliases, etc. The examples are tested using [`beman::execution`](https://github.com/bemanproject/execution) and assume the library is made available using code like this:
+
+
+
+ #include
+ namespace ex = beman::execution;
+
+
+
+ There are or will be other implementations of [`std::execution`](https://wg21.link/exec) and the examples should work with these implementations assuming the declarations are suitably made available:
+
+
+ - The standard C++ library should ship with an implementation with C++26 mode (`-std=c++26`) or later using (as of 2025-12-31 none does, though):
+
+ import std;
+ using ex = std::execution;
+
+
+ - [`stdexec`](https://github.com/NVIDIA/stdexec) using
+
+ #include
+ namespace ex = stdexec;
+
+
+ - [Unifex](https://github.com/facebookexperimental/libunifex) implements an interface which is similar to the specification but predates [`std::execution`](https://wg21.link/exec). Basic examples should work using:
+
+ #include
+ namespace ex = unifex;
+
+
+
+
+
+
+ A library of asynchronous components augmenting the standard C++ library is accessed using:
+
+ #include
+
+
+The standard C++ library doesn't, yet, provide many asynchronous components. To create more realistic examples this tutorial uses a number of components defined in a header file. The implementation of all used components will be described in this tutorial. All additional components live in the namespace `tst`.
+
+## Basic Abstraction: Senders
+
+The key abstraction of [`std::execution`](https://wg21.link/exec) are [_senders_](https://wg21.link/exec.snd). A sender is a fully curried asynchronous function representing some work. There are a few steps needed to get the work represented by a sender executed which are relevant when creating library components using senders. The details will be provided when explaining how to create senders. For now, it is sufficient to know that ex::sync_wait(_sender_) can execute _sender_ and obtain the result of that execution (senders are normally executed differently).
+
+### Standard Library Building Blocks
+
+The first few examples will use the asynchronous interface for synchronous operations to demonstrate how the components are used. The expression ex::just(_args_...) is used to get a sender which completes successful and produces the values _args_... when it gets `start`ed. Likewise, the expression ex::just_error(e) is used to get a sender which completes with the error _e_ when it gets `start`ed. Asynchronous operations can also be stopped (cancelled) without either producing a value or an error and the expression `ex::just_stopped()` produces a sender which completes indicating that it got stopped when it gets `start`ed.
+
+
+The function ex::sync_wait(_sender_) `start`s the argument _sender_, awaits the results, and `return`s them.
+The result of `ex::sync_wait` is an std::optional<std::tuple<_T_...>> to cope with the kinds of results an asynchronous function can produce:
+
+ -
+ When the asynchronous function completes successfully a list of values is produced which become the `tuple` elements in the `optional`, e.g.:
+
+
+
+ auto[b, i] = *ex::sync_wait(ex::just(true, 1));
+ std::cout << "b=" << b << ", i=" << i << '\n';
+
+
+
+ #include
+ #include
+ namespace ex = beman::execution;
+ int main() {
+ auto[b, i] = *ex::sync_wait(ex::just(true, 1));
+ std::cout << "b=" << b << ", i=" << i << '\n';
+ }
+
+
+
+ -
+ When the asynchronous function completes with an error the error is thrown as an exception, e.g.:
+
+
+
+ try { tst::sync_wait(ex::just_error(17)); }
+ catch (int e) { std::cout << "error=" << e << '\n'; }
+
+
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ try { tst::sync_wait(ex::just_error(17)); }
+ catch (int e) { std::cout << "error=" << e << '\n'; }
+ }
+
+
+
+ -
+ When the asynchronous function is asked to stop prematurely it is indicated by a disengaged `optional`, e.g.:
+
+
+
+ if (not tst::sync_wait(ex::just_stopped())) {
+ std::cout << "the operation was cancelled\n";
+ }
+
+
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ if (not tst::sync_wait(ex::just_stopped())) {
+ std::cout << "the operation was cancelled\n";
+ }
+ }
+
+
+
+
+The `just` family of sender algorithms and `sync_wait` are basic building blocks for creating and executing senders. The standard library ships with more building blocks to compose senders, i.e., asynchronous work. Some of the algorithms are _pipeable_ which means that instead of writing _algo_(_sender_, _args_...) it is possible to write _sender_ | _algo_(_args_...). This alternative notation results in composing the algorithms in processing order instead of the algorithm which is executed first being the most nested one. Where applicable, the summary below shows the pipe notation.
+
+ -
+
+
+ The expressions _sender_ | ex::then(_fun_), _sender_ | ex::upon_error(_fun_), and _sender_ | ex::upon_stopped(_fun_) adapt _sender_ to transform the respective results into a new result which is obtained by calling _fun_(_res_...).
+
+ The expressions ex::then(_sender_, _fun_), ex::upon_error(_sender_, _fun_), and ex::upon_stopped(_sender_, _fun_) adapt _sender_ into a new sender _asndr_. When _asndr_ is started the adapted _sender_ is started and its completion is awaited. If _sender_ completes correspondingly (successful for `ex::then`, erronously for `ex::upon_error`, and stopped for `ex::upon_stopped`) the function _fun_ is invoked with the result value(s) as arguments(s). The result of this invocation of _fun_ becomes the [successful] result of _asndr_'s completion. When _sender_ completes differently, the result of _sender_ is forwarded as _asndr_'s completion.
+ Here are a few examples:
+
+ -
+
+
+ Project the successful results to the last one:
+
+ auto[r] = *ex::sync_wait(
+ ex::just(true, 17)
+ | ex::then([](bool, int i){ return i; }));
+
+
+
+ #include
+ #include
+ namespace ex = beman::execution;
+
+ int main() {
+ auto[r] = *ex::sync_wait(
+ ex::just(true, 17)
+ | ex::then([](bool, int i){ return i; }));
+ std::cout << "r=" << r << '\n';
+ }
+
+
+
+
+ -
+
+
+ An unsuccessful result pass to `then` gets passed through:
+
+ auto o = tst::sync_wait(
+ ex::just_stopped()
+ | ex::then([](bool, int i){ return i; }));
+
+
+ Only the succesful result is transformed by `then`. Error or stopped results are just passed through. In this case the stopped result results in the optional returned from `sync_wait` not being set.
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+
+ int main() {
+ auto o = tst::sync_wait(
+ ex::just_stopped()
+ | ex::then([](bool, int i){ return i; }));
+ std::cout << "o=" << (o? "": "not ") << "set\n";
+ }
+
+
+
+ -
+
+
+ Turn an error into an unexpected value:
+
+ auto[r] = *ex::sync_wait(
+ ex::just_error(17)
+ | ex::upon_error([](int i){
+ return std::execpted(std::unexpected, i); }));
+
+
+
+ #include
+ #include
+ #include
+ namespace ex = beman::execution;
+
+ int main() {
+ auto[r] = *ex::sync_wait(
+ ex::just_error(17)
+ | ex::upon_error([](int i){
+ return std::expected(std::unexpected{i}); }));
+ std::cout << "r=" << r.error() << '\n';
+ }
+
+
+
+
+
+
+
+
+For example, `tst::resume_after(500ms)` is a concrete example of a sender whose work consists of doing nothing and completing after `500ms`. One way to `start` this sender and awaiting its completion is using ex::sync_wait(_sender_):
+
+
+
+
+ ex::sync_wait(tst::resume_after(500ms));
+
+
+
+ #include <beman/execution.hpp>
+ namespace ex = beman::execution;
+ #include <tst.hpp>
+ #include <chrono>
+ using std::chrono::literals;
+ int main() {
+ ex::sync_wait(tst::resume_after(500ms));
+ }
+
+
+
+Normally senders aren't `start` directly like that. Instead they are composed into using sender algorithms. For a simple example the sender above could be composed with the ex::then algorithm which executes a function with the results of the sender it is composed with:
+
+
+
+
+ ex::sync_wait(
+ tst::resume_after(500ms)
+ | ex::then([]{ std::cout << "waited 500ms\n"; })
+ );
+
+
+ TODO: provide the complete code
+
+
+A composed sender is a sender which can be further composed. For example, the `ex::when_all` algorithm takes multiple senders as argument and completes when all senders completed:
+
+
+
+
+ auto work = [](auto dur) {
+ return tst::resume_after(dur)
+ | ex::then([]{ std::cout << "waited " << dur << "\n"; });
+ };
+ ex::sync_wait(
+ ex::when_all(work(500ms), work(200ms), work(100ms))
+ );
+
+
+ TODO: provide the complete code
+
+
+Senders can be composed using `ex::task<>` which can `co_await` senders and is itself a sender:
+
+
+
+
+ auto work = [](auto dur, auto fun)->ex::task<> {
+ co_await tst::resume_after(dur);
+ for (int i{}; i < 100; ++) {
+ co_await tst::resume_after(100ms);
+ fun(i);
+ }
+ };
+ ex::sync_wait(
+ ex::when_all(
+ work(3ms, [](int i){
+ (i % 3) && (i % 5) && (std::cout << i);
+ std::cout << '\n';
+ )}),
+ work(1ms, [](int i){ (i % 3) || (std::cout << "fizz"); }),
+ work(2ms, [](int i){ (i % 5) || (std::cout << "buzz"); })
+ );
+
+
+ TODO: provide the complete program
+
+
+If the work isn't known in advanced something is needed to `start` new work and await its completion. This is the purpose of `ex::counting_scope`: it tracks outstanding work and provides a sender which completes when it becomes empty:
+
+
+
+
+ auto work = [](auto fun)->ex::task<> {
+ for (int i{}; i < 100; ++i) {
+ co_await ex::resume_after(100ms);
+ fun(i);
+ }
+ };
+ ex::counting_scope scope;
+ ex::sync_wait([](auto& s)->ex::task<> {
+ co_await tst::resume_after(1ms);
+ ex::spawn(s.get_token(), work([](int i) {
+ (i % 3) && (i % 5) && (std::cout << i);
+ std::cout << '\n';
+ }));
+ co_await tst::resume_after(1ms);
+ ex::spawn(s.get_token(), work([](int i) {
+ (i % 3) || (std::cout << "fizz");
+ }));
+ co_await tst::resume_after(1ms);
+ ex::spawn(s.get_token(), work([](int i) {
+ (i % 5) || (std::cout << "buzz");
+ }));
+ co_await scope.when_empty();
+ }(scope));
+
+
+ TODO: provide a complete example
+
+
+The above are the key tools needed to create an application using asynchronous approaches:
+
+
+ -
+ A way to compose senders from more basic senders using sender algorithms.
+
+ -
+ `start` a sender and synchronously await its completion: `ex::sync_wait`.
+
+ -
+ Create new work, get it `start`ed, and its completion awaited.
+
+ -
+ `ex::task` makes composition of senders easier in many cases.
+
+
+
+ The above doesn't explain how sender are implemented or what tools are available. There are more advanced needs
+
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 757a96ff..baa1019e 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -5,7 +5,6 @@
list(
APPEND EXAMPLES
- tst-basic-timer
stackoverflow
inspect
playground
From bac60a66088a14feb7f9a010c42b6166e2f83d53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dietmar=20K=C3=BChl?=
Date: Sat, 3 Jan 2026 01:51:20 +0100
Subject: [PATCH 3/9] improved the presentation
---
.gitignore | 1 +
Makefile | 3 +-
bin/mk-doc.py | 2 +-
docs/code/CMakeLists.txt | 11 +
docs/code/tst-config.hpp | 16 +
docs/code/tst-repeat_effect_until.hpp | 4 +-
docs/code/tst-sync_wait.hpp | 26 +-
docs/code/tst-timer.hpp | 156 +++++++++
docs/code/tst.hpp | 151 +--------
docs/tutorial.mds | 442 +++++++++++++++++---------
10 files changed, 503 insertions(+), 309 deletions(-)
create mode 100644 docs/code/tst-config.hpp
create mode 100644 docs/code/tst-timer.hpp
diff --git a/.gitignore b/.gitignore
index 08376b2e..166fdc85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,4 @@ CMakeFiles/
docs/html
docs/latex
docs/code/*.cpp
+docs/tutorial.md
diff --git a/Makefile b/Makefile
index b43055a4..88e7925c 100644
--- a/Makefile
+++ b/Makefile
@@ -105,6 +105,7 @@ run: test
./$(BUILD)/examples/$(EXAMPLE)
doc:
+ ./bin/mk-doc.py docs/*.mds
doxygen docs/Doxyfile
# $(SANITIZERS):
@@ -178,7 +179,7 @@ todo:
bin/mk-todo.py
unstage:
- git restore --staged tests/beman/execution/CMakeLists.txt
+ git restore --staged tests/beman/execution/CMakeLists.txt docs/code/CMakeLists.txt
.PHONY: clean-doc
clean-doc:
diff --git a/bin/mk-doc.py b/bin/mk-doc.py
index 5727d230..ec5f0f63 100755
--- a/bin/mk-doc.py
+++ b/bin/mk-doc.py
@@ -59,7 +59,7 @@ def process(root):
text = []
with open(root + ".mds") as input:
text = input.readlines()
- with open(root + ".mdt", "w") as output:
+ with open(root + ".md", "w") as output:
transform(text, output)
sys.argv.pop(0)
diff --git a/docs/code/CMakeLists.txt b/docs/code/CMakeLists.txt
index d4c6a306..97e3d36a 100644
--- a/docs/code/CMakeLists.txt
+++ b/docs/code/CMakeLists.txt
@@ -5,6 +5,17 @@
list(
APPEND EXAMPLES
+sync_wait-just
+sync_wait-just_error
+sync_wait-just_stopped
+just-then
+just-then-throw
+just_error-then
+just_stopped-then
+upon_error
+expected
+upon_stopped
+when_all
)
if(BEMAN_USE_MODULES)
diff --git a/docs/code/tst-config.hpp b/docs/code/tst-config.hpp
new file mode 100644
index 00000000..3df73477
--- /dev/null
+++ b/docs/code/tst-config.hpp
@@ -0,0 +1,16 @@
+// docs/code/tst-config.hpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef INCLUDED_DOCS_CODE_TST_CONFIG
+#define INCLUDED_DOCS_CODE_TST_CONFIG
+
+// ----------------------------------------------------------------------------
+
+#include
+namespace tst {
+ namespace ex = beman::execution;
+}
+
+// ----------------------------------------------------------------------------
+
+#endif
diff --git a/docs/code/tst-repeat_effect_until.hpp b/docs/code/tst-repeat_effect_until.hpp
index ce56e5cc..0a285c9b 100644
--- a/docs/code/tst-repeat_effect_until.hpp
+++ b/docs/code/tst-repeat_effect_until.hpp
@@ -5,16 +5,14 @@
#ifndef INCLUDED_EXAMPLES_TST_REPEAT_EFFECT_UNTIL
#define INCLUDED_EXAMPLES_TST_REPEAT_EFFECT_UNTIL
-#include
#include
#include
#include
+#include "tst-config.hpp"
// ----------------------------------------------------------------------------
namespace tst {
- namespace ex = beman::execution;
-
template
struct connector {
decltype(ex::connect(::std::declval(), ::std::declval())) op;
diff --git a/docs/code/tst-sync_wait.hpp b/docs/code/tst-sync_wait.hpp
index 358b968e..878c9a5f 100644
--- a/docs/code/tst-sync_wait.hpp
+++ b/docs/code/tst-sync_wait.hpp
@@ -5,44 +5,44 @@
#ifndef INCLUDED_DOCS_CODE_TST_SYNC_WAIT
#define INCLUDED_DOCS_CODE_TST_SYNC_WAIT
-#include
#include
#include
+#include "tst-config.hpp"
// ----------------------------------------------------------------------------
namespace tst {
- template
+ template
struct sync_wait_sender {
template struct is_set_value: std::false_type {};
- template struct is_set_value: std::true_type {};
+ template struct is_set_value: std::true_type {};
template struct contains_set_value;
- template struct contains_set_value>: std::bool_constant<(... || is_set_value::value)> {
+ template struct contains_set_value>: std::bool_constant<(... || is_set_value::value)> {
};
template ::value> struct add_set_value { using type = T; };
- template struct add_set_value, false> {
- using type = beman::execution::completion_signatures<
- beman::execution::set_value_t(), S...
+ template struct add_set_value, false> {
+ using type = tst::ex::completion_signatures<
+ tst::ex::set_value_t(), S...
>;
};
- using sender_concept = beman::execution::sender_t;
+ using sender_concept = tst::ex::sender_t;
template
constexpr auto get_completion_signatures(Env const& e) noexcept {
- using orig = decltype(beman::execution::get_completion_signatures(std::declval(), e));
+ using orig = decltype(tst::ex::get_completion_signatures(std::declval(), e));
return typename add_set_value::type{};
}
std::remove_cvref_t inner;
- template
+ template
auto connect(Rcvr&& rcvr) && {
- return beman::execution::connect(
+ return tst::ex::connect(
std::move(this->inner),
std::forward(rcvr));
}
};
- template
+ template
auto sync_wait(Sender&& sndr) {
- return beman::execution::sync_wait(sync_wait_sender{std::forward(sndr)});
+ return tst::ex::sync_wait(sync_wait_sender{std::forward(sndr)});
}
}
diff --git a/docs/code/tst-timer.hpp b/docs/code/tst-timer.hpp
new file mode 100644
index 00000000..a311ee13
--- /dev/null
+++ b/docs/code/tst-timer.hpp
@@ -0,0 +1,156 @@
+// docs/code/tst-timer.hpp -*-C++-*-
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef INCLUDED_DOCS_CODE_TST_TIMER
+#define INCLUDED_DOCS_CODE_TST_TIMER
+
+// ----------------------------------------------------------------------------
+
+#include
+#include
+#include
+#include "tst-config.hpp"
+
+// ----------------------------------------------------------------------------
+
+namespace tst {
+ class timer;
+
+ inline constexpr struct resume_after_t {
+ auto operator()(auto token, std::chrono::milliseconds duration) const noexcept {
+ return token.resume_after(duration);
+ }
+ } resume_after{};
+}
+
+// ----------------------------------------------------------------------------
+
+class tst::timer {
+public:
+ class when_done_sender {
+ public:
+ using sender_concept = tst::ex::sender_t;
+ using completion_signatures =
+ tst::ex::completion_signatures;
+ template
+ struct state {
+ using operation_state_concept = tst::ex::operation_state_t;
+ using scheduler_t = decltype(ex::get_scheduler(ex::get_env(std::declval())));
+ struct execute {
+ state* s{};
+ auto operator()() noexcept -> void {
+ this->s->await_one();
+ }
+ };
+ struct pred {
+ state* s{};
+ auto operator()() noexcept -> bool { return this->s->object->queue.empty(); }
+ };
+ using inner_sender = decltype(tst::repeat_effect_until(ex::schedule(std::declval()) | ex::then(execute()), pred()));
+ tst::timer* object;
+ tst::connector inner_op;
+
+ explicit state(tst::timer* obj, Receiver&& rcvr) noexcept
+ : object(obj)
+ , inner_op(tst::repeat_effect_until(
+ ex::schedule(ex::get_scheduler(ex::get_env(rcvr))) | ex::then(execute{this}),
+ pred{this}),
+ std::forward(rcvr))
+ {
+ static_assert(tst::ex::operation_state);
+ }
+
+ auto start() & noexcept -> void {
+ this->inner_op.start();
+ }
+ auto await_one() & noexcept -> void {
+ this->object->await_one();
+ }
+ };
+ explicit when_done_sender(tst::timer* obj) noexcept: object(obj) {
+ static_assert(tst::ex::sender);
+ }
+
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ return state(this->object, ::std::forward(receiver));
+ }
+ private:
+ tst::timer* object;
+ };
+ struct base {
+ virtual auto complete() noexcept -> void = 0;
+ };
+ struct resume_after_sender
+ {
+ using sender_concept = tst::ex::sender_t;
+ using completion_signatures =
+ tst::ex::completion_signatures;
+ template
+ struct state: base {
+ using operation_state_concept = ex::operation_state_t;
+ tst::timer* object;
+ std::chrono::milliseconds duration;
+ std::remove_cvref_t receiver;
+ state(tst::timer* obj,
+ std::chrono::milliseconds dur,
+ Receiver&& rcvr) noexcept
+ : object(obj)
+ , duration(dur)
+ , receiver(std::forward(rcvr)) {
+ static_assert(tst::ex::operation_state);
+ }
+ auto start() & noexcept -> void {
+ this->object->add_timer(this->duration, this);
+ }
+ auto complete() noexcept -> void override {
+ tst::ex::set_value(std::move(this->receiver));
+ }
+ };
+ tst::timer* object;
+ std::chrono::milliseconds duration;
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ static_assert(ex::sender);
+ return state(this->object, this->duration, std::forward(receiver));
+ }
+ };
+ class token {
+ public:
+ explicit token(timer* obj) noexcept: object(obj) {}
+ auto resume_after(std::chrono::milliseconds duration) noexcept -> resume_after_sender {
+ static_assert(ex::sender);
+ return resume_after_sender{this->object, duration};
+ }
+
+ private:
+ timer* object;
+ };
+
+ auto get_token() noexcept -> token { return token(this); }
+ auto when_done() noexcept -> when_done_sender { return when_done_sender(this); }
+
+private:
+ auto await_one() -> void {
+ if (not this->queue.empty()) {
+ auto[time, node] = this->queue.top();
+ this->queue.pop();
+ ::std::this_thread::sleep_until(time);
+ node->complete();
+ }
+ }
+ auto add_timer(std::chrono::milliseconds duration, base* node) noexcept -> void {
+ auto time_point = std::chrono::system_clock::now() + duration;
+ this->queue.emplace(time_point, node);
+ }
+ std::priority_queue,
+ std::vector>,
+ std::greater<>>
+ queue;
+};
+
+// ----------------------------------------------------------------------------
+
+#endif
diff --git a/docs/code/tst.hpp b/docs/code/tst.hpp
index f4a8cd70..d7541087 100644
--- a/docs/code/tst.hpp
+++ b/docs/code/tst.hpp
@@ -1,159 +1,12 @@
// examples/tst.hpp -*-C++-*-
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-// ----------------------------------------------------------------------------
#ifndef INCLUDED_EXAMPLES_TST
#define INCLUDED_EXAMPLES_TST
+#include "tst-config.hpp"
#include "tst-repeat_effect_until.hpp"
#include "tst-sync_wait.hpp"
-#include
-#include
-#include
-#include
-
-// ----------------------------------------------------------------------------
-
-namespace tst {
- namespace ex = beman::execution;
-
- class timer;
-
- inline constexpr struct resume_after_t {
- auto operator()(auto token, std::chrono::milliseconds duration) const noexcept {
- return token.resume_after(duration);
- }
- } resume_after{};
-}
-
-// ----------------------------------------------------------------------------
-
-class tst::timer {
-public:
- class when_done_sender {
- public:
- using sender_concept = beman::execution::sender_t;
- using completion_signatures =
- beman::execution::completion_signatures;
- template
- struct state {
- using operation_state_concept = beman::execution::operation_state_t;
- using scheduler_t = decltype(ex::get_scheduler(ex::get_env(std::declval())));
- struct execute {
- state* s{};
- auto operator()() noexcept -> void {
- this->s->await_one();
- }
- };
- struct pred {
- state* s{};
- auto operator()() noexcept -> bool { return this->s->object->queue.empty(); }
- };
- using inner_sender = decltype(tst::repeat_effect_until(ex::schedule(std::declval()) | ex::then(execute()), pred()));
- tst::timer* object;
- tst::connector inner_op;
-
- explicit state(tst::timer* obj, Receiver&& rcvr) noexcept
- : object(obj)
- , inner_op(tst::repeat_effect_until(
- ex::schedule(ex::get_scheduler(ex::get_env(rcvr))) | ex::then(execute{this}),
- pred{this}),
- std::forward(rcvr))
- {
- static_assert(beman::execution::operation_state);
- }
-
- auto start() & noexcept -> void {
- this->inner_op.start();
- }
- auto await_one() & noexcept -> void {
- this->object->await_one();
- }
- };
- explicit when_done_sender(tst::timer* obj) noexcept: object(obj) {
- static_assert(beman::execution::sender);
- }
-
- template
- auto connect(Receiver&& receiver) const& noexcept -> state {
- return state(this->object, ::std::forward(receiver));
- }
- private:
- tst::timer* object;
- };
- struct base {
- virtual auto complete() noexcept -> void = 0;
- };
- struct resume_after_sender
- {
- using sender_concept = beman::execution::sender_t;
- using completion_signatures =
- beman::execution::completion_signatures;
- template
- struct state: base {
- using operation_state_concept = ex::operation_state_t;
- tst::timer* object;
- std::chrono::milliseconds duration;
- std::remove_cvref_t receiver;
- state(tst::timer* obj,
- std::chrono::milliseconds dur,
- Receiver&& rcvr) noexcept
- : object(obj)
- , duration(dur)
- , receiver(std::forward(rcvr)) {
- static_assert(beman::execution::operation_state);
- }
- auto start() & noexcept -> void {
- this->object->add_timer(this->duration, this);
- }
- auto complete() noexcept -> void override {
- beman::execution::set_value(std::move(this->receiver));
- }
- };
- tst::timer* object;
- std::chrono::milliseconds duration;
- template
- auto connect(Receiver&& receiver) const& noexcept -> state {
- static_assert(ex::sender);
- return state(this->object, this->duration, std::forward(receiver));
- }
- };
- class token {
- public:
- explicit token(timer* obj) noexcept: object(obj) {}
- auto resume_after(std::chrono::milliseconds duration) noexcept -> resume_after_sender {
- static_assert(ex::sender);
- return resume_after_sender{this->object, duration};
- }
-
- private:
- timer* object;
- };
-
- auto get_token() noexcept -> token { return token(this); }
- auto when_done() noexcept -> when_done_sender { return when_done_sender(this); }
-
-private:
- auto await_one() -> void {
- if (not this->queue.empty()) {
- auto[time, node] = this->queue.top();
- this->queue.pop();
- ::std::this_thread::sleep_until(time);
- node->complete();
- }
- }
- auto add_timer(std::chrono::milliseconds duration, base* node) noexcept -> void {
- auto time_point = std::chrono::system_clock::now() + duration;
- this->queue.emplace(time_point, node);
- }
- std::priority_queue,
- std::vector>,
- std::greater<>>
- queue;
-};
-
-// ----------------------------------------------------------------------------
+#include "tst-timer.hpp"
#endif
diff --git a/docs/tutorial.mds b/docs/tutorial.mds
index 9b43209e..a3499a86 100644
--- a/docs/tutorial.mds
+++ b/docs/tutorial.mds
@@ -10,22 +10,34 @@ This document introduces the use of [`std::execution`](https://wg21.link/exec),
-The examples shown won't be complete code and omit, e.g., headers/`import` statements, namespace aliases, etc. The examples are tested using [`beman::execution`](https://github.com/bemanproject/execution) and assume the library is made available using code like this:
+The examples shown won't be complete code and omit, e.g.,
+`#include`/`import` statements, namespace aliases, etc. The examples
+are tested using
+[`beman::execution`](https://github.com/bemanproject/execution) and
+assume the library is made available using code like this together
+with namespace alias `ex` for the namespace for `beman::execution`:
-
-
- #include
- namespace ex = beman::execution;
-
-
+ `#include `
- There are or will be other implementations of [`std::execution`](https://wg21.link/exec) and the examples should work with these implementations assuming the declarations are suitably made available:
+ There are or will be multiple implementations of
+ [`std::execution`](https://wg21.link/exec) and the examples
+ should work with these implementations assuming the declarations
+ are suitably made available:
+The standard library doesn't readily provide components for certain asynchronous
+operations, it is lacking some algorithms, or the interface as some quirks which
+would cause unnecessary complexities. To make the tutorial go smoothly it uses a
+collections of components mode available by a header:
-
- A library of asynchronous components augmenting the standard C++ library is accessed using:
-
- #include
-
-
-The standard C++ library doesn't, yet, provide many asynchronous components. To create more realistic examples this tutorial uses a number of components defined in a header file. The implementation of all used components will be described in this tutorial. All additional components live in the namespace `tst`.
+ `#include "tst.hpp"`
+ These components use names from the use implementation of
+ [`std::execution`](https://wg21.link/exec). By default the names from
+ [`beman::execution`](https://github.com/bemanproject/execution) are
+ used. The used names are set up via
+ [`docs/code/tst-config.hpp`](https://github.com/bemanproject/execution/files/docs/code/tst-config.hpp).
+ When trying to use the examples with a different implementation it should be sufficient to adjust
+ this header accordingly.
## Basic Abstraction: Senders
The key abstraction of [`std::execution`](https://wg21.link/exec) are [_senders_](https://wg21.link/exec.snd). A sender is a fully curried asynchronous function representing some work. There are a few steps needed to get the work represented by a sender executed which are relevant when creating library components using senders. The details will be provided when explaining how to create senders. For now, it is sufficient to know that ex::sync_wait(_sender_) can execute _sender_ and obtain the result of that execution (senders are normally executed differently).
-### Standard Library Building Blocks
+### Fundamental Building Blocks: `just` and `sync_wait`
-The first few examples will use the asynchronous interface for synchronous operations to demonstrate how the components are used. The expression ex::just(_args_...) is used to get a sender which completes successful and produces the values _args_... when it gets `start`ed. Likewise, the expression ex::just_error(e) is used to get a sender which completes with the error _e_ when it gets `start`ed. Asynchronous operations can also be stopped (cancelled) without either producing a value or an error and the expression `ex::just_stopped()` produces a sender which completes indicating that it got stopped when it gets `start`ed.
+To get going with senders, it is necessary to get some sender. One
+fundamental sender is one which [synchronously] produces a particular
+result when it gets started. The expression
+ex::just(_args_...) is used to get a sender which
+completes successful and produces the values _args_...
+when it gets `start`ed. Likewise, the expression
+ex::just_error(e) is used to get a sender which
+completes with the error _e_ when it gets `start`ed.
+Asynchronous operations can also be stopped (cancelled) without
+either producing a value or an error and the expression
+`ex::just_stopped()` produces a sender which completes indicating
+that it got stopped when it gets `start`ed.
-The function ex::sync_wait(_sender_) `start`s the argument _sender_, awaits the results, and `return`s them.
-The result of `ex::sync_wait` is an std::optional<std::tuple<_T_...>> to cope with the kinds of results an asynchronous function can produce:
+Once you got a sender it needs to be `start`ed and the result needs
+to be awaited to get the sender effect. The function
+ex::sync_wait(_sender_) `start`s the argument
+_sender_, awaits the results, and `return`s them. The
+result of `ex::sync_wait` is an
+std::optional<std::tuple<_T_...>> to cope
+with the kinds of results an asynchronous function can produce:
-
- When the asynchronous function completes successfully a list of values is produced which become the `tuple` elements in the `optional`, e.g.:
+ When the asynchronous function used with `sync_wait` completes
+ successfully producing a list of values, `sync_wait` returns a
+ `tuple` of these values in an `optional`:
-
-
- auto[b, i] = *ex::sync_wait(ex::just(true, 1));
- std::cout << "b=" << b << ", i=" << i << '\n';
-
-
+ `auto[b, i] = *ex::sync_wait(ex::just(true, 1));`
- #include
- #include
- namespace ex = beman::execution;
- int main() {
- auto[b, i] = *ex::sync_wait(ex::just(true, 1));
- std::cout << "b=" << b << ", i=" << i << '\n';
- }
-
+ #include
+ #include
+ namespace ex = beman::execution;
+ int main() {
+ auto[b, i] = *ex::sync_wait(ex::just(true, 1));
+ std::cout << "b=" << b << ", i=" << i << "\n";
+ }
+
-
- When the asynchronous function completes with an error the error is thrown as an exception, e.g.:
+ When the asynchronous function used with `sync_wait` completes
+ with an error, `sync_wait` throws the error an exception:
-
-
- try { tst::sync_wait(ex::just_error(17)); }
- catch (int e) { std::cout << "error=" << e << '\n'; }
-
-
+ `try { tst::sync_wait(ex::just_error(17)); } catch (int e) { ... }`
+ This example uses `tst::sync_wait` instead of `ex::sync_wait`
+ because `ex::sync_wait` requires that the passed sender
+ declares exactly one way how it completes successfully.
+ However, `ex::just_error(17)` only declares one way to complete
+ with an error. tst::sync_wait(_sndr_) wraps the
+ _sndr_ into a new sender which claims to complete
+ successfully if _sndr_ doesn't already declare
+ how it can succeed. How to do that will be shown later in the
+ tutorial.
- #include
- #include
- #include "tst.hpp"
- namespace ex = beman::execution;
- int main() {
- try { tst::sync_wait(ex::just_error(17)); }
- catch (int e) { std::cout << "error=" << e << '\n'; }
- }
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ try { tst::sync_wait(ex::just_error(17)); }
+ catch (int e) { std::cout << "error=" << e << "\n"; }
+ }
-
- When the asynchronous function is asked to stop prematurely it is indicated by a disengaged `optional`, e.g.:
+ When the asynchronous function used with `sync_wait` is asked to stop prematurely it is indicated by `sync_wait` returning a disengaged `optional`:
-
-
- if (not tst::sync_wait(ex::just_stopped())) {
- std::cout << "the operation was cancelled\n";
- }
-
-
+ `if (not tst::sync_wait(ex::just_stopped())) { ... }`
+ This example uses `tst::sync_wait` instead of `ex::sync_wait`
+ because `ex::sync_wait` requires that the passed sender
+ declares exactly one way how it completes successfully.
+ However, `ex::just_stopped()` only declares one way to complete
+ indicating that the operation was asked to stop.
+ tst::sync_wait(_sndr_) wraps the _sndr_
+ into a new sender which claims to complete successfully if
+ _sndr_ doesn't already declare how it can succeed.
+ How to do that will be shown later in the tutorial .
- #include
- #include
- #include "tst.hpp"
- namespace ex = beman::execution;
- int main() {
- if (not tst::sync_wait(ex::just_stopped())) {
- std::cout << "the operation was cancelled\n";
- }
- }
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ if (not tst::sync_wait(ex::just_stopped())) {
+ std::cout << "the operation was cancelled\n";
+ }
+ }
-The `just` family of sender algorithms and `sync_wait` are basic building blocks for creating and executing senders. The standard library ships with more building blocks to compose senders, i.e., asynchronous work. Some of the algorithms are _pipeable_ which means that instead of writing _algo_(_sender_, _args_...) it is possible to write _sender_ | _algo_(_args_...). This alternative notation results in composing the algorithms in processing order instead of the algorithm which is executed first being the most nested one. Where applicable, the summary below shows the pipe notation.
+### Composing Senders
+
+The `just` family of sender algorithms and `sync_wait` are basic
+building blocks for creating and executing senders. The standard
+library ships with various algorithms to compose senders: these
+algorithms take one or more senders as well as other parameters and
+produce a new sender. This composition is a major objective of the
+sender interface. This section describes a few algorithms used in
+the examples below.
+
+
+Some of the algorithms are _pipeable_ which means that instead of
+writing _algo_(_sender_, _args_...) it is possible to
+write _sender_ | _algo_(_args_...). This alternative
+notation results in composing the algorithms in processing order.
+Using the call notation causes the starting point to be the most
+nested algorithm. Where applicable, the summary below shows the
+pipe notation.
+
+
-
-
-
- The expressions _sender_ | ex::then(_fun_), _sender_ | ex::upon_error(_fun_), and _sender_ | ex::upon_stopped(_fun_) adapt _sender_ to transform the respective results into a new result which is obtained by calling _fun_(_res_...).
-
- The expressions ex::then(_sender_, _fun_), ex::upon_error(_sender_, _fun_), and ex::upon_stopped(_sender_, _fun_) adapt _sender_ into a new sender _asndr_. When _asndr_ is started the adapted _sender_ is started and its completion is awaited. If _sender_ completes correspondingly (successful for `ex::then`, erronously for `ex::upon_error`, and stopped for `ex::upon_stopped`) the function _fun_ is invoked with the result value(s) as arguments(s). The result of this invocation of _fun_ becomes the [successful] result of _asndr_'s completion. When _sender_ completes differently, the result of _sender_ is forwarded as _asndr_'s completion.
- Here are a few examples:
-
+ Transform results using a function object _fun_:
+
-
-
-
- Project the successful results to the last one:
-
- auto[r] = *ex::sync_wait(
- ex::just(true, 17)
- | ex::then([](bool, int i){ return i; }));
-
-
-
- #include
- #include
- namespace ex = beman::execution;
+
+ common: successful value when _sender_ completes according to the name
+ When _sender_
+ completes according to the name of the algorithm (`then`
+ => success; `upon_error` => error; `upon_stopped` =>
+ stop), _fun_ is invoked with the value
+ produced by _sender_ and the [successful]
+ result of the algorithm is the value returned from this
+ invocation (if any). If the invocation of _fun_
+ throws, the algorithm completes with an error
+ provding an `std::exception_ptr` to caught exception. If
+ _sender_ doesn't complete accordingly to the
+ algorithm's name, the completion is forwarded.
+
+
+ -
+
+ _sender_ | ex::then(_fun_)
+ Transform a successful result using _fun_.
+
+ -
+
+ `ex::just(true, 17) | ex::then([](bool, int i){ return 2 * i; })`
+ Transform the results by doubling the second element and returning it.
+
+ #include
+ #include
+ namespace ex = beman::execution;
- int main() {
- auto[r] = *ex::sync_wait(
- ex::just(true, 17)
- | ex::then([](bool, int i){ return i; }));
- std::cout << "r=" << r << '\n';
- }
-
-
+ int main() {
+ auto[r] = *ex::sync_wait(ex::just(true, 17) | ex::then([](bool, int i){ return 2 * i; }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+
+ -
+
+ `ex::just(true, 17) | ex::then([](bool, int i){ throw 2 * i; })`
+ If the transformation fails with an exception, an error with the caught
+ exception is produced.
+
+ #include
+ #include
+ namespace ex = beman::execution;
+
+ int main() {
+ try { ex::sync_wait(ex::just(true, 17) | ex::then([](bool, int i){ throw 2 * i; })); }
+ catch (int e) { std::cout << "e=" << e << "\n"; }
+ }
+
+
+
+ -
+
+ `ex::just_error(17) | ex::then([](bool, int i){ return 2 * i; })`
+ The transformed sender completes with an error, i.e., the result doesn't get
+ transformed by the function passed to `ex::then`. Instead, the error is passed
+ through.
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+
+ int main() {
+ try {
+ tst::sync_wait(ex::just_error(17) | ex::then([](bool, int i){ return 2 * i; }));
+ }
+ catch (int e) {
+ std::cout << "e=" << e << "\n";
+ }
+ }
+
+
+
+ -
+
+ `ex::just_stopped() | ex::then([](bool, int i){ return 2 * i; })`
+ The transformed sender completes with stopped,
+ i.e., the result doesn't get transformed by the
+ function passed to `ex::then`. Instead, the stopped
+ completion is passed through.
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+
+ int main() {
+ auto o = tst::sync_wait(ex::just_stopped() | ex::then([](bool, int i){ return 2 * i; }));
+ std::cout << "o=" << (o? "": "not ") << "set\n";
+ }
+
+
+
+
+
-
-
-
-
- An unsuccessful result pass to `then` gets passed through:
-
- auto o = tst::sync_wait(
- ex::just_stopped()
- | ex::then([](bool, int i){ return i; }));
-
-
- Only the succesful result is transformed by `then`. Error or stopped results are just passed through. In this case the stopped result results in the optional returned from `sync_wait` not being set.
-
- #include
- #include
- #include "tst.hpp"
- namespace ex = beman::execution;
+
+ _sender_ | ex::upon_error(_fun_)
+ Transform an error result.
+
+ -
+
+ `ex:::just_error(17) | ex::upon_error([](int e){ return e; })`
+
+ #include
+ #include
+ namespace ex = beman::execution;
- int main() {
- auto o = tst::sync_wait(
- ex::just_stopped()
- | ex::then([](bool, int i){ return i; }));
- std::cout << "o=" << (o? "": "not ") << "set\n";
- }
-
-
+ int main() {
+ auto[r] = *ex::sync_wait(ex::just_error(17) | ex::upon_error([](int e){ return e; }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+ -
+
+ turn a result into an `expected`
+ Using both `then` and `upon_error` the result of an
+ operation can be turned into an `std::expected`:
+
+ #include
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+
+ int main() {
+ auto[e] = *tst::sync_wait(
+ ex::just_error(17)
+ | ex::then([](bool, int i) noexcept { return std::expected(i); })
+ | ex::upon_error([](int e) noexcept { return std::expected(std::unexpected(e)); })
+ );
+ std::cout << (e? "success": "failure") << " e=" << (e? e.value(): e.error()) << "\n";
+ }
+
+
+
+
-
-
- Turn an error into an unexpected value:
-
- auto[r] = *ex::sync_wait(
- ex::just_error(17)
- | ex::upon_error([](int i){
- return std::execpted(std::unexpected, i); }));
-
-
-
+
+ _sender_ | ex::upon_stopped(_fun_)
+ Transform a stop result.
+
+ -
+
+ `ex:::just_stopped() | ex::upon_stopped([](){ return 42; })`
+
+ #include
+ #include
+ namespace ex = beman::execution;
+
+ int main() {
+ auto[r] = *ex::sync_wait(ex::just_stopped() | ex::upon_stopped([](){ return 42; }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+
+
+
+
+
+
+
+ `let` family
+
+
+
#include
#include
#include
namespace ex = beman::execution;
int main() {
- auto[r] = *ex::sync_wait(
- ex::just_error(17)
- | ex::upon_error([](int i){
- return std::expected(std::unexpected{i}); }));
- std::cout << "r=" << r.error() << '\n';
+ auto[b, i, s] = *ex::sync_wait(
+ ex::when_all(ex::just(), ex::just(true), ex::just(17, std::string("data")))
+ );
+ std::cout << "b=" << b << " i=" << i << " s=" << s << "\n";
}
-
-
-
-
-
-
+
From 98baf435c4f81dd49cdf3d4788eea13daea256f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dietmar=20K=C3=BChl?=
Date: Sat, 3 Jan 2026 17:17:13 +0100
Subject: [PATCH 4/9] added the let-family to the tutorial
---
docs/code/CMakeLists.txt | 15 +---
docs/code/tst-sync_wait.hpp | 18 ++--
docs/tutorial.mds | 160 +++++++++++++++++++++++++++++++++++-
3 files changed, 174 insertions(+), 19 deletions(-)
diff --git a/docs/code/CMakeLists.txt b/docs/code/CMakeLists.txt
index 97e3d36a..dc1341c9 100644
--- a/docs/code/CMakeLists.txt
+++ b/docs/code/CMakeLists.txt
@@ -1,21 +1,10 @@
# gersemi: off
-# examples/CMakeLists.txt -*-makefile-*-
+# docs/code/CMakeLists.txt -*-makefile-*-
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# gersemi: on
list(
APPEND EXAMPLES
-sync_wait-just
-sync_wait-just_error
-sync_wait-just_stopped
-just-then
-just-then-throw
-just_error-then
-just_stopped-then
-upon_error
-expected
-upon_stopped
-when_all
)
if(BEMAN_USE_MODULES)
@@ -23,7 +12,7 @@ if(BEMAN_USE_MODULES)
endif()
foreach(EXAMPLE ${EXAMPLES})
- set(EXAMPLE_TARGET ${TARGET_PREFIX}.examples.${EXAMPLE})
+ set(EXAMPLE_TARGET ${TARGET_PREFIX}.tutorial.${EXAMPLE})
add_executable(${EXAMPLE_TARGET})
target_sources(${EXAMPLE_TARGET} PRIVATE ${EXAMPLE}.cpp)
target_link_libraries(
diff --git a/docs/code/tst-sync_wait.hpp b/docs/code/tst-sync_wait.hpp
index 878c9a5f..d11a3e61 100644
--- a/docs/code/tst-sync_wait.hpp
+++ b/docs/code/tst-sync_wait.hpp
@@ -13,15 +13,15 @@
namespace tst {
template
- struct sync_wait_sender {
+ struct add_set_value {
template struct is_set_value: std::false_type {};
template struct is_set_value: std::true_type {};
template struct contains_set_value;
template struct contains_set_value>: std::bool_constant<(... || is_set_value::value)> {
};
- template ::value> struct add_set_value { using type = T; };
- template struct add_set_value, false> {
+ template ::value> struct add_signature { using type = T; };
+ template struct add_signature, false> {
using type = tst::ex::completion_signatures<
tst::ex::set_value_t(), S...
>;
@@ -30,7 +30,7 @@ namespace tst {
template
constexpr auto get_completion_signatures(Env const& e) noexcept {
using orig = decltype(tst::ex::get_completion_signatures(std::declval(), e));
- return typename add_set_value::type{};
+ return typename add_signature::type{};
}
std::remove_cvref_t inner;
template
@@ -40,9 +40,17 @@ namespace tst {
std::forward(rcvr));
}
};
+ template
+ add_set_value(Sender&&) -> add_set_value>;
+
+ inline constexpr struct just_error_t {
+ template
+ auto operator()(E&& e) const { return add_set_value(ex::just_error(std::forward(e))); }
+ } just_error{};
+
template
auto sync_wait(Sender&& sndr) {
- return tst::ex::sync_wait(sync_wait_sender{std::forward(sndr)});
+ return tst::ex::sync_wait(add_set_value{std::forward(sndr)});
}
}
diff --git a/docs/tutorial.mds b/docs/tutorial.mds
index a3499a86..71c7b60b 100644
--- a/docs/tutorial.mds
+++ b/docs/tutorial.mds
@@ -360,7 +360,165 @@ pipe notation.
- `let` family
+ Transform results into a sender using a function object _fun_:
+
+ -
+
+ common: result of a sender returned from _fun_
+ All of these algorithms require that invoking the function
+ object _fun_ with the results from
+ _sender_ returns a sender _s_
+ when the result of _sender_ match algorithm
+ name (`let_value` => success; `let_error` => error,
+ `let_stopped` => stopped). In that case the result becomes
+ the result of executing this sender _s_.
+ If _sender_ completes differently, the result is
+ forwarded. If the invocation of _fun_ exits with
+ an exception the algorithm completes with an error passing an
+ std::exception_ptr to the caught exception.
+
+
+ -
+
+ _sender_ | ex::let_value(_fun_)
+ Transform a successful result into a sender and get
+ this sender executed.
+
+ -
+
+ `ex::just(17) | ex::let_value([f](int i){ return ex::just(f * i); })`
+ The transformation returns another sender. In this example
+ a value is returned synchronously but in general the
+ sender can be any asynchronous operation.
+
+ #include
+ #include
+ namespace ex = beman::execution;
+ int main() {
+ int f{3};
+ auto[r] = *ex::sync_wait(ex::just(17) | ex::let_value([f](int i){ return ex::just(f * i); }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+
+ -
+
+ `ex::just(17) | ex::let_value([f](int i){ return ex::just_error(f * i); })`
+ The transformation returns another sender. In this example
+ a value is returned synchronously but in general the
+ sender can be any asynchronous operation. `let_value` can
+ be used to turn a result into a non-successful one (without
+ throwing an exception; in this case the result is used with
+ `sync_wait` which will still throw an exception).
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ int f{3};
+ try { tst::sync_wait(ex::just(17) | ex::let_value([f](int i){ return ex::just_error(f * i); })); }
+ catch (int e) {
+ std::cout << "e=" << e << "\n";
+ }
+ }
+
+
+
+ -
+
+ `tst::just_error(17) | ex::let_value([f](auto...){ return ex::just(f); })`
+ As with `then`, if the upstream sender's result doesn't match the
+ kind of results to be transformed, the upstream sender's result
+ is forwarded. Note that upstream sender also needs to be able to
+ complete the transformed kind of results (`tst::just_error` is like
+ `ex::just_error` but also pretends to complete successful).
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ int f{3};
+ try { tst::sync_wait(tst::just_error(17) | ex::let_value([f](auto...){ return ex::just(f); })); }
+ catch (int e) {
+ std::cout << "e=" << e << "\n";
+ }
+ }
+
+
+
+ -
+
+ `ex::just(17) | ex::let_value([f](int i){ throw f * i; return ex::just(); })`
+ When an exception is throwing from the function object, the result is an
+ error with a std::exception_ptr to the caught exception. Note
+ the function object still needs to return a sender to satisfy the requirement
+ of `let_value`'s interface.
+
+ #include
+ #include
+ #include "tst.hpp"
+ namespace ex = beman::execution;
+ int main() {
+ int f{3};
+ try { *tst::sync_wait(ex::just(17) | ex::let_value([f](int i){ throw f * i; return ex::just(); })); }
+ catch (int e) {
+ std::cout << "e=" << e << "\n";
+ }
+ }
+
+
+
+
+
+
+ -
+
+ _sender_ | ex::let_error(_fun_)
+ Transform an error result into a sender and get this sender executed.
+
+ -
+
+ `ex::just_error(17) | ex::let_error([](int e) { return ex::just(e); })`
+
+ #include
+ #include
+ namespace ex = beman::execution;
+ int main() {
+ auto[r] = *ex::sync_wait(ex::just_error(17) | ex::let_error([](int e){ return ex::just(e); }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+
+
+
+
+ -
+
+ _sender_ | ex::let_stopped(_fun_)
+ Transform a stopped result into a sender and get this sender executed.
+
+ -
+
+ `ex::just_stopped() | ex::let_stopped([](int e) { return ex::just(7); })`
+
+ #include
+ #include
+ namespace ex = beman::execution;
+ int main() {
+ auto[r] = *ex::sync_wait(ex::just_error(17) | ex::let_error([](int e){ return ex::just(e); }));
+ std::cout << "r=" << r << "\n";
+ }
+
+
+
+
+
+
+
From d6e63d0df7d6ded1af4faa78382b9a206585bf2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dietmar=20K=C3=BChl?=
Date: Sat, 3 Jan 2026 21:16:55 +0100
Subject: [PATCH 5/9] added text for when_all to the tutorial
---
Makefile | 4 +-
bin/mk-doc.py | 6 +-
docs/code/CMakeLists.txt | 19 +++
docs/code/tst-basic-timer.cpp | 43 ++---
docs/code/tst-config.hpp | 2 +-
docs/code/tst-repeat_effect_until.hpp | 117 +++++++------
docs/code/tst-sync_wait.hpp | 86 +++++-----
docs/code/tst-timer.hpp | 110 ++++++-------
docs/overview.md | 17 +-
docs/questions.md | 4 +-
docs/tutorial.mds | 104 ++++++++++--
include/beman/execution/detail/just.hpp | 6 +-
include/beman/execution/detail/sync_wait.hpp | 47 ++++++
include/beman/execution/detail/then.hpp | 165 +++++++++++++++++++
14 files changed, 508 insertions(+), 222 deletions(-)
diff --git a/Makefile b/Makefile
index 88e7925c..83545dd7 100644
--- a/Makefile
+++ b/Makefile
@@ -172,8 +172,8 @@ cmake-format:
pre-commit run gersemi
clang-format:
- pre-commit run $@
- # XXX TBD: git clang-format main
+ # pre-commit run $@
+ git clang-format main
todo:
bin/mk-todo.py
diff --git a/bin/mk-doc.py b/bin/mk-doc.py
index ec5f0f63..2be4af43 100755
--- a/bin/mk-doc.py
+++ b/bin/mk-doc.py
@@ -37,7 +37,8 @@ def transform(text, output):
print(f"example: {match.group(1)}")
name = match.group(1)
code = ""
- output.write(f"")
+ #output.write(f"")
+ output.write(f"```c++\n")
capturing = True
elif capturing and preend_re.match(line):
if name in names:
@@ -47,7 +48,8 @@ def transform(text, output):
with open("docs/code/" + name + ".cpp", "w") as codeout:
codeout.write(code)
capturing = False
- output.write(line)
+ #output.write(line)
+ output.write(f"```\n")
else:
if capturing:
code += line
diff --git a/docs/code/CMakeLists.txt b/docs/code/CMakeLists.txt
index dc1341c9..ff3a7095 100644
--- a/docs/code/CMakeLists.txt
+++ b/docs/code/CMakeLists.txt
@@ -5,6 +5,25 @@
list(
APPEND EXAMPLES
+sync_wait-just
+sync_wait-just_error
+sync_wait-just_stopped
+just-then
+just-then-throw
+just_error-then
+just_stopped-then
+upon_error
+expected
+upon_stopped
+just-let_value
+just-let_value-just_error
+just_error-let_value
+just-let_value-throw
+just_error-let_error
+just_stopped-let_stopped
+when_all
+when_all-error
+when_all-stopped
)
if(BEMAN_USE_MODULES)
diff --git a/docs/code/tst-basic-timer.cpp b/docs/code/tst-basic-timer.cpp
index f0ed3af3..3779e482 100644
--- a/docs/code/tst-basic-timer.cpp
+++ b/docs/code/tst-basic-timer.cpp
@@ -12,42 +12,27 @@ namespace ex = beman::execution;
struct receiver {
using receiver_concept = ex::receiver_t;
- auto set_value() && noexcept {
- std::cout << "timer done\n";
- }
- auto set_error(const std::exception_ptr&) && noexcept {
- std::cout << "timer error\n";
- }
- auto set_stopped() && noexcept {
- std::cout << "timer stopped\n";
- }
+ auto set_value() && noexcept { std::cout << "timer done\n"; }
+ auto set_error(const std::exception_ptr&) && noexcept { std::cout << "timer error\n"; }
+ auto set_stopped() && noexcept { std::cout << "timer stopped\n"; }
};
-std::ostream& fmt_now(std::ostream& os) {
- return os << std::chrono::system_clock::now();
-}
+std::ostream& fmt_now(std::ostream& os) { return os << std::chrono::system_clock::now(); }
auto main() -> int {
std::size_t count{3};
- ex::sync_wait(
- tst::repeat_effect_until(
- ex::just() | ex::then([]() noexcept { std::cout << "effect\n"; }),
- [&count]() noexcept { return --count == 0u; })
- );
+ ex::sync_wait(tst::repeat_effect_until(ex::just() | ex::then([]() noexcept { std::cout << "effect\n"; }),
+ [&count]() noexcept { return --count == 0u; }));
tst::timer timer{};
std::cout << fmt_now << ": start\n";
ex::sync_wait(
- ex::when_all(
- tst::resume_after(timer.get_token(), std::chrono::milliseconds(1000))
- | ex::then([]() noexcept { std::cout << fmt_now << ": 1000ms timer fired\n"; }),
- tst::resume_after(timer.get_token(), std::chrono::milliseconds(500))
- | ex::then([]() noexcept { std::cout << fmt_now << ": 500ms timer fired\n"; }),
- tst::resume_after(timer.get_token(), std::chrono::milliseconds(1500))
- | ex::then([]() noexcept { std::cout << fmt_now << ": 1500ms timer fired\n"; }),
- timer.when_done() | ex::then([]() noexcept { std::cout << fmt_now << ": timer done\n"; }),
- ex::just()
- )
- );
-
+ ex::when_all(tst::resume_after(timer.get_token(), std::chrono::milliseconds(1000)) |
+ ex::then([]() noexcept { std::cout << fmt_now << ": 1000ms timer fired\n"; }),
+ tst::resume_after(timer.get_token(), std::chrono::milliseconds(500)) |
+ ex::then([]() noexcept { std::cout << fmt_now << ": 500ms timer fired\n"; }),
+ tst::resume_after(timer.get_token(), std::chrono::milliseconds(1500)) |
+ ex::then([]() noexcept { std::cout << fmt_now << ": 1500ms timer fired\n"; }),
+ timer.when_done() | ex::then([]() noexcept { std::cout << fmt_now << ": timer done\n"; }),
+ ex::just()));
}
diff --git a/docs/code/tst-config.hpp b/docs/code/tst-config.hpp
index 3df73477..c25fde48 100644
--- a/docs/code/tst-config.hpp
+++ b/docs/code/tst-config.hpp
@@ -8,7 +8,7 @@
#include
namespace tst {
- namespace ex = beman::execution;
+namespace ex = beman::execution;
}
// ----------------------------------------------------------------------------
diff --git a/docs/code/tst-repeat_effect_until.hpp b/docs/code/tst-repeat_effect_until.hpp
index 0a285c9b..bfb6e89f 100644
--- a/docs/code/tst-repeat_effect_until.hpp
+++ b/docs/code/tst-repeat_effect_until.hpp
@@ -13,77 +13,74 @@
// ----------------------------------------------------------------------------
namespace tst {
- template
- struct connector {
- decltype(ex::connect(::std::declval(), ::std::declval())) op;
- connector(auto sndr, auto rcvr)
- : op(ex::connect(::std::move(sndr), ::std::move(rcvr))) {}
-
- auto start() & noexcept -> void { ex::start(this->op); }
- };
+template
+struct connector {
+ decltype(ex::connect(::std::declval(), ::std::declval())) op;
+ connector(auto sndr, auto rcvr) : op(ex::connect(::std::move(sndr), ::std::move(rcvr))) {}
- inline constexpr struct repeat_effect_unilt_t {
- template
- struct sender {
- using sender_concept = ex::sender_t;
- using completion_signatures
- = ex::completion_signatures;
-
- template
- struct state {
- using operation_state_concept = ex::operation_state_t;
- struct own_receiver {
- using receiver_concept = ex::receiver_t;
- state* s;
- auto set_value() && noexcept -> void {
- static_assert(ex::receiver);
- this->s->next();
- }
- auto set_error(std::exception_ptr error) && noexcept -> void {
- ex::set_error(::std::move(this->s->receiver), std::move(error));
- }
- auto set_stopped() && noexcept -> void {
- ex::set_stopped(::std::move(this->s->receiver));
- }
- };
+ auto start() & noexcept -> void { ex::start(this->op); }
+};
- std::remove_cvref_t child;
- std::remove_cvref_t fun;
- std::remove_cvref_t receiver;
- std::optional, own_receiver>> child_op;
+inline constexpr struct repeat_effect_unilt_t {
+ template
+ struct sender {
+ using sender_concept = ex::sender_t;
+ using completion_signatures =
+ ex::completion_signatures;
- auto start() & noexcept -> void {
- static_assert(ex::operation_state);
- this->run_one();
- }
- auto run_one() & noexcept -> void {
- this->child_op.emplace(this->child, own_receiver{this});
- this->child_op->start();
+ template
+ struct state {
+ using operation_state_concept = ex::operation_state_t;
+ struct own_receiver {
+ using receiver_concept = ex::receiver_t;
+ state* s;
+ auto set_value() && noexcept -> void {
+ static_assert(ex::receiver);
+ this->s->next();
}
- auto next() & noexcept -> void {
- if (this->fun()) {
- ex::set_value(::std::move(this->receiver));
- } else {
- this->run_one();
- }
+ auto set_error(std::exception_ptr error) && noexcept -> void {
+ ex::set_error(::std::move(this->s->receiver), std::move(error));
}
+ auto set_stopped() && noexcept -> void { ex::set_stopped(::std::move(this->s->receiver)); }
};
- std::remove_cvref_t child;
- std::remove_cvref_t fun;
+ std::remove_cvref_t child;
+ std::remove_cvref_t fun;
+ std::remove_cvref_t receiver;
+ std::optional, own_receiver>> child_op;
- template
- auto connect(Receiver&& receiver) const& noexcept -> state {
- static_assert(ex::sender);
- return state(this->child, this->fun, ::std::forward(receiver));
+ auto start() & noexcept -> void {
+ static_assert(ex::operation_state);
+ this->run_one();
+ }
+ auto run_one() & noexcept -> void {
+ this->child_op.emplace(this->child, own_receiver{this});
+ this->child_op->start();
+ }
+ auto next() & noexcept -> void {
+ if (this->fun()) {
+ ex::set_value(::std::move(this->receiver));
+ } else {
+ this->run_one();
+ }
}
};
- template
- auto operator()(Child&& child, Pred&& pred) const -> sender {
- return {::std::forward(child), ::std::forward(pred)};
+
+ std::remove_cvref_t child;
+ std::remove_cvref_t fun;
+
+ template
+ auto connect(Receiver&& receiver) const& noexcept -> state {
+ static_assert(ex::sender);
+ return state(this->child, this->fun, ::std::forward(receiver));
}
- } repeat_effect_until{};
-}
+ };
+ template
+ auto operator()(Child&& child, Pred&& pred) const -> sender {
+ return {::std::forward(child), ::std::forward(pred)};
+ }
+} repeat_effect_until{};
+} // namespace tst
// ----------------------------------------------------------------------------
diff --git a/docs/code/tst-sync_wait.hpp b/docs/code/tst-sync_wait.hpp
index d11a3e61..c7ea2225 100644
--- a/docs/code/tst-sync_wait.hpp
+++ b/docs/code/tst-sync_wait.hpp
@@ -12,47 +12,59 @@
// ----------------------------------------------------------------------------
namespace tst {
- template
- struct add_set_value {
- template struct is_set_value: std::false_type {};
- template struct is_set_value: std::true_type {};
- template struct contains_set_value;
- template struct contains_set_value>: std::bool_constant<(... || is_set_value::value)> {
- };
-
- template ::value> struct add_signature { using type = T; };
- template struct add_signature, false> {
- using type = tst::ex::completion_signatures<
- tst::ex::set_value_t(), S...
- >;
- };
- using sender_concept = tst::ex::sender_t;
- template
- constexpr auto get_completion_signatures(Env const& e) noexcept {
- using orig = decltype(tst::ex::get_completion_signatures(std::declval(), e));
- return typename add_signature::type{};
- }
- std::remove_cvref_t inner;
- template
- auto connect(Rcvr&& rcvr) && {
- return tst::ex::connect(
- std::move(this->inner),
- std::forward(rcvr));
- }
- };
- template
- add_set_value(Sender&&) -> add_set_value>;
+template
+struct add_set_value {
+ template
+ struct is_set_value : std::false_type {};
+ template
+ struct is_set_value : std::true_type {};
+ template
+ struct contains_set_value;
+ template
+ struct contains_set_value>
+ : std::bool_constant<(... || is_set_value::value)> {};
- inline constexpr struct just_error_t {
- template
- auto operator()(E&& e) const { return add_set_value(ex::just_error(std::forward(e))); }
- } just_error{};
+ template ::value>
+ struct add_signature {
+ using type = T;
+ };
+ template
+ struct add_signature, false> {
+ using type = tst::ex::completion_signatures;
+ };
+ using sender_concept = tst::ex::sender_t;
+ template
+ constexpr auto get_completion_signatures(const Env& e) noexcept {
+ using orig = decltype(tst::ex::get_completion_signatures(std::declval(), e));
+ return typename add_signature::type{};
+ }
+ std::remove_cvref_t inner;
+ template
+ auto connect(Rcvr&& rcvr) && {
+ return tst::ex::connect(std::move(this->inner), std::forward(rcvr));
+ }
+};
+template
+add_set_value(Sender&&) -> add_set_value>;
- template
- auto sync_wait(Sender&& sndr) {
- return tst::ex::sync_wait(add_set_value{std::forward(sndr)});
+inline constexpr struct just_error_t {
+ template
+ auto operator()(E&& e) const {
+ return add_set_value(ex::just_error(std::forward(e)));
+ }
+} just_error{};
+inline constexpr struct when_all_t {
+ template
+ auto operator()(Senders&&... sndrs) const {
+ return ex::when_all(tst::add_set_value(std::forward(sndrs))...);
}
+} when_all{};
+
+template
+auto sync_wait(Sender&& sndr) {
+ return tst::ex::sync_wait(add_set_value{std::forward(sndr)});
}
+} // namespace tst
// ----------------------------------------------------------------------------
diff --git a/docs/code/tst-timer.hpp b/docs/code/tst-timer.hpp
index a311ee13..7762612b 100644
--- a/docs/code/tst-timer.hpp
+++ b/docs/code/tst-timer.hpp
@@ -14,62 +14,55 @@
// ----------------------------------------------------------------------------
namespace tst {
- class timer;
+class timer;
- inline constexpr struct resume_after_t {
- auto operator()(auto token, std::chrono::milliseconds duration) const noexcept {
- return token.resume_after(duration);
- }
- } resume_after{};
-}
+inline constexpr struct resume_after_t {
+ auto operator()(auto token, std::chrono::milliseconds duration) const noexcept {
+ return token.resume_after(duration);
+ }
+} resume_after{};
+} // namespace tst
// ----------------------------------------------------------------------------
class tst::timer {
-public:
+ public:
class when_done_sender {
- public:
- using sender_concept = tst::ex::sender_t;
- using completion_signatures =
- tst::ex::completion_signatures;
+ public:
+ using sender_concept = tst::ex::sender_t;
+ using completion_signatures = tst::ex::completion_signatures;
template
struct state {
using operation_state_concept = tst::ex::operation_state_t;
- using scheduler_t = decltype(ex::get_scheduler(ex::get_env(std::declval())));
+ using scheduler_t = decltype(ex::get_scheduler(ex::get_env(std::declval())));
struct execute {
state* s{};
- auto operator()() noexcept -> void {
- this->s->await_one();
- }
+ auto operator()() noexcept -> void { this->s->await_one(); }
};
struct pred {
state* s{};
- auto operator()() noexcept -> bool { return this->s->object->queue.empty(); }
+ auto operator()() noexcept -> bool { return this->s->object->queue.empty(); }
};
- using inner_sender = decltype(tst::repeat_effect_until(ex::schedule(std::declval()) | ex::then(execute()), pred()));
- tst::timer* object;
+ using inner_sender = decltype(tst::repeat_effect_until(
+ ex::schedule(std::declval()) | ex::then(execute()), pred()));
+ tst::timer* object;
tst::connector inner_op;
explicit state(tst::timer* obj, Receiver&& rcvr) noexcept
- : object(obj)
- , inner_op(tst::repeat_effect_until(
- ex::schedule(ex::get_scheduler(ex::get_env(rcvr))) | ex::then(execute{this}),
- pred{this}),
- std::forward(rcvr))
- {
+ : object(obj),
+ inner_op(tst::repeat_effect_until(ex::schedule(ex::get_scheduler(ex::get_env(rcvr))) |
+ ex::then(execute{this}),
+ pred{this}),
+ std::forward(rcvr)) {
static_assert(tst::ex::operation_state);
}
- auto start() & noexcept -> void {
- this->inner_op.start();
- }
- auto await_one() & noexcept -> void {
- this->object->await_one();
- }
+ auto start() & noexcept -> void { this->inner_op.start(); }
+ auto await_one() & noexcept -> void { this->object->await_one(); }
};
- explicit when_done_sender(tst::timer* obj) noexcept: object(obj) {
+ explicit when_done_sender(tst::timer* obj) noexcept : object(obj) {
static_assert(tst::ex::sender);
}
@@ -77,39 +70,30 @@ class tst::timer {
auto connect(Receiver&& receiver) const& noexcept -> state {
return state(this->object, ::std::forward(receiver));
}
- private:
+
+ private:
tst::timer* object;
};
struct base {
virtual auto complete() noexcept -> void = 0;
};
- struct resume_after_sender
- {
- using sender_concept = tst::ex::sender_t;
- using completion_signatures =
- tst::ex::completion_signatures;
+ struct resume_after_sender {
+ using sender_concept = tst::ex::sender_t;
+ using completion_signatures = tst::ex::completion_signatures