beman::execution
Building Block For Asynchronous Programs
Loading...
Searching...
No Matches
overview

std::execution Overview

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.

Terms

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.

completion signal

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.
  • Collectively the value, error, and cancellation completion signals are referred to as completion signal. Note that any started asynchronous operation triggers exactly one completion signal.
environment

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.

Concepts

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:

  • The type operation_state_concept is an alias for operation_state_t or a type derived thereof.
  • state.start() & noexcept
Example

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

{c++}
template <std::execution::receiver Receiver>
struct example_state
{
using operation_state_concept = std::execution::operation_state_t;
std::remove_cvref_t<Receiver> receiver;
auto start() & noexcept {
std::execution::set_value(std::move(this->receiver));
}
};
static_assert(std::execution::operation_state<example_state<SomeReceiver>>);
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:

  • The type receiver_concept is an alias for receiver_t or a type derived thereof`.
  • Rvalues of type Receiver are movable.
  • Lvalues of type Receiver are copyable.
  • std::execution::get_env(receiver) returns an object. By default this operation returns std::execution::env<>.

Typical members for Receiver:

Example

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

{c++}
template <std::execution::receiver NestedReceiver>
struct example_receiver
{
using receiver_concept = std::execution::receiver_t;
std::remove_cvref_t<NestedReceiver> nested;
auto get_env() const noexcept {
return std::execution::get_env(this->nested);
}
template <typename… A>
auto set_value(A&&… a) && noexcept -> void {
std::cout << “set_value\n”;
std::execution::set_value(std::move(this->nested), std::forward<A>(a)…);
}
template <typename E>
auto set_error(E&& e) && noexcept -> void {
std::cout << “set_error\n”;
std::execution::set_error(std::move(this->nested), std::forward<E>(e));
}
auto set_stopped() && noexcept -> void {
std::cout << “set_stopped\n”;
std::execution::set_stopped(std::move(this->nested));
}
};
static_assert(std::execution::receiver<example_receiver<SomeReceiver>>);
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.

Example

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

{c++}
struct example_receiver
{
using receiver_concept = std::execution::receiver_t;
auto set_value(int) && noexcept ->void {}
auto set_stopped() && noexcept ->void {}
};
// matching the exact signals models receiver_of:
static_assert(std::execution::receiver_of<example_receiver,
std::execution::completion_signals<
std::execution::set_value_t(int),
std::execution::set_stopped_t()
>);
// providing a superset of signal models models receiver_of:
static_assert(std::execution::receiver_of<example_receiver,
std::execution::completion_signals<
std::execution::set_value_t(int)
>);
// providing only a subset of signals doesn’t model receiver_of:
static_assert(not std::execution::receiver_of<example_receiver,
std::execution::completion_signals<
std::execution::set_value_t(),
std::execution::set_value_t(int)
>);
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:

  • The type Scheduler::scheduler_concept is an alias for scheduler_t or a type derived thereof.
  • schedule(scheduler) -> sender
  • The value completion scheduler of the sender’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:

  • The type 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<>.
  • Rvalues of type Sender can be moved.
  • Lvalues of type Sender can be copied.

Typical members for Sender:

Example

The example shows a sender implementing an operation similar to just(_value).

{c++}
struct example_sender
{
template <std::execution::receiver Receiver>
struct state
{
using operation_state_concept = std::execution::operation_state_t;
std::remove_cvref_t<Receiver> receiver;
int value;
auto start() & noexcept {
std::execution::set_value(
std::move(this->receiver),
this->value
);
}
};
using sender_concept = std::execution::sender_t;
using completion_signatures = std::execution::completion_signatures<
std::execution::set_value_t(int)
>;
int value{};
template <std::execution::receiver Receiver>
auto connect(Receiver&& receiver) const -> state<Receiver> {
return { std::forward<Receiver>(receiver), this->value };
}
};
static_assert(std::execution::sender<example_sender>);
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 -> bool
  • token.stop_possible() const noexcept -> bool
  • std::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 as sleep_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_state can use the callback_type together with a token to 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 true for the std::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>);

Queries

The queries are used to obtain properties associated with an object.

Example defining a query on an environment

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

struct alloc_env {
std::pmr::memory_resource res{std::pmr::new_delete_resource()};
auto query(get_allocator_t const&) const noexcept {
return std::pmr::polymorphic_allocator<>(this->res);
}
};
forwarding_query(query) -> bool

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

  1. The result is the value of the expression query.query(forwarding_query) if this expression is valid and noexcept.
  2. The result is true if the type of query is publicly derived from forwarding_query.
  3. Otherwise the result is false.
Example

When defining a custom query custom it is desirable to allow the query getting forwarded. It is necessary to explicit define the result of forwarding_query(custom). The result can be defined by providing a corresponding query member function. When using this approach the function isn’t allowed to throw, needs to return bool, 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) -> env

Default: env&lt;&gt;
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

  1. the result of as_const(queryable).get_env() if this expression is valid and noexcept.
  2. env<> otherwise.
Example

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:

{c++}
struct data { /*...*/ };
struct env { data* d; /* ... */ };
struct queryable {
data* d;\
// ...
env get_env() const noexcept { return { this->d }; }
};

Note that the get_env member is both const and noexcept.

get_allocator(env) -> allocator

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

  • the expression is valid;
  • the expression is noexcept;
  • the result of the expression satisfies simple-allocator.

Otherwise the expression is ill-formed.

Example

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

struct alloc_env {
std::pmr::memory_resource res{std::pmr::new_delete_resource()};
auto query(get_allocator_t const&) const noexcept {
return std::pmr::polymorphic_allocator<>(this->res);
}
};
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

  1. Tag is one of the types set_value_t, set_error_t, or set_stopped_t;
  2. this expression is valid;
  3. this expression is noexcept;
  4. the expression’s type satisfies 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

  1. the type of new-sender.get_completion_signatures(env) if this expression is valid;
  2. the type remove_cvref_t<New-Sender-Type>::completion_signatures if this type exists;
  3. 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;
  4. invalid otherwise.
Example

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

{c++}
struct sender {
using sender_concept = std::execution::sender_t;
using completion_signatures = std::completion_signatures<
std::execution::set_value_t(int),
std::execution::set_error_t(std::error_code),
std::execution::set_stopped()
>;
// ...
};
get_delegation_scheduler(env) -> scheduler

The 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

  1. this expression is valid;
  2. this expression is noexcept;
  3. the expression’s type satisfies scheduler.

Otherwise the expression is invalid.

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. this expression is noexcept.

Otherwise the expression is invalid.

get_forward_progress_guarantee(scheduler) -> forward_progress_guarantee

The 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

  1. this expression is valid;
  2. this expression is noexcept;
  3. the expression’s type is forward_progress_guarantee.

Otherwise the expression is invalid.

get_scheduler(env) -> scheduler

The 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

  1. this expression is valid;
  2. this expression is noexcept;
  3. the expression’s type satisfies scheduler.

Otherwise the expression is invalid.

get_stop_token(env) -> stoppable_token

The 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

  • the expression is valid;
  • the expression is noexcept;
  • the expression satisfies stoppable_token.

Otherwise the value is never_stop_token{}.

Customization Point Objects

connect(sender, receiver) -> operation_state

The 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 -> void

The 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 -> void

The expression set_stopped(receiver) invokes the set_stopped completion signal on receiver, i.e., it invokes receiver.set_stopped().

set_value(receiver, value...) noexcept -> void

The 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 -> void

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

Senders

Sender Factories

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:

{c++\}
auto token = co_await read_env(get_stop_token);

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

Sender Adaptors

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.

bulk

continues_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) -> sender

let_stopped(upstream, fun) -> sender

let_value(upstream, fun) -> sender

on(sched, sndr)

schedule_from(scheduler, sender) -> sender

split

starts_on(scheduler, sender) -> sender

stopped_as_error

stopped_as_optional

then(upstream, fun) -> sender

upon_error(upstream, fun) -> sender

upon_stopped(upstream, fun) -> sender

when_all(sender...) -> sender

when_all_with_variant(sender...) -> sender

write_env(sender, env) -> sender

Sender Consumers

  • sync_wait(sender) -> std::optional<std::tuple<T...>>

Helpers

  • as_awaitable
  • with_awaitable_sender
  • apply_sender
  • completion_signatures
  • completion_signatures_t
  • connect_result_t
  • default_domain
  • env&lt;T...&gt;
  • env_of_t
  • error_types_of_t
  • fwd_env
  • operation_state_t
  • receiver_t
  • run_loop
  • scheduler_t
  • schedule_result_t
  • sender_adaptor_closure
  • sender_t
  • stop_token_of_t
  • tag_of_t
  • transform_sender
  • transform_completion_signatures
  • transform_completion_signatures_of
  • value_types_of_t

Stop Token

  • never_stop_token
  • stop_token
  • inplace_stop_token