![]() |
beman::execution
Building Block For Asynchronous Programs
|
This page provides an overview of the components in std::execution. The documentation on this page doesn’t represent all details of the specification. However, it should capture enough details to be a suitable resource to determine how the various components are used.
For each of the components a summary view is provided. To get more details expand the respective section.
This section defines a few terms used throughout the description on this page. The terms aren’t taken from the specification and are, thus, somewhat informal.
When an asynchronous operation completes it signals its completion by calling a completion function on a receiver:
std::execution::set_value(receiver, args...) is called when an operation completes successfully. A call to this completion function is referred to as value completion signal.std::execution::set_error(receiver, error) is called when an operation fails to deliver its success results. A call to this completion function is referred to as error completion signal.std::execution::set_stopped() is called when an operation was cancelled. A call to this completion function is referred to as cancellation completion signal.started asynchronous operation triggers exactly one completion signal. The term enviroment refers to the bag of properties associated with an object by the call std::execution::get_env(object). By default the environment for objects is empty (std::execution::env<>). In particular, environments associated with receivers are used to provide access to properties like the stop token, scheduler, or allocator associated with the receiver. The various properties associated with an object are accessed via queries.
This section lists the concepts from std::execution.
operation_state<State>Operation states represent asynchronous operations ready to be started or executing. Operation state objects are normally neither movable nor copyable. Once started the object needs to be kept alive until a completion signal is received. Users don’t interact with operation states explicitly except when implementing new sender algorithms.
Required members for State:
operation_state_concept is an alias for operation_state_t or a type derived thereof.state.start() & noexceptThis example shows a simple operation state object which immediately completes successfully without any values (as () would do). Normally start() initiates an asynchronous operation completing at some point later.
receiver<Receiver>Receivers are used to receive completion signals: when an asynchronous operation completes the corresponding completion signal is called with the appropriate arguments. In addition receivers provide access to the environment for the operation via the get_env method. Users don’t interact with receivers explicitly except when implementing new sender algorithms.
Required members for Receiver:
receiver_concept is an alias for receiver_t or a type derived thereof`.Receiver are movable.Receiver are copyable.std::execution::get_env(receiver) returns an object. By default this operation returns std::execution::env<>.Typical members for Receiver:
get_env() const noexceptset_value(args…) && noexcept -> voidset_error(error) && noexcept -> voidset_stopped() && noexcept -> voidThe example receiver prints the name of each the received completion signal before forwarding it to a receiver. It forwards the request for an environment (get_env) to the nested receiver. This example is resembling a receiver as it would be used by a sender injecting logging of received signals.
receiver_of<Receiver, Completions>The concept receiver_of<Receiver, Completions> tests whether std::execution::receiver<Receiver> is true and if an object of type Receiver can be invoked with each of the completion signals in Completions.
The example defines a simple receiver and tests whether it models receiver_of with different completion signals in Completions (note that not all cases are true).
scheduler<Scheduler>Schedulers are used to specify the execution context where the asynchronous work is to be executed. A scheduler is a lightweight handle providing a schedule operation yielding a sender with a value completion signal without parameters. The completion is on the respective execution context.
Requirements for Scheduler:
Scheduler::scheduler_concept is an alias for scheduler_t or a type derived thereof.schedule(scheduler) -> sendersender’s environment is the scheduler: scheduler == std::execution::get_completion_scheduler<std::execution::set_value_t>( std::execution::get_env(std::execution::schedule(scheduler)) )std::equality_comparable<Scheduler>std::copy_constructible<Scheduler> sender<Sender>Senders represent asynchronous work. They may get composed from multiple senders to model a workflow. Senders can’t be run directly. Instead, they are passed to a <a href=‘::sender-consumer’ which connects the sender to a receiver to produce an operation_state which may get started. When using senders to represent work the inner workings shouldn’t matter. They do become relevant when creating sender algorithms.
Requirements for Sender:
Sender::sender_concept is an alias for sender_t or a type derived thereof or Sender is a suitable awaitable.std::execution::get_env(sender) is valid. By default this operation returns std::execution::env<>.Sender can be moved.Sender can be copied.Typical members for Sender:
get_env() const noexceptget_completion_signatures(env) const noexcept -> std::execution::completion_signatures<...>Sender::completion_signatures is a type alias for std::execution::completion_signatures<...> (if there is no get_completion_signatures member).connect(sender, receiver) -> operation_stateThe example shows a sender implementing an operation similar to just(_value).
sender_in<Sender, Env = std::execution::env<>>The concept sender_in<Sender, Env> tests whether Sender is a sender, Env is a destructible type, and std::execution::get_completion_signatures(sender, env) yields a specialization of std::execution::completion_signatures.
sender_to<Sender, Receiver>The concept sender_to<Sender, Receiver> tests if std::execution::sender_in<Sender, std::execution::env_of_t<Receiver>> is true, and if Receiver can receive all completion signals which can be sent by Sender, and if Sender can be connected to Receiver.
To determine if Receiver can receive all completion signals from Sender it checks that for each Signature in std::execution::get_completion_signals(sender, std::declval<std::execution::env_of_t<Receiver>>()) the test std::execution::receiver_of<Receiver, Signature> yields true. To determine if Sender can be connected to Receiver the concept checks if connect(std::declval<Sender>(), std::declval<Receiver>) is a valid expression.
sends_stopped<Sender, Env = std::execution::env<>>The concept sends_stopped<Sender, Env> determines if Sender may send a stopped completion signal. To do so, the concepts determines if std::execution::get_completion_signals(sender, env) contains the signatures std::execution::set_stopped_t().
stoppable_token<Token>A stoppable_token<Token>, e.g., obtained via std::execution::get_stop_token(env) is used to support cancellation of asynchronous operations. Using token.stop_requested() an active operation can poll whether it was requested to cancel. An inactive operation waiting for a notification can use an object of a specialization of the template Token::callback_type to get notified when cancellation is requested.
Required members for Token:
Token::callback_type<Callback> can be specialized with a std::callable<Callback> type.token.stop_requested() const noexcept -> booltoken.stop_possible() const noexcept -> boolstd::copyable<Token>std::equality_comparable<Token>std::swappable<Token> Example: concept use
{c++}static_assert(std::execution::unstoppable_token<std::execution::never_stop_token>);static_assert(std::execution::unstoppable_token<std::execution::stop_token>);static_assert(std::execution::unstoppable_token<std::execution::inline_stop_token>);Example: polling
This example shows a sketch of using a
stoppable_token<Token>to cancel an active operation. The computation in this example is represented assleep_for.{c++}void compute(std::stoppable_token auto token){using namespace std::chrono::literals;while (not token.stop_requested()) {std::this_thread::sleep_for(1s);}}Example: inactive
This example shows how an
operation_statecan use thecallback_typetogether with atokento get notified when cancellation is requested.{c++}template <std::execution::receiver Receiver>struct example_state{struct on_cancel{example_state& state;auto operator()() const noexcept {this->state.stop();}};using operation_state_concept = std::execution::operation_state_t;using env = std::execution::env_of_t<Receiver>;using token = std::execution::stop_callback_of_t<env>;using callback = std::execution::stop_callback_of_t<token, on_cancel>;std::remove_cvref_t<Receiver> receiver;std::optional<callback> cancel{};std::atomic<std::size_t> outstanding{};auto start() & noexcept {this->outstanding += 2u;this->cancel.emplace(std::execution::get_stop_token(this->receiver),on_cancel{*this});if (this->outstanding != 2u)std::execution::set_stopped(std::move(this->receiver));else {register_work(this);if (this->outstanding == 0u)std::execution::set_value(std::move(this->receiver));}}auto stop() {unregister_work(this);if (--this->outstanding == 0u)std::execution::set_stopped(std::move(this->receiver));}auto complete() {if (this->outstanding == 2u) {this->cancel.reset();std::execution::set_value(std::move(this->receiver));}}};
unstoppable_token<Token>The concept unstoppable_token<Token> is modeled by a Token if stoppable_token<Token> is true and it can statically be determined that both token.stop_requested() and token.stop_possible() are constexpr epxressions yielding false. This concept is used to avoid extra work when using stop tokens which will never indicate that cancellations are requested.
Example
The concept yields
truefor thestd::execution::never_stop_token:{c++}static_assert(std::execution::unstoppable_token<std::execution::never_stop_token>);static_assert(not std::execution::unstoppable_token<std::execution::stop_token>);static_assert(not std::execution::unstoppable_token<std::execution::inline_stop_token>);
The queries are used to obtain properties associated with an object.
This example shows how to define an environment class which provides a get_allocator query. The objects stores a std::pmr::memory_resource* and returns a correspondingly initialized std::pmr::polymorphic_allocator<>.
forwarding_query(query) -> boolDefault: false
The expression forwarding_query(query) is a constexpr query used to determine if the query query should be forwarded when wrapping an environment. The expression is required to be a core constant expression if query is a core constant expression.
The result of the expression is determined as follows:
query.query(forwarding_query) if this expression is valid and noexcept. true if the type of query is publicly derived from forwarding_query. false. Example
When defining a custom query
customit is desirable to allow the query getting forwarded. It is necessary to explicit define the result offorwarding_query(custom). The result can be defined by providing a correspondingquerymember function. When using this approach the function isn’t allowed to throw, needs to returnbool, and needs to be a core constant expression:struct custom_t {// ...constexpr bool query(forwarding_query_t const&) const noexcept {return true;}};inline constexpr custom_t custom{};Alternatively, the query can be defined as forwarding by deriving publicly from
forwarding_query_t:struct custom_t: forwarding_query_t {// ...};
get_env(queryable) -> envDefault: env<>
The expression get_env(queryable) is used to get the environment env associated with queryable. To provide a non-default environment for a queryable a get_env member needs to be defined. If queryable doesn’t provide the get_env query an object of type env<> is returned. The value of the expression is
as_const(queryable).get_env() if this expression is valid and noexcept. env<> otherwise. The example defines an environment class env which stores a pointer to the relevant data and is returned as the environment for the type queryable:
Note that the get_env member is both const and noexcept.
get_allocator(env) -> allocatorDefault: none
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
noexcept; simple-allocator. Otherwise the expression is ill-formed.
This example shows how to define an environment class which provides a get_allocator query. The objects stores a std::pmr::memory_resource* and returns a correspondingly initialized std::pmr::polymorphic_allocator<>.
get_completion_scheduler<Tag>(env) -> schedulerDefault: 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
Tag is one of the types set_value_t, set_error_t, or set_stopped_t; noexcept; scheduler. Otherwise the expression is invalid.
get_completion_signatures(sender, env)The expression get_completion_signatures(sender, env) returns an object whose type is a specialization of completion_signatures defining the possible completion signatures of sender when connected to a receiver whose <a href=‘::environment'>environment get_env(receiver) is env. A sender can define the result of this query either by defining a member function get_completion_signatures or using a type alias completion_signatures.
To determine the result the sender is first transformed using transform_sender(domain, sender, env) to get new-sender with type New-Sender-Type. With that the result type is
new-sender.get_completion_signatures(env) if this expression is valid; 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; When a sender doesn’t need to compute the completion signatures based on an environment it is easiest to use a the type alias, e.g.:
get_delegation_scheduler(env) -> schedulerThe expression get_delegation_scheduler(env) yields the scheduler associated with env which is used for forward progress delegation. The value of the expression is equivalent to as_const(env).query(get_delegation_scheduler) -> scheduler if
noexcept; scheduler. Otherwise the expression is invalid.
get_domain(env) -> domainThe 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
noexcept. Otherwise the expression is invalid.
get_forward_progress_guarantee(scheduler) -> forward_progress_guaranteeThe expression get_forward_progress_guarantee(scheduler) yields the forward progress guarantee of the scheduler’s execution agent. The value of the expression is equivalent to as_const(env).query(get_scheduler) if
noexcept; forward_progress_guarantee. Otherwise the expression is invalid.
get_scheduler(env) -> schedulerThe expression get_scheduler(env) yields the scheduler associated with env. The value of the expression is equivalent to as_const(env).query(get_scheduler) if
noexcept; scheduler. Otherwise the expression is invalid.
get_stop_token(env) -> stoppable_tokenThe expression get_stop_token(env) yields the stop token associated with env. The value is the result of the expression as_const(env).query(get_stop_token) if
noexcept; stoppable_token. Otherwise the value is never_stop_token{}.
connect(sender, receiver) -> operation_stateThe expression connect(sender, receiver) combines sender and receiver into an operation state state. When this state gets started using start(state) the operation represented by sender gets started and reports its completion to receiver or an object copied or moved from receiver. While the operation state state isn’t started it can be destroyed but once it got started it needs to stay valid until one of the completion signals is called on receiver.
set_error(receiver, error) noexcept -> voidThe expression set_error(receiver, error) invokes the set_error completion signal on receiver with the argument error, i.e., it invokes receiver.set_error(error).
set_stopped(receiver) noexcept -> voidThe expression set_stopped(receiver) invokes the set_stopped completion signal on receiver, i.e., it invokes receiver.set_stopped().
set_value(receiver, value...) noexcept -> voidThe expression set_value(receiver, value...) invokes the set_value completion signal on receiver with the argument(s) value..., i.e., it invokes receiver.set_value(value...).
start(state) noexcept -> voidThe expression start(state) starts the execution of the operation_state object state. Once this expression started executing the object state is required to stay valid at least until one of the completion signals of state’s receiver is invoked. Once started exactly one of the completion signals is eventually called.
Sender factories create a sender which forms the start of a graph of lazy work items.
just(value...) -> sender-of<set_value_t(Value...)>The expression just(value...) creates a sender which sends value... on the set_value (success) channel when started (note that value... can be empty).
Completions
set_value_t(decltype(value)...) 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
set_error_t(decltype(error)) 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
set_stopped_t() 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:
Completions
set_value_t(decltype(query(get_env(receiver)))) 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
set_value_t() upon success set_error_t(Error) upon failure if scheduler may fail set_stopped_t() upon cancellation if scheduler supports cancellation The sender adaptors take one or more senders and adapt their respective behavior to complete with a corresponding result. The description uses the informal function completions-of(sender) to represent the completion signatures which sender produces. Also, completion signatures are combined using +: the result is the deduplicated set of the combined completion signatures.
affine_on(sender) -> sender-of<completions-of(sender)>The expression affine_on(sender) creates a sender which completes on the same scheduler it was started on, even if sender changes the scheduler. The scheduler to resume on is determined using get_scheduler(get_env(rcvr)) where rcvr is the receiver the sender is connected to.
The primary use of affine_on is implementing scheduler affinity for task.
bulkcontinues_on(sender, scheduler) -> sender-of<completions-of(sender) + completions-of(schedule(scheduler))>The expression continues_on(sender, scheduler) creates a sender cs which starts sender when started. The results from sender are stored. Once that is cs creates a sender using schedule(scheduler) and completes itself on the execution once that sender completes.
Completions
completions-of(sender) completions-of(schedule(scheduler)) into_variant(sender) -> sender-of<set_value_t(std::variant<Tuple...>)>The expression into_variant(sender) creates a sender which transforms the results of possibly multiple set_value completions of sender into one set_value completion representing the different upstream results as different options of a variant<Tuple...> where each Tuple is a tuple of values initialized with the respective arguments passed to set_value. The order of options in the variant isn’t specified.
let_error(upstream, fun) -> senderlet_stopped(upstream, fun) -> senderlet_value(upstream, fun) -> senderon(sched, sndr)schedule_from(scheduler, sender) -> sendersplitstarts_on(scheduler, sender) -> senderstopped_as_errorstopped_as_optionalthen(upstream, fun) -> senderupon_error(upstream, fun) -> senderupon_stopped(upstream, fun) -> senderwhen_all(sender...) -> senderwhen_all_with_variant(sender...) -> senderwrite_env(sender, env) -> sendersync_wait(sender) -> std::optional<std::tuple<T...>>as_awaitablewith_awaitable_senderapply_sendercompletion_signaturescompletion_signatures_tconnect_result_tdefault_domainenv<T...>env_of_terror_types_of_tfwd_envoperation_state_treceiver_trun_loopscheduler_tschedule_result_tsender_adaptor_closuresender_tstop_token_of_ttag_of_ttransform_sendertransform_completion_signaturestransform_completion_signatures_ofvalue_types_of_tnever_stop_tokenstop_tokeninplace_stop_token