Guide

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.

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

Concepts

This section lists the concepts from std::execution.

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_tag or a type derived thereof.

  • state.start() & noexcept

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.

template <std::execution::receiver Receiver>
struct example_state
{
    using operation_state_concept = std::execution::operation_state_tag;
    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>>);

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

  • get_env() const noexcept

  • set_value(args…) && noexcept → void

  • set_error(error) && noexcept → void

  • set_stopped() && noexcept → void

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.

template <std::execution::receiver NestedReceiver>
struct example_receiver
{
    using receiver_concept = std::execution::receiver_tag;
    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>>);

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

struct example_receiver
{
    using receiver_concept = std::execution::receiver_tag;

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

Requirements for Scheduler:

  • The type Scheduler::scheduler_concept is an alias for scheduler_tag 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>

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

  • get_env() const noexcept

  • get_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_state

struct example_sender
{
    template <std::execution::receiver Receiver>
    struct state
    {
        using operation_state_concept = std::execution::operation_state_tag;
        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_tag;
    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>);

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.

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.

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

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>

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>);
void compute(std::stoppable_token auto token)
{
    using namespace std::chrono::literals;
    while (not token.stop_requested()) {
         std::this_thread::sleep_for(1s);
    }
}
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_tag;
    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));
        }
    }
};
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.

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);
   }
};

The result of the expression is determined as follows:

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 {
    // ...
};
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.

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);
   }
};

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

Customization Point Objects

Senders

Sender Factories

Sender factories create a sender which forms the start of a graph of lazy work items.

Completions

Completions

Completions

Completions

Completions

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.

The primary use of affine is implementing scheduler affinity for task.

Completions

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<T…​>

  • env_of_t

  • error_types_of_t

  • fwd_env

  • operation_state_tag

  • receiver_tag

  • run_loop

  • scheduler_tag

  • schedule_result_t

  • sender_adaptor_closure

  • sender_tag

  • 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