diff --git a/.gitignore b/.gitignore index a376cafa..166fdc85 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ CMakeFiles/ *.log docs/html docs/latex +docs/code/*.cpp +docs/tutorial.md 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/Makefile b/Makefile index b956d026..83545dd7 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 @@ -105,6 +105,7 @@ run: test ./$(BUILD)/examples/$(EXAMPLE) doc: + ./bin/mk-doc.py docs/*.mds doxygen docs/Doxyfile # $(SANITIZERS): @@ -171,14 +172,14 @@ 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 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 new file mode 100755 index 00000000..2b15fd23 --- /dev/null +++ b/bin/mk-doc.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 + +import re +import sys + +prestart_re = re.compile('\s*
')
+preend_re = re.compile("\s*
") +append_re = re.compile("list\(APPEND EXAMPLES") +appendp_re = re.compile("list\(APPEND EXAMPLES\)") +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: + if append_re.match(line): + text += "list(APPEND EXAMPLES\n" + text += "\n".join(list) + "\n" + skipping = not appendp_re.match(line) + if not skipping: + text += ")\n" + else: + text += line + + 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"```c++\n") + 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(f"```\n") + 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 + ".md", "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..2d2752d3 --- /dev/null +++ b/docs/code/CMakeLists.txt @@ -0,0 +1,20 @@ +# gersemi: off +# docs/code/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}.tutorial.${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/docs/code/tst-basic-timer.cpp b/docs/code/tst-basic-timer.cpp new file mode 100644 index 00000000..3779e482 --- /dev/null +++ b/docs/code/tst-basic-timer.cpp @@ -0,0 +1,38 @@ +// 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/docs/code/tst-config.hpp b/docs/code/tst-config.hpp new file mode 100644 index 00000000..c25fde48 --- /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 new file mode 100644 index 00000000..bfb6e89f --- /dev/null +++ b/docs/code/tst-repeat_effect_until.hpp @@ -0,0 +1,87 @@ +// 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 "tst-config.hpp" + +// ---------------------------------------------------------------------------- + +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); } +}; + +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{}; +} // namespace tst + +// ---------------------------------------------------------------------------- + +#endif diff --git a/docs/code/tst-sync_wait.hpp b/docs/code/tst-sync_wait.hpp new file mode 100644 index 00000000..c7ea2225 --- /dev/null +++ b/docs/code/tst-sync_wait.hpp @@ -0,0 +1,71 @@ +// 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 "tst-config.hpp" + +// ---------------------------------------------------------------------------- + +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; + }; + 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>; + +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 + +// ---------------------------------------------------------------------------- + +#endif diff --git a/docs/code/tst-timer.hpp b/docs/code/tst-timer.hpp new file mode 100644 index 00000000..c1ccb5e7 --- /dev/null +++ b/docs/code/tst-timer.hpp @@ -0,0 +1,140 @@ +// 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{}; +} // namespace tst + +// ---------------------------------------------------------------------------- + +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 drtn, Receiver&& rcvr) noexcept + : object(obj), duration(drtn), 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 new file mode 100644 index 00000000..d7541087 --- /dev/null +++ b/docs/code/tst.hpp @@ -0,0 +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 "tst-timer.hpp" + +#endif diff --git a/docs/overview.md b/docs/overview.md index 0f53a851..634ed1ec 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -441,7 +441,7 @@ Note that the `get_env` member is both `const` and `noexcept`.
The expression get_allocator(env) returns an allocator for any memory allocations in the respective context. If env doesn’t support this query any attempt to access it will result in a compilation error. The value of the expression get_allocator(env) is the result of as_const(env).query(get_allocator) if
    -
  • the expression is valid;
  • +
  • the expression is valid;
  • the expression is noexcept;
  • the result of the expression satisfies simple-allocator.
@@ -464,7 +464,7 @@ struct alloc_env {
-get_completion_scheduler<>(env) -> scheduler +get_completion_scheduler<Tag>(env) -> scheduler **Default**: none
The expression get_complet_scheduler<Tag>(env) yields the completion scheduler for the completion signal Tag associated with env. This query can be used to determine the scheduler a sender sender completes on for a given completion signal Tag by using get_completion_scheduler<Tag>(get_env(sender)). The value of the expression is equivalent to as_const(env).query(get_completion_scheduler<Tag>) if @@ -485,7 +485,7 @@ To determine the result the sender is first transformed usin
  • the type of new-sender.get_completion_signatures(env) if this expression is valid;
  • the type remove_cvref_t<New-Sender-Type>::completion_signatures if this type exists;
  • completion_signatures<set_value_t(T), set_error_t(exception_ptr), set_stopped_t()> if New-Sender-Type is an awaitable type which would yield an object of type T when it is co_awaited;
  • -
  • invalid otherwise. +
  • invalid otherwise.
  • @@ -516,8 +516,7 @@ The expression get_delegation_scheduler(env) yields the sche Otherwise the expression is invalid.
    -get_domain(env) -> domain - +get_domain(env) -> domain The expression get_domain(env) yields the domain associated with env. The value of the expression is equivalent to as_const(env).query(get_domain) if
    1. this expression is valid;
    2. @@ -595,7 +594,7 @@ The expression just(value...) creates a sender which sends <
    -just_error(error) -> sender-of<set_error_t(Error)> +just_error(error) -> sender-of<set_error_t(Error)> The expression just_error(error) creates a sender which sends error on the `set_error` (failure) channel when started. Completions @@ -604,7 +603,7 @@ The expression just_error(error) creates a sender which send
    -just_stopped() -> sender-of<set_stopped_t()> +just_stopped() -> sender-of<set_stopped_t()> The expression just_stopped() creates a sender which sends a completion on the `set_stopped` (cancellation) channel when started. Completions @@ -613,7 +612,7 @@ The expression just_stopped() creates a sender which sends a comple
    -read_env(query) -> sender-of<set_value_t(query-result)> +read_env(query) -> sender-of<set_value_t(query-result)> The expression read_env(query) creates a sender which sends the result of querying query the environment of the receiver it gets connected to on the `set_value` channel when started. Put differently, it calls set_value(move(receiver), query(get_env(receiver))). For example, in a coroutine it may be useful to extra the stop token associated with the coroutine which can be done using read_env: ```c++\ @@ -626,7 +625,7 @@ auto token = co_await read_env(get_stop_token);
    -schedule(scheduler) -> sender-of<set_value_t()> +schedule(scheduler) -> sender-of<set_value_t()> The expression schedule(scheduler) creates a sender which upon success completes on the set_value channel without any arguments running on the execution context associated with scheduler. Depending on the scheduler it is possible that the sender can complete with an error if the scheduling fails or using `set_stopped()` if the operation gets cancelled before it is successful. Completions @@ -667,7 +666,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/questions.md b/docs/questions.md index 7e120b04..4c086f35 100644 --- a/docs/questions.md +++ b/docs/questions.md @@ -20,7 +20,7 @@ likely observable. - [exec.snd.concepts] "The type tag_of_t is +defined+ as follows:" - [exec.sched] uses `auto(get_completion_scheduler(...))` which is OK for clang but doesn't seem to compile for g++ os MSVC. -- [exec.just] p2.1: movable-value doesn't seems right: movable-value> +- [exec.just] p2.1: `movable-value` doesn't seems right: `movable-value>` - [exec.just] Otherwise after p2.3 is missing - [exec.run.loop.types] p9.1: "refers remains" -> "refers to remains" - [exec.run.loop.types] p9.2: "get_stop_token(REC(o))": REC is a receiver, any @@ -36,7 +36,7 @@ likely observable. in turn calls loop.finish(). When the loop then is run() it immediately std::terminate()s because the run_loop's state is finished not starting. I can see two potential fixes: - 1. connect(on(sender), sync-wait-receiver{&state}) + 1. connect(on(sender), `sync-wait-receiver{&state})` 2. have run_loop::run() only terminate when the state is running; if the state is already finishing, leave it at that - [exec.sync_wait] are sync_wait(just_error(17)) and sync_wait(just_stopped()) diff --git a/docs/tutorial.mds b/docs/tutorial.mds new file mode 100644 index 00000000..55f36dbe --- /dev/null +++ b/docs/tutorial.mds @@ -0,0 +1,755 @@ +# 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., +`#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 ` +

    + 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: +

    +
      +
    • + This documentation is part of [`beman::execution`](https://github.com/bemanproject/execution) + the example were tested with this implementation. The examples would start + with +
      +         #include 
      +         namespace ex = beman::execution;
      +       
      +
    • +
    • 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;
      +         namespace 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;
      +        
      +
    • +
    +
    +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: +
    + `#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). +

    +### Fundamental Building Blocks: `just` and `sync_wait` +

    +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. +

    +

    +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 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));` +
      +  #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 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) { ... }` + 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"; }
      +  }
      +      
      +
      +
    • +
    • + 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())) { ... }` + 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";
      +    }
      +  }
      +      
      +
      +
    • +
    +### 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. +

    + +
      +
    • + Transform results using a function object _fun_: +
        +
      • +
        + 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 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";
          +  }
          +                   
          +
          +
        • +
        +
        +
      • +
      • +
        + _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[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 
          +  namespace ex = beman::execution;
          +
          +  int main() {
          +    auto[e] = *ex::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";
          +  }
          +                   
          +
        • +
        +
        +
      • +
      • +
        + _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";
          +  }
          +                   
          +
          +
        • +
        +
        +
      • +
      +
    • +
    • + 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";
          +  }
          +                  
          +
          +
        • +
        +
        +
      • +
      +
    • +
    • + Await completion of multiple senders. +
        +
      • +
        + ex::when_all(_s_...) + The algorithm `when_all` is used to await completion + of multiple senders. Upon successful completion of all + senders _s_... a `std::tuple` with all + results is produced. If an error or a cancellation is + encountered all outstanding senders are stopped and + once all senders are completed the unsuccessful result + is reported. +
          +
        • +
          + `ex::when_all(ex::just(true), ex::just(), ex::just(17, 2.5))` + When all senders complete successfully the result is a `std::tuple` of + all values produced. In the example above there are three senders producing + a result: +
            +
          1. The first sender `ex::just(true)` produces one `bool` value.
          2. +
          3. The second sender `ex::just()` doesn't produce any value.
          4. +
          5. The third sender `ex::just(17, 2.5)` produces one `int` and one `float` value. +
          + The result of `ex::when_all(ex::just(true), ex::just(), ex::just(17, 2.5))` is + a `std::tuple(true, 17, 2.5)`. +
          +  #include 
          +  #include 
          +  namespace ex = beman::execution;
          +
          +  int main() {
          +    auto[b, i, f] = *ex::sync_wait(ex::when_all(ex::just(true), ex::just(), ex::just(17, 2.5)));
          +    std::cout << "b=" << b << " i=" << i << " f=" << f << "\n";
          +  }
          +  
          +
          +
        • +
        • +
          + `tst::when_all(ex::just(true), ex::just_error(1), ex::just(17, 2.5))` + If one sender completes with an error all outstanding senders + are stopped. Once all senders complete the result of `ex::when_all` + becomes the error. The example uses `tst::when_all` because `ex::when_all` + expects all senders to have exactly one successful way to complete and + `tst::when_all` adapts each sender to pretend that they can complete + successfully if it doesn't already. +
          +  #include 
          +  #include 
          +  #include "tst.hpp"
          +  namespace ex = beman::execution;
          +
          +  int main() {
          +    try { ex::sync_wait(tst::when_all(ex::just(true), ex::just_error(1), ex::just(17, 2.5))); }
          +    catch (int e) {
          +       std::cout << "e=" << e << "\n";
          +    }
          +  }
          +  
          +
          +
        • +
        • +
          + `tst::when_all(ex::just(true), ex::just_stopped(), ex::just(17, 2.5))` + If one sender completes with stopped all outstanding senders + are stopped. Once all senders complete the result of `ex::when_all` + becomes stopped. The example uses `tst::when_all` because `ex::when_all` + expects all senders to have exactly one successful way to complete and + `tst::when_all` adapts each sender to pretend that they can complete + successfully if it doesn't already. +
          +  #include 
          +  #include 
          +  #include "tst.hpp"
          +  namespace ex = beman::execution;
          +
          +  int main() {
          +    if (not ex::sync_wait(tst::when_all(ex::just(true), ex::just_stopped(), ex::just(17, 2.5)))) {
          +      std::cout << "no result set => operation was stopped\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: +

    +
      +
    1. + A way to compose senders from more basic senders using sender algorithms. +
    2. +
    3. + `start` a sender and synchronously await its completion: `ex::sync_wait`. +
    4. +
    5. + Create new work, get it `start`ed, and its completion awaited. +
    6. +
    7. + `ex::task` makes composition of senders easier in many cases. +
    8. +
    +

    + The above doesn't explain how sender are implemented or what tools are available. There are more advanced needs +

    diff --git a/include/beman/execution/detail/just.hpp b/include/beman/execution/detail/just.hpp index 6486e298..e5e2b264 100644 --- a/include/beman/execution/detail/just.hpp +++ b/include/beman/execution/detail/just.hpp @@ -61,7 +61,7 @@ using just_error_t = ::beman::execution::detail::just_t<::beman::execution::se using just_stopped_t = ::beman::execution::detail::just_t<::beman::execution::set_stopped_t>; /*! - * \brief just(_arg_...)` yields a sender completing with set_value_t(_Arg_...) + * \brief just(_arg_...) yields a sender completing with set_value_t(_Arg_...) * \headerfile beman/execution/execution.hpp * * \details @@ -92,7 +92,7 @@ using just_stopped_t = ::beman::execution::detail::just_t<::beman::execution::se * their starting. The example below create a sender yielding three * values and awaits the completion using sync_wait(_sender_): * for a value completion of _sender_ it will yield an - * std::optional<std::tuple<_Args_...>>` with the + * std::optional<std::tuple<_Args_...>> with the * `tuple` containing the value copied/moved from the original arguments * (an `optional` is returned to indicate cancellation). * @@ -170,7 +170,7 @@ inline constexpr ::beman::execution::just_t just{}; inline constexpr ::beman::execution::just_error_t just_error{}; /*! - * \brief just_stopped(_) yields a sender completing with set_stopped_t() + * \brief just_stopped() yields a sender completing with set_stopped_t() * \headerfile beman/execution/execution.hpp * * \details diff --git a/include/beman/execution/detail/sync_wait.hpp b/include/beman/execution/detail/sync_wait.hpp index 36964ef1..6d38b213 100644 --- a/include/beman/execution/detail/sync_wait.hpp +++ b/include/beman/execution/detail/sync_wait.hpp @@ -108,6 +108,53 @@ struct sync_wait_t { namespace beman::execution { using sync_wait_t = ::beman::execution::detail::sync_wait_t; +/*! + * \brief sync_wait(_sender_) starts _sender_ and waits for its completion. + * \headerfile beman/execution/execution.hpp + * + * \details + * `sync_wait` is a callable object of type `sync_wait_t`. Invoking + * sync_wait(_sender_) starts _sender_ and + * waits for its completion. This involves a few steps: + * 1. A run_loop is created to provide a scheduler. + * 2. The _sender_ is `connect`ed to a receiver capturing + * the results and providing an environment with access to the + * `run_loop`'s scheduler. + * 3. The operation state returned from `connect` is `start`ed. + * 4. The `run_loop` is run to process any work scheduled. + * + * Once the _sender_ completes, the result is provided by `sync_wait`: + * - If the _sender_ completes with set_value(_arg_...), `sync_wait` returns + * an std::optional> containing the results + * _arg_.... + * - If the _sender_ completes with `set_stopped()`, `sync_wait` returns a + * disengaged std::optional>. + * - If the _sender_ completes with + * set_error(_error_), `sync_wait` throw _error_ or rethrows the exception if + * _error_ is an std::exception_ptr. + * + *

    Usage

    + *
    + * sync_wait(sender...)
    + * 
    + * + *

    Example

    + * + * The use of sync_wait(_sender_) is in `main` + * to synchronously wait for the completion of the asynchronous work + * of the program represented by _sender_. + * + *
    + * #include 
    + * #include 
    + *
    + * int main() {
    + *     auto result = ex::sync_wait(ex::just(17));
    + *     assert(result);
    + *     assert(*result == std::tuple(17));
    + * }
    + * 
    + */ inline constexpr ::beman::execution::sync_wait_t sync_wait{}; } // namespace beman::execution diff --git a/include/beman/execution/detail/then.hpp b/include/beman/execution/detail/then.hpp index 67891260..1a342201 100644 --- a/include/beman/execution/detail/then.hpp +++ b/include/beman/execution/detail/then.hpp @@ -139,12 +139,177 @@ struct completion_signatures_for_impl< #include namespace beman::execution { -using then_t = ::beman::execution::detail::then_t<::beman::execution::set_value_t>; -using upon_error_t = ::beman::execution::detail::then_t<::beman::execution::set_error_t>; +/*! + * \brief then_t is the type of then. + * \headerfile beman/execution/execution.hpp + */ +using then_t = ::beman::execution::detail::then_t<::beman::execution::set_value_t>; +/*! + * \brief upon_error_t is the type of upon_error. + * \headerfile beman/execution/execution.hpp + */ +using upon_error_t = ::beman::execution::detail::then_t<::beman::execution::set_error_t>; +/*! + * \brief upon_stopped_t is the type of upon_stopped. + * \headerfile beman/execution/execution.hpp + */ using upon_stopped_t = ::beman::execution::detail::then_t<::beman::execution::set_stopped_t>; -inline constexpr ::beman::execution::then_t then{}; -inline constexpr ::beman::execution::upon_error_t upon_error{}; +/*! + * \brief then(_sender_, _fun_) yields a sender transforming a set_value_t(_A_...) completion + * \headerfile beman/execution/execution.hpp + * + * \details + * `then` is a callable object of type `then_t`. Invoking then(_sender_, _fun_) or + * _sender_ | then(_fun_) yields a sender + * which, when `start`ed starts _sender_ and awaits its completion. When + * _sender_ completes `then` proceeds according to this completion: + * - If the completion is set_value(_a_...), _fun_(_a_...) is invoked: + * - if that invocation throws, `then` completes with set_error(_r_, std::current_exception()); + * otherwise + * - if the invocation returns `void`, `then` completes with set_value(_r_); otherwise + * - if the invocation returns _v_, `then` completes with set_value(_r_, _v_). + * - Otherwise, if the completion is set_error(_e_), `then` completes with set_error(_r_, + * _e_) + * - Otherwise, if the completion is set_stopped(), `then` completes with set_stopped(_r_). + * + *

    Usage

    + *
    + * then(sender, fun)
    + * sender | then(fun)
    + * 
    + * + *

    Completions Signatures

    + * The completion signatures depends on the completion signatures _CS_ of _sender_ (the + * completion signatures will be deduplicated): + * - For each set_value_t(_A_...) in _CS_, + * there is a completion signature + * set_value_t(decltype(_fun_(std::declval<_A_>()...))). + * - If for any of the set_value_t(_A_...) in + * _CS_ the expression noexcept(_fun_(std::declval<_A_>()...)) + * is `false` there is a completion signature set_error_t(std::exception_ptr). + * - Each set_error_t(_Error_) in _CS_ is copied. + * - If set_stopped_t() is in _CS_ it is copied. + * + *

    Example

    + * + *
    + * #include 
    + * #include 
    + * namespace ex = beman::execution;
    + *
    + * int main() {
    + *     auto result = ex::sync_wait(ex::just(10) | ex::then([](int v) { return v == 3; }));
    + *     assert(result);
    + *     assert(*result == std::tuple(false));
    + * }
    + * 
    + */ +inline constexpr ::beman::execution::then_t then{}; + +/*! + * \brief upon_error(_sender_, _fun_) yields a sender transforming a set_error_t(_E_) + * completion + * \headerfile beman/execution/execution.hpp + * + * \details + * `upon_error` is a callable object of type `upon_error_t`. Invoking upon_error(_sender_, _fun_) or + * _sender_ | upon_error(_fun_) yields a sender + * which, when `start`ed starts _sender_ and awaits its completion. When + * _sender_ completes `upon_error` proceeds according to this completion: + * - If the completion is set_error(_e_), _fun_(_e_) is invoked: + * - if that invocation throws, `upon_error` completes with set_error(_r_, std::current_exception()); + * otherwise + * - if the invocation returns `void`, `upon_error` completes with set_value(_r_); otherwise + * - if the invocation returns _v_, `upon_error` completes with set_value(_r_, _v_). + * - Otherwise, if the completion is set_value(_a_...), `upon_error` completes with set_value(_r_, + * _a_...) + * - Otherwise, if the completion is set_stopped(), `upon_error` completes with + * set_stopped(_r_). + * + *

    Usage

    + *
    + * upon_error(sender, fun)
    + * sender | upon_error(fun)
    + * 
    + * + *

    Completions Signatures

    + * The completion signatures depend on the completion signatures _CS_ of _sender_ (the + * completion signatures will be deduplicated): + * - For each set_error_t(_E_) in _CS_, + * there is a completion signature + * set_value_t(decltype(_fun_(std::declval<_E_>()))). + * - If for any of the set_error_t(_E_) in + * _CS_ the expression noexcept(_fun_(_std::declval()_)) + * is `false` there is a completion signature set_error_t(std::exception_ptr). + * - Each set_value_t(_A_...) in _CS_ is copied. + * - If set_stopped_t() is in _CS_ it is copied. + * + *

    Example

    + * + *
    + * #include 
    + * #include 
    + * namespace ex = beman::execution;
    + *
    + * int main() {
    + *     auto result = ex::sync_wait(ex::just_error(10) | ex::upon_error([](int v) { return v == 3; }));
    + *     assert(result);
    + *     assert(*result == std::tuple(false));
    + * }
    + * 
    + */ +inline constexpr ::beman::execution::upon_error_t upon_error{}; + +/*! + * \brief upon_stopped(_sender_, _fun_) yields a sender transforming a set_stopped_t() + * completion + * \headerfile beman/execution/execution.hpp + * + * \details + * `upon_stopped` is a callable object of type `upon_stopped_t`. Invoking upon_stopped(_sender_, _fun_) or + * _sender_ | upon_stopped(_fun_) yields a sender + * which, when `start`ed starts _sender_ and awaits its completion. When + * _sender_ completes `upon_stopped` proceeds according to this completion: + * - If the completion is set_stopped(_e_), _fun_(_e_) is invoked: + * - if that invocation throws, `upon_stopped` completes with set_error(_r_, + * std::current_exception()); otherwise + * - if the invocation returns `void`, `upon_stopped` completes with set_value(_r_); otherwise + * - if the invocation returns _v_, `upon_stopped` completes with set_value(_r_, _v_). + * - Otherwise, if the completion is set_value(_a_...), `upon_stopped` completes with set_value(_r_, + * _a_...) + * - Otherwise, if the completion is set_error(_e_), `upon_stopped` completes with set_error(_r_, + * _e_). + * + *

    Usage

    + *
    + * upon_stopped(sender, fun)
    + * sender | upon_stopped(fun)
    + * 
    + * + *

    Completions Signatures

    + * The completion signatures depend on the completion signatures _CS_ of _sender_ (the + * completion signatures will be deduplicated): + * - There is a completion signature set_value_t(decltype(_fun_())). + * - If the expression noexcept(_fun_()) is `false` there is a completion signature + * set_error_t(std::exception_ptr). + * - Each set_value_t(_A_...) in _CS_ is copied. + * - Each set_error_t(_A_...) in _CS_ is copied. + * + *

    Example

    + * + *
    + * #include 
    + * #include 
    + * namespace ex = beman::execution;
    + *
    + * int main() {
    + *     auto result = ex::sync_wait(ex::just_stopped() | ex::upon_stopped([]() { return true; }));
    + *     assert(result);
    + *     assert(*result == std::tuple(true));
    + * }
    + * 
    + */ inline constexpr ::beman::execution::upon_stopped_t upon_stopped{}; } // namespace beman::execution