Reference

Concepts

The stdexec API is structured around a small set of foundational concepts. Most sender adaptors and consumers express their requirements in terms of these concepts, so understanding them — and which one to reach for in which situation — pays off across the rest of the reference.

The concepts fall into three layers:

  • Sender side: sender, sender_in, sender_to. A sender is the basic unit of composition — a value that describes (but does not yet execute) an async computation.

  • Receiver / operation-state side: receiver, receiver_of, operation_state. These describe the consumer half of a sender/receiver pair — the destination of completion signals and the running operation that delivers them.

  • Context side: scheduler, scope_token, scope_association. These describe how work is dispatched onto execution resources and how its lifetime is tracked.

Sender concepts

sender

template<class _Sender>
concept sender
#include <__sender_concepts.hpp>

The fundamental concept of the sender model: a type that describes (but does not yet execute) an asynchronous operation.

A sender is the basic unit of composition in stdexec. It is a value type that describes an async computation; the work it describes does not start until the sender is connected to a receiver (via stdexec::connect) and the resulting operation state is started (via stdexec::start).

Concretely, a type S satisfies sender if:

  1. S has been opted into the concept — either by exposing a sender_concept type alias derived from stdexec::sender_tag, or by specializing stdexec::enable_sender<S> to true, or by being an awaitable in stdexec’s coroutine promise type.

  2. S provides an environment via stdexec::get_env (every sender has an environment, possibly empty).

  3. S's decayed type is move-constructible and constructible from an S (this is what allows senders to be stored and forwarded by value).

Note that sender by itself does not require the sender’s completion signatures to be computable. That is the additional constraint of sender_in

(which carries an environment). Generic sender-adaptor code that needs to know “what does this sender

complete with?” uses

sender_in<S, Env>, not sender (alone).

See [exec.snd.concepts] in the C++26 working draft.

See also

stdexec::sender_in — sender plus a specific environment, with computable signatures

See also

stdexec::sender_to — sender plus a specific receiver, with compatible signatures

See also

stdexec::sender_tag — the tag type that opts a class into this concept

See also

stdexec::enable_sender — alternative opt-in path

struct sender_tag

Tag type used to opt a class into the stdexec::sender concept.

A user-defined type satisfies stdexec::sender by exposing a public sender_concept type alias whose type derives from sender_tag:

struct my_sender {
  using sender_concept = stdexec::sender_tag;

  // ... usual sender machinery: completion signatures, connect ...
};

See also

stdexec::sender

Subclassed by experimental::execution::sequence_sender_tag

template<class _Sender>
bool const stdexec::enable_sender = __detail::__enable_sender<_Sender>

A variable template that opts a class into the stdexec::sender concept by an alternative path.

Specialize enable_sender<MySender> to true to declare that MySender is a sender, without having to define a sender_concept type alias on the class itself. This is useful when the class cannot be modified (e.g. third-party types) or when the class is a coroutine awaitable type.

struct legacy_sender { };  // cannot be modified

template <>
inline constexpr bool stdexec::enable_sender<legacy_sender> = true;

By default, enable_sender<S> is true when S has a sender_concept alias deriving from sender_tag, or when S is awaitable in stdexec’s coroutine promise type.

sender_in

template<class _Sender, class ..._Env>
concept sender_in
#include <__sender_concepts.hpp>

A sender whose completion signatures can be computed in a given environment.

sender_in is the form of the sender concept that generic adaptor code actually uses. Where sender

just asks “is this a sender at

all?”,

sender_in<S, Env>

asks “is @c S a sender whose completion

signatures we can compute when connected to a receiver with

environment @c Env?” — that information is what every adaptor needs to type-check itself.

Concretely, sender_in<S, Env> requires:

  1. S satisfies sender.

  2. get_completion_signatures<S, Env>() is a constant expression whose value is a valid completion_signatures specialization.

The Env parameter is optional (the variadic accepts zero or one environment). When no environment is supplied, the sender must have a dependent-environment-free set of completion signatures — i.e. its signatures must not vary by environment.

See [exec.snd.concepts] in the C++26 working draft.

See also

stdexec::sender — the base concept

See also

stdexec::sender_to — adds a specific receiver

See also

stdexec::get_completion_signatures — the customization point this concept depends on

sender_to

template<class _Sender, class _Receiver>
concept sender_to
#include <__sender_concepts.hpp>

A sender that can be connected to a specific receiver.

sender_to<S, R> is the strongest form of the sender concept: it requires that S is a sender whose completion signatures can be computed in R's environment, that R is a receiver that accepts all of those signatures, and that connect(S, R) is well-formed.

This is the constraint a sender consumer or scheduler implementation uses just before actually calling connect — it’s the strongest way to say “yes, this pair is wired up correctly.”

See [exec.snd.concepts] in the C++26 working draft.

See also

stdexec::sender_in — without the receiver-compatibility check

See also

stdexec::receiver_of — the receiver-side mirror of this concept

See also

stdexec::connect — the operation sender_to validates

Receiver and operation-state concepts

receiver

template<class _Receiver>
concept receiver
#include <__receivers.hpp>

The fundamental concept of the receiver model: a callback-shaped object that consumes the result of an asynchronous operation.

A receiver is the destination half of a sender/receiver pair. It is the object on which a started operation eventually invokes one of set_value, set_error, or set_stopped to deliver its completion. Receivers are typically synthesized by sender consumers and adaptors — most user code never writes a receiver by hand; it writes senders and composes them.

Concretely, a type R satisfies receiver if:

  1. R is opted into the concept via a receiver_concept type alias deriving from stdexec::receiver_tag.

  2. R provides an environment via stdexec::get_env (so child operations can query for the stop token, allocator, scheduler, etc.).

  3. R's decayed type is nothrow move-constructible — receivers are moved into operation states by sender adaptors, and that move must not throw.

  4. R's decayed type is constructible from an R.

Note that this concept alone does not require R to accept any particular completion signals — for that, see receiver_of, which takes a completion_signatures pack and validates that the receiver has matching set_value / set_error / set_stopped members.

See [exec.recv.concepts] in the C++26 working draft.

See also

stdexec::receiver_of — receiver plus specific completion signatures

See also

stdexec::receiver_tag — the tag type that opts a class into this concept

struct receiver_tag

Tag type used to opt a class into the stdexec::receiver concept.

A user-defined type satisfies stdexec::receiver by exposing a public receiver_concept type alias whose type derives from receiver_tag:

struct my_receiver {
  using receiver_concept = stdexec::receiver_tag;

  void set_value(int v) noexcept                { ... }
  void set_error(std::exception_ptr e) noexcept { ... }
  void set_stopped() noexcept                   { ... }
};

Subclassed by stdexec::__receiver_adaptor< _Derived, _Base >

receiver_of

template<class _Receiver, class _Completions>
concept receiver_of
#include <__receivers.hpp>

A receiver that accepts a specific set of completion signatures.

receiver_of<R, Sigs>

says: “R is a receiver, and for every

@c set_xxx_t(Args…) signature in @c Sigs (a

@c stdexec::completion_signatures pack), R has a matching member that

is callable with @c Args… “. This is the constraint that ensures a sender’s completion signals can actually be delivered to the receiver — sender adaptors typically express their compatibility requirements in terms of

receiver_of, not bare receiver.

When this concept fails, stdexec produces a focused error message naming the specific completion signal the receiver doesn’t accept (e.g. “the receiver does not accept set_value_t(int)”) — this is the main reason to use receiver_of over manually checking each member’s callability.

See [exec.recv.concepts] in the C++26 working draft.

See also

stdexec::receiver — without the signature check

See also

stdexec::sender_to — the sender-side mirror of this concept

See also

stdexec::completion_signatures — the signature pack this concept consumes

operation_state

template<class _Op>
concept operation_state
#include <__operation_states.hpp>

An in-progress, immovable, startable representation of an asynchronous operation — the result of connecting a sender to a receiver.

An operation state is what you get from stdexec::connect: it is the concrete, type-erased-by-construction record of one specific sender being driven into one specific receiver. Calling stdexec::start on it begins the work; the operation runs until it eventually invokes one of set_value / set_error / set_stopped on the receiver it was constructed with.

Three properties define the concept operation_state:

  1. The type is destructible — operations clean up cleanly when their storage goes away (typically after they have completed).

  2. The type is an object type (not a reference, not a function) — operation states are stored, not passed by handle.

  3. stdexec::start(op) is well-formed for any lvalue op of the type.

What the concept does not require (but the rules of the sender model do):

  • Immovability after construction. Once an operation state has been constructed, it must not be moved or copied — child operations inside it typically hold pointers into its storage, which would dangle on move. The natural way to satisfy this is to delete the move and copy constructors, which is what most operation states do. The concept itself doesn’t check this; the rule is part of the sender-model contract.

  • Lifetime guarantee until completion. Once start has been called, the operation state must remain alive until the receiver has been completed. This is the caller’s responsibility (the caller of start, typically a sender adaptor or a consumer).

Like receivers, operation states are usually an implementation detail of sender adaptors and consumers — most user code never names a specific operation-state type.

See [exec.opstate] in the C++26 working draft.

See also

stdexec::connect — the customization point that produces operation states

See also

stdexec::start — the customization point this concept depends on

See also

stdexec::operation_state_tag — the tag type that opts a class into this concept

struct operation_state_tag

Tag type used to opt a class into the stdexec::operation_state concept.

A user-defined operation-state type satisfies stdexec::operation_state by exposing a public operation_state_concept type alias whose type derives from operation_state_tag:

struct my_opstate {
  using operation_state_concept = stdexec::operation_state_tag;

  void start() noexcept { ... }
};

Context concepts

scheduler

template<class _Scheduler>
concept scheduler
#include <__schedulers.hpp>

A lightweight handle to an execution context — the abstraction over things that can run sender pipelines.

A scheduler is a small, value-like handle to a (potentially heavy and immovable) execution resource: a thread pool, a GPU stream, an event loop, an inline run-loop, etc. The only operation a scheduler must support is stdexec::schedule, which obtains a sender that, when connected and started, eventually value-completes on that resource.

Concretely, a type S satisfies scheduler if:

  1. schedule(s) is well-formed and returns a sender. This is the defining operation; everything else is value-semantics plumbing.

  2. S's decayed type is equality-comparable (two schedulers compare equal iff they refer to the same execution resource — used for optimization decisions such as elision of redundant continues_on hops).

  3. S's decayed type is copy-constructible and nothrow move-constructible — schedulers are cheap to pass around.

See [exec.sched] in the C++26 working draft.

See also

stdexec::schedule — the customization point that defines this concept

See also

stdexec::starts_on — adaptor that runs a sender on a scheduler

See also

stdexec::continues_on — adaptor that transfers to a scheduler mid-pipeline

See also

stdexec::schedule_result_t — the sender type returned by schedule(s)

scope_token

template<class _Token>
concept scope_token
#include <__scope_concepts.hpp>

A copyable handle to an async scope — the owner of lifetime for spawned operations.

An async scope is a logical container for fire-and-forget operations launched via stdexec::spawn or stdexec::spawn_future. It tracks every operation associated with it so that, at shutdown, it can block until every spawned operation has completed (typically via a .join() member that returns a sender).

A scope_token is a small, copyable handle to such a scope. User code typically obtains a token from an exec::async_scope via scope.get_token() and passes it into spawn or spawn_future.

Concretely, a type T satisfies scope_token if it is copyable and provides two members:

  1. token.try_associate() — attempts to register a new operation with the scope, returning a scope_association whose boolean conversion indicates success. Fails (returns “false”) when the scope has already begun shutting down.

  2. token.wrap(sndr) — wraps a sender so that, when started via spawn, its lifetime is tied to the scope.

See [exec.scope.concepts] in the C++26 working draft.

See also

stdexec::scope_association — the return type of try_associate

See also

stdexec::spawn — fire-and-forget into a scope

See also

stdexec::spawn_future — spawn into a scope and observe via a sender

scope_association

template<class _Assoc>
concept scope_association
#include <__scope_concepts.hpp>

A movable handle representing successful (or failed) registration of an operation with an async scope.

Implementations of stdexec::scope_token return a scope_association from try_associate(). It is a small, movable value that:

  • Contextually converts to bool to indicate whether the association succeeded (the scope is still open and the operation was registered) or failed (the scope is shutting down and the operation must not start).

  • Holds onto whatever state the scope needs to track the operation so that the operation can be deregistered at completion.

  • Can produce a fresh, equivalent association via try_associate()

    — used to model “re-associate with the

    same scope”.

Concretely, scope_association requires the type to be movable, nothrow move-constructible and assignable, default-initializable, and to support both the bool conversion and the try_associate() member.

User code typically does not interact with scope_association directly — it is the return type of scope_token::try_associate and is used internally by stdexec::spawn and stdexec::spawn_future.

See [exec.scope.concepts] in the C++26 working draft.

See also

stdexec::spawn

Core Customization Points

The customization points listed here are the defining operations of the sender model. They are what every sender, receiver, and operation state type must support (each in its own way) to participate in the protocol. Most user code never calls these directly — sender adaptors and consumers do — but anyone writing a new sender, receiver, or scheduler will implement one or more of these.

The CPOs fall into three layers:

  • Sender-side: connect, get_completion_signatures, get_env. These describe how a sender exposes its computation and attributes to the framework.

  • Operation-state-side: start. The trigger that turns a connected sender into a running operation.

  • Receiver-side: set_value, set_error, set_stopped, get_env. These describe how an operation state delivers a completion to its receiver and queries the receiver’s environment.

See the Developer’s Guide for a narrative walkthrough of how these fit together when writing a new sender adaptor.

Sender-side

connect

struct connect_t

Customization point object that connects a sender to a receiver, producing an operation state.

connect is the central seam of the sender model. Calling stdexec::connect(sndr, rcvr) does not run the sender; it produces an operation state — an opaque, immovable, startable object that, when subsequently passed to stdexec::start, will eventually deliver exactly one completion signal (set_value, set_error, or set_stopped) to the receiver.

Every sender adaptor and consumer ultimately reaches for connect. Most user code does not call it directly — sync_wait, spawn, and the various adaptors do — but it is the operation a sender author must support, either by exposing a .connect(receiver) member, by being a coroutine awaitable (so the fallback awaitable adapter applies), or — historically — via tag_invoke (now deprecated).

See [exec.connect] in the C++26 working draft for the normative specification.

Lookup.

Before dispatching, connect transforms the sender via the active domain’s transform_sender (passing in get_env(rcvr) as the environment). This is how domain-based customization — e.g. the GPU scheduler taking over a then chain — is implemented.

connect then dispatches by trying, in order:

  1. A static member: S::__static_connect(sndr, rcvr) (an stdexec-internal extension point).

  2. A non-static member: sndr.connect(rcvr). This is the standard way sender authors customize connect in C++26.

  3. The awaitable fallback: if sndr is awaitable in stdexec’s receiver-promise type, an adapter operation state is synthesized. This is what makes coroutines work as senders.

  4. tag_invoke(connect, sndr, rcvr) — deprecated, retained for backwards compatibility.

Customization.

The standard pattern for sender authors is the .connect() member:

struct my_sender {
  using sender_concept = stdexec::sender_tag;
  using completion_signatures = stdexec::completion_signatures<
    stdexec::set_value_t(int)>;

  template <stdexec::receiver_of<completion_signatures> R>
  auto connect(R rcvr) && -> my_opstate<R> {
    return my_opstate<R>{std::move(rcvr), ...};
  }
};

The returned object must satisfy stdexec::operation_state. In particular, it must be immovable once constructed (typically by deleting the move and copy constructors).

Concept checks.

connect(s, r) is only well-formed when both sender_in<S, env_of_t<R>> and receiver_of<R, completion_signatures_of_t<S, env_of_t<R>>> hold. The diagnostics for failures here are intentionally focused — stdexec emits messages that name the specific completion signal or environment query the receiver doesn’t accept.

See also

stdexec::start — what you call on the returned operation state

See also

stdexec::operation_state — the concept the result satisfies

See also

stdexec::sender_to — the concept this CPO drives

See also

stdexec::set_value — one of the completions the operation eventually delivers

See also

stdexec::transform_sender — the domain-customization step run before dispatch

Public Functions

template<class _Sender, class _Receiver, class _DeclFn = __connect::__connect_declfn_t<_Sender, _Receiver>>
inline constexpr auto operator()(_Sender &&__sndr, _Receiver &&__rcvr) const noexcept(__nothrow_callable<_DeclFn>) -> __call_result_t<_DeclFn>

Connect __sndr to __rcvr, returning an operation state.

Template Parameters:
Parameters:
  • __sndr – The sender describing the asynchronous work. Perfect-forwarded into the operation state.

  • __rcvr – The receiver that will eventually receive a completion. Perfect-forwarded into the operation state.

Returns:

An object satisfying stdexec::operation_state. Pass it to stdexec::start to begin the work.

constexpr connect_t stdexec::connect

The customization point object for connecting a sender to a receiver.

connect is an instance of connect_t. See connect_t for the full description, the lookup order, and customization examples.

get_completion_signatures

Unlike the other entries in this section, get_completion_signatures is a function template (not a CPO instance), so it has no underlying struct. The two forms — environment-free and environment-dependent — share documentation:

Operation-state-side

start

struct start_t

Customization point object that begins the execution of an operation state.

start is the trigger that turns a connected sender into a running asynchronous operation. The operation state returned by stdexec::connect does nothing until it is passed to start; from that moment on, it is running and will eventually deliver exactly one completion signal to the receiver it was connected with.

See [exec.opstate.start] in the C++26 working draft.

Lifetime contract.

The operation state passed to start must:

  1. Be an lvaluestart(op) with op an rvalue is ill-formed. The reason: the caller is responsible for keeping the operation state alive until completion, which only makes sense for objects with stable identity.

  2. Remain alive until the receiver has been completed. The operation state itself typically holds child operation states (for sender adaptors) and references to the receiver’s storage — destroying it early would invalidate those.

Once start returns, the operation is running but may or may not have already completed (an inline-completing operation may have completed synchronously before start returned; an async operation may complete arbitrarily later).

Customization.

An operation state opts in by exposing a noexcept, void-returning .start() member:

struct my_opstate {
  using operation_state_concept = stdexec::operation_state_tag;

  // Immovable — once constructed, must stay put:
  my_opstate(my_opstate&&) = delete;

  void start() noexcept {
    // Begin async work; eventually call set_value/set_error/set_stopped
    // on the receiver stored in this op-state.
  }
};

start() must be noexcept — there’s nowhere for it to throw to, since the caller is typically the runtime, not user code that can handle exceptions. It must also return void. The dispatch site enforces both with static asserts.

tag_invoke-based customization is supported via a deprecated overload, retained for backwards compatibility.

See also

stdexec::connect — the CPO that produces operation states

See also

stdexec::operation_state — the concept this CPO drives

See also

stdexec::set_value — one of the completions start eventually triggers

Public Functions

template<class _Op>
inline constexpr void operator()(_Op &__op) const noexcept

Begin execution of __op.

Dispatches to __op.start(). Statically asserts both noexcept and void-returning.

Template Parameters:

_Op – A type satisfying stdexec::operation_state.

Parameters:

__op – An lvalue reference to the operation state. Passing an rvalue is ill-formed.

start_t const stdexec::start

The customization point object for starting an operation state.

start is an instance of start_t. See start_t for the full description, the lifetime contract, and customization examples.

Receiver-side

set_value

struct set_value_t : public stdexec::__detail::__completion_tag<__disposition::__value>

Customization point object for the value completion signal of the sender/receiver protocol.

set_value(rcvr, vs...) is the call an operation state makes on its receiver to deliver a successful asynchronous result. It is one of three terminal completion signals (alongside set_error_t and set_stopped_t) that exactly one of will be called on a receiver once a connected operation has started.

User code rarely calls set_value directly — it is invoked from inside operation-state implementations. Sender authors writing new adaptors do call it, and receiver authors provide the matching member that this CPO dispatches to.

Customization.

A receiver opts into receiving value completions by defining a noexcept, void-returning member:

struct my_receiver {
  using receiver_concept = stdexec::receiver_tag;
  void set_value(int v) noexcept { ... }   // overload set per value type
};

At the call site, stdexec::set_value(rcvr, vs...) dispatches to rcvr.set_value(vs...), statically asserting both that the member is noexcept and that it returns void.

See [exec.recv] in the C++26 working draft.

See also

stdexec::set_error — the error-completion CPO

See also

stdexec::set_stopped — the stopped-completion CPO

See also

stdexec::receiver — the receiver concept this CPO drives

See also

stdexec::receiver_of — receiver plus specific completion signatures

Public Functions

template<class _Receiver, class ..._As>
inline constexpr void operator()(_Receiver &&__rcvr, _As&&... __as) const noexcept

Deliver a value completion to __rcvr.

Dispatches to __rcvr.set_value(__as...). The static asserts inside enforce that the member is noexcept and that it returns void — the two non-negotiable properties of every completion signal.

Template Parameters:
set_value_t const stdexec::set_value

The customization point object for delivering a value completion.

set_value is an instance of set_value_t. See set_value_t for the full description and customization rules.

set_error

struct set_error_t : public stdexec::__detail::__completion_tag<__disposition::__error>

Customization point object for the error completion signal of the sender/receiver protocol.

set_error(rcvr, e) is the call an operation state makes on its receiver to deliver a failure. Unlike a thrown exception, the error is a typed datum — receivers may distinguish, say, std::exception_ptr from std::error_code from a domain-specific error enum by overloading on the argument type.

Customization.

A receiver opts into receiving error completions of a given type E by defining a noexcept, void-returning member:

struct my_receiver {
  using receiver_concept = stdexec::receiver_tag;
  void set_error(std::exception_ptr e) noexcept { ... }
  void set_error(std::error_code e) noexcept { ... }   // multiple OK
};

Like set_value, the dispatch site enforces noexcept and void return via static asserts.

Receivers MUST accept exactly one error completion at runtime. That is: at most one of set_value, set_error, set_stopped is ever called on a given receiver, exactly once.

See [exec.recv] in the C++26 working draft.

Public Functions

template<class _Receiver, class _Error>
inline constexpr void operator()(_Receiver &&__rcvr, _Error &&__err) const noexcept

Deliver an error completion to __rcvr.

Dispatches to __rcvr.set_error(__err). Statically asserts both noexcept and void-returning.

Template Parameters:
  • _Receiver – A type with a matching .set_error(_Error) member.

  • _Error – The error datum type.

set_error_t const stdexec::set_error

The customization point object for delivering an error completion.

set_error is an instance of set_error_t. See set_error_t for the full description and customization rules.

set_stopped

struct set_stopped_t : public stdexec::__detail::__completion_tag<__disposition::__stopped>

Customization point object for the stopped completion signal of the sender/receiver protocol.

set_stopped(rcvr) is the call an operation state makes on its receiver to report that the operation was cancelled. It carries no datum

— the stopped channel is informational only (“we are

ending early because cancellation was requested or because no result

is needed any more”).

Cancellation is cooperative: receivers can request stop via the stop token in their environment (see stdexec::get_stop_token); senders that observe such a request may complete with set_stopped instead of set_value or set_error.

Customization.

A receiver opts into receiving stopped completions by defining a noexcept, void-returning nullary member:

struct my_receiver {
  using receiver_concept = stdexec::receiver_tag;
  void set_stopped() noexcept { ... }
};

See [exec.recv] in the C++26 working draft.

See also

stdexec::get_stop_token — the receiver-environment query for the stop token

Public Functions

template<class _Receiver>
inline constexpr void operator()(_Receiver &&__rcvr) const noexcept

Deliver a stopped completion to __rcvr.

Dispatches to __rcvr.set_stopped(). Statically asserts both noexcept and void-returning.

Template Parameters:

_Receiver – A type with a matching nullary .set_stopped() member.

set_stopped_t const stdexec::set_stopped

The customization point object for delivering a stopped completion.

set_stopped is an instance of set_stopped_t. See set_stopped_t for the full description and customization rules.

get_env

struct get_env_t

Customization point object that obtains the environment of a sender or receiver.

Every sender and every receiver has an associated environment — an unordered, type-keyed bag of properties such as the stop token, the allocator, the preferred scheduler, the start scheduler, and any domain-specific properties a sender adaptor wants to expose. The environment is what makes the sender model contextual: a sender adapts its behavior based on the environment of the receiver it is connected to.

get_env(provider) returns the environment of provider, where provider is either a receiver (yielding its environment, which the sender will introspect via queries) or a sender (yielding its attributes, which the framework consults to determine things like the sender’s completion scheduler).

See [exec.queries.get_env] in the C++26 working draft.

Customization.

Most receivers and senders simply expose a noexcept, const-callable .get_env() member returning their environment:

struct my_receiver {
  using receiver_concept = stdexec::receiver_tag;

  auto get_env() const noexcept {
    return stdexec::env{stdexec::prop{stdexec::get_stop_token, my_stop_token_}};
  }
};

Many receivers don’t have any properties to expose — for those, the get_env member can simply return an empty stdexec::env<> (or the get_env CPO will return one automatically via its __ignore overload).

tag_invoke-based customization is supported via a deprecated overload, retained for backwards compatibility.

Environment queries.

Once you have an environment, you query it by calling the appropriate query CPO on it: get_stop_token(env), get_allocator(env), get_scheduler(env), etc. Each query is a separate CPO; the environment dispatches based on the query’s type. Inside a sender pipeline you almost always reach for stdexec::read_env (or its helpers like get_stop_token() with no argument) rather than calling get_env directly.

See also

stdexec::env — the environment container type

See also

stdexec::read_env — the sender factory that exposes env values to pipelines

See also

stdexec::get_stop_token — example of an environment query CPO

See also

stdexec::get_allocator

See also

stdexec::get_scheduler

Public Functions

template<class _EnvProvider>
inline constexpr auto operator()(_EnvProvider const &__env_provider) const noexcept -> __detail::__get_env_member_result_t<_EnvProvider const&>

Obtain the environment of __env_provider.

Dispatches to __env_provider.get_env(), statically asserting that the member is noexcept.

Template Parameters:

_EnvProvider – A type whose const-lvalue has a .get_env() const member.

Parameters:

__env_provider – The receiver or sender whose environment to retrieve.

Returns:

The environment object (typed as defined by the provider).

constexpr get_env_t stdexec::get_env

The customization point object for obtaining a sender’s or receiver’s environment.

get_env is an instance of get_env_t. See get_env_t for the full description and customization examples.

Sender Factories

A sender factory is an algorithm that produces a sender from non-sender inputs (values, an error, a scheduler, an environment query). Factories sit at the head of a sender pipeline.

just — produce a sender from values

Produces a sender that synchronously completes with the given values on the value channel. The canonical way to inject literal values into a sender pipeline. See just — inject literal values for an approachable introduction with worked examples.

struct just_t

A sender factory that produces a sender which completes synchronously with the given values on the value channel.

just is the simplest sender factory: it captures zero or more values and produces a sender that, when connected and started, immediately delivers those values via set_value to the connected receiver — all within the receiver’s start() call, without any context transition. It is the canonical way to inject literal values into a sender pipeline.

auto s0 = stdexec::just();              // value-completes with no datums
auto s1 = stdexec::just(42);            // value-completes with one int
auto s2 = stdexec::just(1, 2, 3);       // value-completes with three ints
auto s3 = stdexec::just(std::string{"x"}, 7);  // mixed types are fine

See [exec.just] in the C++26 working draft for the normative specification.

Completion signatures.

Given just(ts...) with argument-pack types Ts…, the resulting sender has the single completion signature:

set_value_t(std::decay_t<Ts>...)

Each argument is decay-copied into the resulting sender. The error and stopped channels are empty — just never completes with set_error or set_stopped.

Exception behavior.

The factory call itself is noexcept when every decay-copy is noexcept. The produced sender’s start() is noexcept by construction.

Cancellation.

just does not consult the receiver’s stop token; it always synchronously completes with set_value.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto sndr = just(21) | then([](int x) { return x * 2; });
  auto [v]  = sync_wait(std::move(sndr)).value();
  assert(v == 42);
}

See also

stdexec::just_error — synchronously complete with an error

See also

stdexec::just_stopped — synchronously complete with stopped

See also

stdexec::read_env — synchronously complete with a value read from the environment

Public Functions

template<__movable_value... _Ts>
inline constexpr auto operator()(_Ts&&... __ts) const noexcept(__nothrow_decay_copyable<_Ts...>)

Construct a sender that synchronously value-completes with the decay-copies of __ts….

Template Parameters:

_Ts – Zero or more types each satisfying the internal __movable_value concept.

Parameters:

__ts – The values to deliver. Each is decay-copied into the resulting sender.

Returns:

A sender with the single completion signature set_value_t(std::decay_t<_Ts>...).

just_t const stdexec::just

The customization point object for the just sender factory.

just is an instance of just_t. See just_t for the full description, completion signatures, and a usage example.

just_error — produce a sender from an error

Produces a sender that synchronously completes with the given error on the error channel. Mostly useful for testing error-handling adaptors.

struct just_error_t

A sender factory that produces a sender which completes synchronously with the given error on the error channel.

just_error is the error-channel analogue of just: it captures a single error datum and produces a sender that, when connected and started, immediately delivers that error via set_error to the connected receiver. It is the canonical way to inject a literal error into a sender pipeline — useful for testing error-handling adaptors such as upon_error and let_error.

auto s1 = stdexec::just_error(std::error_code{ENOENT, std::system_category()});
auto s2 = stdexec::just_error(std::make_exception_ptr(std::runtime_error{"boom"}));

See [exec.just] in the C++26 working draft for the normative specification (just_error is specified alongside just and just_stopped).

Completion signatures.

Given just_error(e) with E = decltype((e)), the resulting sender has the single completion signature:

set_error_t(std::decay_t<E>)

The error is decay-copied into the resulting sender. The value and stopped channels are empty.

Cancellation.

just_error does not consult the receiver’s stop token; it always synchronously completes with set_error.

See also

stdexec::just — synchronously complete with values

See also

stdexec::just_stopped — synchronously complete with stopped

See also

stdexec::upon_error — handle the error channel

See also

stdexec::let_error — handle the error channel with a sender-returning function

Public Functions

template<__movable_value _Error>
inline constexpr auto operator()(_Error &&__err) const noexcept(__nothrow_decay_copyable<_Error>)

Construct a sender that synchronously error-completes with the decay-copy of __err.

Template Parameters:

_Error – A type satisfying the internal __movable_value concept.

Parameters:

__err – The error datum to deliver. Decay-copied into the sender.

Returns:

A sender with the single completion signature set_error_t(std::decay_t<_Error>).

just_error_t const stdexec::just_error

The customization point object for the just_error sender factory.

just_error is an instance of just_error_t. See just_error_t for the full description, completion signatures, and a usage example.

just_stopped — produce a stopped sender

Produces a sender that synchronously completes on the stopped channel. Mostly useful for testing cancellation-handling adaptors.

struct just_stopped_t

A sender factory that produces a sender which completes synchronously on the stopped channel.

just_stopped is the stopped-channel analogue of just: it produces a sender that, when connected and started, immediately invokes set_stopped on the connected receiver. It carries no datum (the stopped channel has none). It is the canonical way to inject a literal cancellation into a sender pipeline — useful for testing cancellation handling adaptors such as upon_stopped and let_stopped.

auto s = stdexec::just_stopped();

See [exec.just] in the C++26 working draft for the normative specification (just_stopped is specified alongside just and just_error).

Completion signatures.

The resulting sender has the single completion signature:

set_stopped_t()

The value and error channels are empty.

Cancellation.

just_stopped does not consult the receiver’s stop token (the cancellation it delivers is unconditional, not a response to a request).

See also

stdexec::just — synchronously complete with values

See also

stdexec::just_error — synchronously complete with an error

See also

stdexec::upon_stopped — handle the stopped channel

See also

stdexec::let_stopped — handle the stopped channel with a sender-returning function

Public Functions

template<class _Tag = just_stopped_t>
inline constexpr auto operator()() const noexcept

Construct a sender that synchronously stops-completes.

Returns:

A sender with the single completion signature set_stopped_t().

just_stopped_t const stdexec::just_stopped

The customization point object for the just_stopped sender factory.

just_stopped is an instance of just_stopped_t. See just_stopped_t for the full description, completion signatures, and a usage example.

read_env — produce a sender from an environment query

Produces a sender whose value completion is the result of querying the connected receiver’s environment with a given query CPO. It is the primitive behind the standard get_stop_token(), get_allocator(), get_scheduler() helpers. See read_env — read a value from the receiver’s environment for an approachable introduction.

__read_env_t const stdexec::read_env

A sender factory that produces a sender whose value completion is the result of querying the receiver’s environment.

read_env reaches into the receiver to read a value associated with a query CPO — things like the receiver’s stop token, its associated allocator, or its preferred scheduler. The resulting sender, when connected and started, evaluates q(get_env(rcvr)) and delivers the result via set_value to the connected receiver.

It is the primitive used by the standard environment-query helpers such as get_stop_token(), get_allocator(), get_scheduler(), and get_delegation_scheduler() — each of those is simply read_env applied to the corresponding query CPO.

The call form takes a query CPO (not a value):

auto sndr = stdexec::read_env(stdexec::get_stop_token);

See [exec.read.env] in the C++26 working draft for the normative specification.

Completion signatures.

Given read_env(q) and an environment type Env (taken from the connected receiver), the resulting sender has completion signatures:

set_value_t(decltype(q(declval<Env>())))    // always present
set_error_t(std::exception_ptr)             // present iff q(env) may throw

The query result type is taken from the actual environment at connect time, so the same read_env sender may have different concrete completion signatures depending on which receiver it is connected to.

If the environment does not provide a value for q (i.e. q(env) is ill-formed or returns void), the program is ill-formed at the point where the sender is connected, with a diagnostic that names the offending query.

Exception behavior.

If invoking q on the receiver’s environment throws, the exception is delivered through set_error_t(std::exception_ptr). If q is noexcept (typical for query CPOs), no std::exception_ptr error completion is added.

Cancellation.

read_env does not consult the receiver’s stop token; it completes synchronously in its start.

Example.

#include <stdexec/execution.hpp>
using namespace stdexec;

// Lift the current stop token into the pipeline so a downstream
// algorithm can inspect it:
auto sndr =
  read_env(get_stop_token)
  | then([](auto tok) {
      return tok.stop_requested();
    });

See also

stdexec::just — synchronously complete with literal values

See also

stdexec::get_stop_token — equivalent to read_env(get_stop_token)

See also

stdexec::get_scheduler — equivalent to read_env(get_scheduler)

schedule — produce a sender from a scheduler

Produces a sender that, when connected and started, value-completes from the context of the given scheduler. It is the bridge between the scheduler and sender abstractions, and the way to begin a pipeline that must run on a specific execution context. See schedule — start a pipeline on a scheduler for an approachable introduction.

struct schedule_t

A sender factory that obtains a sender from a scheduler.

schedule is the bridge between a scheduler (a lightweight handle to an execution context) and the sender world: it produces a sender that, when connected and started, eventually completes with set_value to the connected receiver from the context of the scheduler. It is the canonical way to begin a sender pipeline that needs to run on a specific execution context (a thread pool, a GPU stream, an event loop, …).

auto sched = stdexec::get_parallel_scheduler();
auto sndr  = stdexec::schedule(sched);

The sender returned by schedule(sched) is informally called a “schedule-sender” or “schedule sender”. The expression schedule(sch) is expression-equivalent to sch.schedule(), which is the customization point that scheduler authors implement.

schedule also drives the stdexec::scheduler concept: a type S satisfies scheduler if (and roughly only if) schedule(s) is a valid expression returning a sender, and S is equality-comparable and nothrow-move-constructible.

See [exec.schedule] in the C++26 working draft for the normative specification.

Completion signatures.

The exact signatures are determined by the scheduler’s implementation, but every conforming schedule-sender includes:

set_value_t()                   // delivered on the scheduler's context
set_stopped_t()                 // typical: stop-token observed during scheduling
set_error_t(...)                // implementation-defined; some schedulers can fail

set_value carries no datums. The point of schedule is the context transition, not the value — downstream adaptors (then, let_value, etc.) chained onto the schedule-sender therefore run on the scheduler’s context.

Cancellation.

A schedule-sender that has not yet started executing on the scheduler’s context typically observes the receiver’s stop token and may complete with set_stopped instead. Once it has begun running on the scheduler’s context, the value completion is delivered.

Example.

#include <stdexec/execution.hpp>

int main() {
  using namespace stdexec;
  auto sched = get_parallel_scheduler();

  auto sndr =
    schedule(sched)                                // hop onto sched
    | then([] { return 42; });                     // ... and compute on it

  auto [v] = sync_wait(std::move(sndr)).value();
  (void)v;
}

See also

stdexec::starts_onstart a sender on a given scheduler

See also

stdexec::continues_on — transfer execution to a scheduler mid-pipeline

See also

stdexec::on — execute a sender on a scheduler then return

Public Functions

template<class _Scheduler>
inline auto operator()(_Scheduler &&__sched) const noexcept(noexcept(static_cast<_Scheduler&&>(__sched).schedule())) -> decltype(static_cast<_Scheduler&&>(__sched).schedule())

Obtain a schedule-sender by calling __sched.schedule().

Template Parameters:

_Scheduler – A type whose lvalue has a member function .schedule() returning a sender.

Parameters:

__sched – The scheduler to obtain a sender from.

Returns:

The sender produced by __sched.schedule() — a sender that, when connected and started, value-completes (with no datums) on __sched's execution context.

Pre:

decltype(__sched.schedule()) must satisfy the stdexec::sender concept (statically checked).

template<class _Scheduler>
inline auto operator()(_Scheduler &&__sched) const noexcept(__nothrow_tag_invocable<schedule_t, _Scheduler>) -> __tag_invoke_result_t<schedule_t, _Scheduler>

Deprecated overload: obtain a schedule-sender via tag_invoke.

Deprecated:

The tag_invoke-based customization of schedule is deprecated in favor of the sched.schedule() member-function form. New scheduler types should provide a member .schedule() instead of a tag_invoke overload for schedule_t.

schedule_t const stdexec::schedule

The customization point object for the schedule sender factory.

schedule is an instance of schedule_t. See schedule_t for the full description, completion signatures, scheduler-concept relationship, and a usage example.

just_from (experimental) — like just but value-producing via a function

constexpr just_from_t experimental::execution::just_from = {}
constexpr just_error_from_t experimental::execution::just_error_from = {}
constexpr just_stopped_from_t experimental::execution::just_stopped_from = {}

Sender Adaptors

TODO: More sender adaptor algorithms

then — apply a function to the value channel

Transforms a predecessor sender’s value completion by invoking a callable with the values it produces. See then — transform a value for an approachable introduction with worked examples; the complete reference (including completion-signature transformation rules, exception behavior, and the operator() overloads) follows.

struct then_t

A pipeable sender adaptor that transforms a predecessor sender’s value completion by invoking a callable on the values it produces.

then maps the value channel of a sender through a function while forwarding the error and stopped channels unchanged. It is the asynchronous analogue of applying std::invoke to the result of a synchronous computation, and is the most common sender adaptor in practice; most sender pipelines contain at least one then.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::then(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::then(f);  // pipe syntax

The two forms are expression-equivalent. See [exec.then] in the C++26 working draft for the normative specification.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // one or more value completions
set_error_t(Es)...               // zero or more error completions
set_stopped_t()                  // optional stopped completion

the sender produced by then(sndr, f) has completion signatures

set_value_t(R)                   // R = decltype(std::invoke(f, Vs...))
                                 // (or set_value_t() when R is void)
set_error_t(Es)...               // forwarded unchanged from sndr
set_error_t(std::exception_ptr)  // added when invoking f may throw
set_stopped_t()                  // forwarded unchanged from sndr

If sndr has multiple value completions, f must be invocable with every one of them; otherwise the program is ill-formed and the diagnostic surfaces at the point where the resulting sender is connected to a receiver. The resulting value-completion arity is always one: each distinct return type R from invoking f contributes a set_value_t(R) overload.

Exception behavior.

If invoking f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender. When f is noexcept for every value-argument pack of sndr, no additional std::exception_ptr error completion is added.

Cancellation.

then does not interact with the receiver’s stop token. When sndr completes with set_stopped, f is not invoked and the stopped completion is forwarded to the downstream receiver.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto sndr = just(21)
            | then([](int x) { return x * 2; })
            | then([](int x) { return x + 1; });

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == 43);
}

See also

stdexec::upon_error — adapt the error channel

See also

stdexec::upon_stopped — adapt the stopped channel

See also

stdexec::let_value — adapt the value channel with a sender-returning function

Public Functions

template<sender _Sender, __movable_value _Fun>
inline constexpr auto operator()(_Sender &&__sndr, _Fun __fun) const -> __well_formed_sender auto

Construct a sender that adapts __sndr by invoking __fun with each value-completion argument pack it produces.

Template Parameters:
  • _Sender – A type satisfying the stdexec::sender concept.

  • _Fun – A decayed, move-constructible callable type (satisfying the internal __movable_value concept).

Parameters:
  • __sndr – The predecessor sender whose value-completion is to be adapted. Perfect-forwarded into the resulting sender, so an rvalue is moved and an lvalue copied as needed.

  • __fun – The function (or callable) to invoke with each value-completion of __sndr. Stored by value (decayed) in the resulting sender.

Returns:

A sender that, when connected to a receiver and started, drives __sndr and routes each of its value-completions through __fun. The error and stopped channels are forwarded unchanged.

Pre:

__fun must be invocable with every value-completion argument pack of __sndr (with appropriate value categories). Otherwise the program is ill-formed at the point where the resulting sender is connected to a receiver.

template<__movable_value _Fun>
inline constexpr auto operator()(_Fun __fun) const

Construct a sender-adaptor closure that, when applied to a sender, produces then(sndr, __fun).

This overload enables the pipe syntax: sndr | then(__fun) is equivalent to then(sndr, __fun).

Template Parameters:

_Fun – A decayed, move-constructible callable type (satisfying the internal __movable_value concept).

Parameters:

__fun – The callable to invoke on the predecessor’s value completions when the closure is later applied to a sender.

Returns:

A sender-adaptor closure object that captures __fun by value. When piped against a sender sndr, it yields the sender then(sndr, std::move(__fun)).

then_t const stdexec::then

The customization point object for the then sender adaptor.

then is an instance of then_t. See then_t for the full description, the completion-signature transformation rules, exception and cancellation behavior, and a usage example.

upon_error — handle the error channel

Handles a predecessor sender’s error completion by invoking a callable on the error datum and delivering the result as a value completion — the canonical way to recover from an error and continue the pipeline. See upon_error — recover from an error for an approachable introduction with worked examples.

struct upon_error_t

A pipeable sender adaptor that handles a predecessor sender’s error completion by invoking a callable on the error datum.

upon_error maps the error channel of a sender through a function while forwarding the value and stopped channels unchanged. The function’s return value becomes a value completion on the resulting sender — so upon_error is the canonical way to recover from an error: turn it into a substitute value and continue the pipeline as if nothing had gone wrong.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::upon_error(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::upon_error(f);  // pipe syntax

The two forms are expression-equivalent. See [exec.then] in the C++26 working draft for the normative specification (upon_error is specified alongside then and upon_stopped).

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // forwarded unchanged
set_error_t(Es)...               // one or more error completions
set_stopped_t()                  // forwarded unchanged (if present)

the sender produced by upon_error(sndr, f) has completion signatures

set_value_t(Vs...)               // forwarded unchanged from sndr
set_value_t(R)                   // R = decltype(std::invoke(f, E))  for each E
                                 // (or set_value_t() when R is void)
set_error_t(std::exception_ptr)  // added when invoking f may throw
set_stopped_t()                  // forwarded unchanged from sndr

For each distinct error type E that sndr may complete with, f must be invocable with E (with appropriate value category). Otherwise the program is ill-formed at the point where the resulting sender is connected to a receiver. All original set_error_t completions are replaced by the union of value completions produced by f — only errors thrown by f itself remain on the error channel.

Exception behavior.

If invoking f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender. When f is noexcept for every error type of sndr, no std::exception_ptr error completion is added.

Cancellation.

upon_error does not interact with the receiver’s stop token. When sndr completes with set_stopped, f is not invoked and the stopped completion is forwarded to the downstream receiver.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto sndr = just_error(std::error_code{ENOENT, std::system_category()})
            | upon_error([](std::error_code) { return -1; });

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == -1);
}

See also

stdexec::then — adapt the value channel

See also

stdexec::upon_stopped — adapt the stopped channel

See also

stdexec::let_error — adapt the error channel with a sender-returning function

Public Functions

template<sender _Sender, __movable_value _Fun>
inline constexpr auto operator()(_Sender &&__sndr, _Fun __fun) const -> __well_formed_sender auto

Construct a sender that handles each error completion of __sndr by invoking __fun on the error datum.

Template Parameters:
  • _Sender – A type satisfying the stdexec::sender concept.

  • _Fun – A decayed, move-constructible callable type (satisfying the internal __movable_value concept).

Parameters:
  • __sndr – The predecessor sender whose error-completions are to be adapted. Perfect-forwarded into the resulting sender.

  • __fun – The function (or callable) to invoke with each error-completion datum of __sndr. Stored by value (decayed) in the resulting sender.

Returns:

A sender that, when connected to a receiver and started, drives __sndr and routes each of its error-completions through __fun, delivering the result on the value channel. The value and stopped channels of __sndr are forwarded unchanged.

Pre:

__fun must be invocable with every error type of __sndr (with appropriate value categories). Otherwise the program is ill-formed at the point where the resulting sender is connected to a receiver.

template<__movable_value _Fun>
inline constexpr auto operator()(_Fun __fun) const noexcept(__nothrow_move_constructible<_Fun>)

Construct a sender-adaptor closure that, when applied to a sender, produces upon_error(sndr, __fun).

This overload enables the pipe syntax: sndr | upon_error(__fun) is equivalent to upon_error(sndr, __fun).

Template Parameters:

_Fun – A decayed, move-constructible callable type.

Parameters:

__fun – The callable to invoke on the predecessor’s error completions when the closure is later applied to a sender.

Returns:

A sender-adaptor closure object that captures __fun by value. When piped against a sender sndr, it yields the sender upon_error(sndr, std::move(__fun)).

upon_error_t const stdexec::upon_error

The customization point object for the upon_error sender adaptor.

upon_error is an instance of upon_error_t. See upon_error_t for the full description, completion-signature transformation rules, exception and cancellation behavior, and a usage example.

upon_stopped — handle the stopped channel

Handles a predecessor sender’s stopped completion by invoking a nullary callable and delivering its return value as a value completion — the canonical way to recover from cancellation. See upon_stopped — recover from cancellation for an approachable introduction with worked examples.

struct upon_stopped_t

A pipeable sender adaptor that handles a predecessor sender’s stopped completion by invoking a nullary callable.

upon_stopped maps the stopped channel of a sender into a value completion by invoking a callable with no arguments and forwarding its return value downstream. The value and error channels are forwarded unchanged. This is the canonical way to recover from cancellation: turn a stopped completion into a substitute value and continue.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::upon_stopped(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::upon_stopped(f);  // pipe syntax

The two forms are expression-equivalent. See [exec.then] in the C++26 working draft for the normative specification (upon_stopped is specified alongside then and upon_error).

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // forwarded unchanged
set_error_t(Es)...               // forwarded unchanged
set_stopped_t()                  // (must be present)

the sender produced by upon_stopped(sndr, f) has completion signatures

set_value_t(Vs...)               // forwarded unchanged from sndr
set_value_t(R)                   // R = decltype(std::invoke(f))
                                 // (or set_value_t() when R is void)
set_error_t(Es)...               // forwarded unchanged from sndr
set_error_t(std::exception_ptr)  // added when invoking f may throw
                                 // (no set_stopped_t in the output)

f must be invocable with no arguments — this requirement is enforced by the requires clause on the operator overloads. The original set_stopped_t completion is consumed: the resulting sender will never complete via set_stopped (unless f itself returns a sender that does, which upon_stopped does not — see let_stopped for that).

Exception behavior.

If invoking f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender. When f is noexcept, no std::exception_ptr error completion is added.

Cancellation.

upon_stopped does not interact with the receiver’s stop token. It reacts to the predecessor’s set_stopped by invoking f; it does not itself initiate cancellation.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto sndr = just_stopped()
            | upon_stopped([] { return 42; });

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == 42);
}

See also

stdexec::then — adapt the value channel

See also

stdexec::upon_error — adapt the error channel

See also

stdexec::let_stopped — adapt the stopped channel with a sender-returning function

Public Functions

template<sender _Sender, __movable_value _Fun>
inline auto operator()(_Sender &&__sndr, _Fun __fun) const -> __well_formed_sender auto

Construct a sender that handles a stopped completion of __sndr by invoking __fun and delivering its return value.

Template Parameters:
  • _Sender – A type satisfying the stdexec::sender concept.

  • _Fun – A decayed, move-constructible, nullary callable type.

Parameters:
  • __sndr – The predecessor sender whose stopped completion is to be adapted. Perfect-forwarded into the resulting sender.

  • __fun – The function (or callable) to invoke when __sndr completes via set_stopped. Stored by value (decayed) in the resulting sender.

Returns:

A sender that, when connected to a receiver and started, drives __sndr and reacts to its set_stopped completion by invoking __fun and forwarding the result via set_value. The value and error channels of __sndr are forwarded unchanged.

Pre:

__fun must be invocable with no arguments (the requires clause enforces this). Otherwise the call is not viable.

template<__movable_value _Fun>
inline auto operator()(_Fun __fun) const noexcept(__nothrow_move_constructible<_Fun>)

Construct a sender-adaptor closure that, when applied to a sender, produces upon_stopped(sndr, __fun).

This overload enables the pipe syntax: sndr | upon_stopped(__fun) is equivalent to upon_stopped(sndr, __fun).

Template Parameters:

_Fun – A decayed, move-constructible, nullary callable type.

Parameters:

__fun – The callable to invoke on the predecessor’s stopped completion when the closure is later applied to a sender.

Returns:

A sender-adaptor closure object that captures __fun by value. When piped against a sender sndr, it yields the sender upon_stopped(sndr, std::move(__fun)).

upon_stopped_t const stdexec::upon_stopped

The customization point object for the upon_stopped sender adaptor.

upon_stopped is an instance of upon_stopped_t. See upon_stopped_t for the full description, completion-signature transformation rules, exception and cancellation behavior, and a usage example.

let_value — chain a sender-returning function on the value channel

Chains a sender-returning function onto a predecessor’s value completion. The returned sender is connected and started, and its completions become the completions of the overall pipeline. This is the way to launch another asynchronous operation based on a predecessor’s values. See let_value — chain another async operation for an approachable introduction with worked examples.

struct let_value_t : public stdexec::__let::__let_t<let_value_t>

A pipeable sender adaptor that chains a sender-returning function onto a predecessor’s value completion.

let_value is the way to launch another asynchronous operation based on the values produced by a predecessor sender. Where then takes a function returning a value, let_value takes a function returning a sender, which is then connected and started, becoming the tail of the pipeline.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::let_value(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::let_value(f);  // pipe syntax

The signature of the operator overloads (inherited from a detail base) is:

template <sender Sender, movable-value Fun>
  auto operator()(Sender&& sndr, Fun fun) const -> sender auto;   // direct

template <class Fun>
  auto operator()(Fun fun) const;                                 // closure

The two forms are expression-equivalent. See [exec.let] in the C++26 working draft for the normative specification.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // one or more value completions
set_error_t(Es)...               // forwarded unchanged
set_stopped_t()                  // forwarded unchanged (if present)

the sender produced by let_value(sndr, f) has completion signatures equal to the union of:

  • the completion signatures of every sender returned by an invocation std::invoke(f, vs...) for each value-completion argument pack (vs…) of sndr, plus

  • the set_error_t completions of sndr (forwarded unchanged),

  • the set_stopped_t completion of sndr (forwarded unchanged), and

  • set_error_t(std::exception_ptr) if invoking f or connecting its returned sender may throw.

f must be invocable with every value-completion argument pack of sndr, and every such invocation must return a type satisfying the sender concept. Otherwise the program is ill-formed at the point where the resulting sender is connected to a receiver.

Use then vs. let_value: choose then when the callable returns a value; choose let_value when the callable returns a sender (i.e. when the next step is itself asynchronous). Passing a sender-returning function to then would forward the sender as a value — almost never what you want.

Exception behavior.

If invoking f throws, or if connecting the sender returned by f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender. When both steps are noexcept, no std::exception_ptr error completion is added.

Cancellation.

let_value does not introduce stop-token interaction of its own. If sndr completes with set_stopped, f is not invoked and the stopped completion is forwarded.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto fetch_async = [](int id) {
    return just(id * 10);  // pretend this is a non-trivial async op
  };

  auto sndr = just(7)
            | let_value(fetch_async);

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == 70);
}

See also

stdexec::then — adapt the value channel with a value-returning function

See also

stdexec::let_error — adapt the error channel with a sender-returning function

See also

stdexec::let_stopped — adapt the stopped channel with a sender-returning function

let_value_t const stdexec::let_value

The customization point object for the let_value sender adaptor.

let_value is an instance of let_value_t. See let_value_t for the full description, completion-signature transformation rules, exception and cancellation behavior, and a usage example.

let_error — chain a sender-returning function on the error channel

Chains a sender-returning function onto a predecessor’s error completion — the way to launch another asynchronous operation to recover from an error.

struct let_error_t : public stdexec::__let::__let_t<let_error_t>

A pipeable sender adaptor that chains a sender-returning function onto a predecessor’s error completion.

let_error is to upon_error what let_value is to then: it lets the recovery step be another asynchronous operation. When the predecessor completes with set_error_t(e), f is invoked with e and is expected to return a sender; that sender is connected and started, and its completions become the completions of the overall pipeline.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::let_error(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::let_error(f);  // pipe syntax

See [exec.let] in the C++26 working draft for the normative specification.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // forwarded unchanged
set_error_t(Es)...               // one or more error completions
set_stopped_t()                  // forwarded unchanged (if present)

the sender produced by let_error(sndr, f) has completion signatures equal to the union of:

  • the set_value_t completions of sndr (forwarded unchanged),

  • the completion signatures of every sender returned by an invocation std::invoke(f, e) for each error type e of sndr,

  • the set_stopped_t completion of sndr (forwarded unchanged), and

  • set_error_t(std::exception_ptr) if invoking f or connecting its returned sender may throw.

All original set_error_t completions are consumed: only errors produced by the senders that f returns (or thrown by f itself) survive on the error channel.

Use upon_error vs. let_error: choose upon_error when the recovery is synchronous (returns a value); choose let_error when the recovery is itself asynchronous (returns a sender).

Exception behavior.

If invoking f throws, or if connecting the sender returned by f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender.

Cancellation.

let_error does not introduce stop-token interaction of its own. A set_stopped completion of sndr is forwarded without invoking f.

Example.

#include <stdexec/execution.hpp>
#include <cassert>
#include <system_error>

int main() {
  using namespace stdexec;

  auto retry_async = [](std::error_code) { return just(7); };

  auto sndr = just_error(std::error_code{ENOENT, std::system_category()})
            | let_error(retry_async);

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == 7);
}

See also

stdexec::upon_error — adapt the error channel with a value-returning function

See also

stdexec::let_value — adapt the value channel with a sender-returning function

See also

stdexec::let_stopped — adapt the stopped channel with a sender-returning function

let_error_t const stdexec::let_error

The customization point object for the let_error sender adaptor.

let_error is an instance of let_error_t. See let_error_t for the full description and example.

let_stopped — chain a sender-returning function on the stopped channel

Chains a sender-returning nullary function onto a predecessor’s stopped completion — the way to launch another asynchronous operation to recover from cancellation.

struct let_stopped_t : public stdexec::__let::__let_t<let_stopped_t>

A pipeable sender adaptor that chains a sender-returning nullary function onto a predecessor’s stopped completion.

let_stopped is to upon_stopped what let_value is to then: it lets the recovery from cancellation be another asynchronous operation. When the predecessor completes with set_stopped, f is invoked with no arguments and is expected to return a sender; that sender is connected and started, and its completions become the completions of the overall pipeline.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::let_stopped(sndr, f);   // direct invocation
auto s2 = sndr | stdexec::let_stopped(f);  // pipe syntax

See [exec.let] in the C++26 working draft for the normative specification.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)               // forwarded unchanged
set_error_t(Es)...               // forwarded unchanged
set_stopped_t()                  // (must be present)

the sender produced by let_stopped(sndr, f) has completion signatures equal to the union of:

  • the set_value_t completions of sndr (forwarded unchanged),

  • the set_error_t completions of sndr (forwarded unchanged),

  • the completion signatures of the sender returned by std::invoke(f), and

  • set_error_t(std::exception_ptr) if invoking f or connecting its returned sender may throw.

The original set_stopped_t completion is consumed: it appears on the resulting sender only if the sender returned by f itself completes via set_stopped.

Use upon_stopped vs. let_stopped: choose upon_stopped when the recovery is synchronous (returns a value); choose let_stopped when the recovery is itself asynchronous (returns a sender).

Exception behavior.

If invoking f throws, or if connecting the sender returned by f throws, the exception is delivered through set_error_t(std::exception_ptr) on the resulting sender.

Cancellation.

let_stopped reacts to the predecessor’s set_stopped; it does not initiate cancellation. The sender returned by f sees the receiver’s stop token as normal.

Example.

#include <stdexec/execution.hpp>
#include <cassert>

int main() {
  using namespace stdexec;

  auto fallback_async = [] { return just(42); };

  auto sndr = just_stopped()
            | let_stopped(fallback_async);

  auto [v] = sync_wait(std::move(sndr)).value();
  assert(v == 42);
}

See also

stdexec::upon_stopped — adapt the stopped channel with a value-returning function

See also

stdexec::let_value — adapt the value channel with a sender-returning function

See also

stdexec::let_error — adapt the error channel with a sender-returning function

let_stopped_t const stdexec::let_stopped

The customization point object for the let_stopped sender adaptor.

let_stopped is an instance of let_stopped_t. See let_stopped_t for the full description and example.

Scheduling adaptors

These adaptors move work between execution contexts. starts_on begins a sender on a new scheduler; continues_on transfers execution to a new scheduler after a sender completes; on runs work on a different scheduler and then returns to where it started. See starts_on vs. continues_on vs. on: which one? for a side-by-side comparison.

starts_on — run a sender on a scheduler

Produces a sender that runs an input sender starting on a given scheduler’s execution resource. The completion is delivered on that same resource (no round-trip back).

struct starts_on_t

A sender adaptor that runs a sender starting on the execution resource associated with a given scheduler.

starts_on takes a scheduler sched and a sender sndr and produces a sender that, when connected and started, first hops onto sched's execution resource and then runs sndr there. The completions of the produced sender are delivered to the connected receiver from sched's resource — there is no “round trip” back to whatever scheduler started the operation (compare on_t).

Unlike most sender adaptors in stdexec, starts_on has no pipe form: it is always called as starts_on(sched, sndr), never sndr | starts_on(sched). This reflects the spec: starts_on takes the scheduler first, mirroring the order of operations (schedule, then run).

auto s = stdexec::starts_on(some_sched, sndr);

See [exec.starts.on] in the C++26 working draft for the normative specification.

Equivalence.

Semantically, starts_on(sch, sndr) is equivalent to

schedule(sch) | let_value([sndr = std::forward<Sndr>(sndr)] {
  return std::move(sndr);
})

stdexec’s implementation is structured differently for efficiency on GPU contexts (it avoids making sndr dependent on the schedule-completion), but the observable semantics match.

Completion signatures.

The resulting sender’s completion signatures are essentially those of sndr, augmented with any error completion that the scheduling step itself can produce:

// (signatures of sndr) ...
set_error_t(/* scheduler error type, if scheduling may fail */)
set_stopped_t()                  // if not already present

If scheduling onto sch fails, an error completion is delivered to the receiver on an unspecified execution agent.

Cancellation.

If the receiver requests stop before scheduling has produced its value-completion, the resulting sender typically completes via set_stopped rather than starting sndr at all — the precise behavior depends on the scheduler.

Example.

#include <stdexec/execution.hpp>

int main() {
  using namespace stdexec;

  auto sched = get_parallel_scheduler();

  // Run the entire chain on `sched`:
  auto sndr =
    starts_on(sched, just(21) | then([](int x) { return x * 2; }));

  auto [v] = sync_wait(std::move(sndr)).value();
  (void)v;  // == 42, computed on `sched`
}

See also

stdexec::schedule — the primitive that produces a schedule-sender

See also

stdexec::continues_on — transfer to a scheduler after a sender completes

See also

stdexec::on — run on a scheduler then transfer back to the original

Public Functions

template<scheduler _Scheduler, sender _Sender>
inline constexpr auto operator()(_Scheduler &&__sched, _Sender &&__sndr) const -> __well_formed_sender auto

Construct a sender that runs __sndr on __sched's execution resource.

Template Parameters:
Parameters:
  • __sched – The scheduler whose execution resource will host __sndr.

  • __sndr – The sender to run on __sched.

Returns:

A sender that, when connected to a receiver and started, first schedules onto __sched, then connects and starts __sndr there, and forwards __sndr's completion to the receiver.

starts_on_t const stdexec::starts_on

The customization point object for the starts_on sender adaptor.

starts_on is an instance of starts_on_t. See starts_on_t for the full description, completion signatures, and a usage example.

continues_on — transfer to a scheduler after completion

Produces a sender that runs the input sender to completion, then transfers execution to a given scheduler’s resource before delivering the completion downstream. Anything chained after continues_on runs on the new scheduler.

struct continues_on_t

A pipeable sender adaptor that transfers a predecessor sender’s completion to a different scheduler’s execution resource.

continues_on lets a sender pipeline change execution context in the middle. Given a predecessor sender sndr and a scheduler sched, continues_on produces a sender that, when connected and started, runs sndr to completion on whatever context sndr ran on, then transfers execution to sched's resource, and only then forwards sndr's completion (value, error, or stopped) to the connected receiver. Anything chained after continues_on therefore runs on sched.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::continues_on(sndr, sched);   // direct invocation
auto s2 = sndr | stdexec::continues_on(sched);  // pipe syntax

The two forms are expression-equivalent. See [exec.continues.on] in the C++26 working draft for the normative specification.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)    // forwarded — but delivered on `sched`'s resource
set_error_t(Es)...    // forwarded — but delivered on `sched`'s resource
set_stopped_t()       // forwarded — but delivered on `sched`'s resource

the sender produced by continues_on(sndr, sched) has the same completion signatures as sndr, except that a set_error_t(std::exception_ptr) completion may be added if any of sndr's completion datums are not nothrow decay-copyable (the datums must be stored across the scheduling hop).

continues_on does not alter sndr's values or errors; it only changes the execution context on which the completion is delivered.

Exception behavior.

If decay-copying a completion datum across the scheduling hop throws, the exception is delivered through set_error_t(std::exception_ptr). If scheduling onto sched fails, an error completion is delivered on an unspecified execution agent.

Cancellation.

continues_on respects the receiver’s stop token while waiting to be scheduled onto sched: if cancellation is requested after sndr completes but before the hop finishes, the resulting sender typically completes via set_stopped.

Example.

#include <stdexec/execution.hpp>

int main() {
  using namespace stdexec;

  auto io_sched   = get_parallel_scheduler();   // pretend: I/O
  auto cpu_sched  = get_parallel_scheduler();   // pretend: compute

  auto sndr =
    starts_on(io_sched, just(42))               // produce on io_sched
    | continues_on(cpu_sched)                   // hop to cpu_sched
    | then([](int x) { return x * 2; });        // then() runs on cpu_sched

  auto [v] = sync_wait(std::move(sndr)).value();
  (void)v;  // == 84
}

See also

stdexec::schedule — the primitive that produces a schedule-sender

See also

stdexec::starts_onbegin execution on a given scheduler

See also

stdexec::on — run on a different scheduler, then transfer back

Public Functions

template<scheduler _Scheduler, sender _Sender>
inline constexpr auto operator()(_Sender &&__sndr, _Scheduler __sched) const -> __well_formed_sender auto

Construct a sender that runs __sndr to completion, then transfers execution to __sched before forwarding the completion downstream.

Template Parameters:
Parameters:
  • __sndr – The predecessor sender. Forwarded into the resulting sender.

  • __sched – The scheduler whose execution resource will host the delivery of __sndr's completion.

Returns:

A sender with the same completion signatures as __sndr (plus a possible set_error_t(std::exception_ptr) for decay-copy failures during the hop). The completions are delivered on __sched's execution resource.

template<scheduler _Scheduler>
inline constexpr auto operator()(_Scheduler __sched) const noexcept

Construct a sender-adaptor closure that, when applied to a sender, produces continues_on(sndr, __sched).

This overload enables the pipe syntax: sndr | continues_on(__sched) is equivalent to continues_on(sndr, __sched).

Template Parameters:

_Scheduler – A type satisfying the stdexec::scheduler concept.

Parameters:

__sched – The scheduler to transfer execution to when the closure is later applied to a sender.

Returns:

A sender-adaptor closure object capturing __sched.

constexpr continues_on_t stdexec::continues_on

The customization point object for the continues_on sender adaptor.

continues_on is an instance of continues_on_t. See continues_on_t for the full description, completion signatures, and a usage example.

on — run on a scheduler and return to the original

The “go there, do work, come back” adaptor. Two forms: on(sched, sndr) runs sndr on sched and returns to the start scheduler; on(sndr, sched, closure) (and its pipe form sndr | on(sched, closure)) hops to sched for an inserted closure then hops back. See on — take a side trip to another scheduler for guidance on when to reach for which form.

struct on_t

A sender adaptor that runs work on a different scheduler and then transfers execution back to the original scheduler.

on is the “go there, do work, come back” scheduling adaptor. It has two distinct shapes:

  1. Whole-sender form: on(sched, sndr) — runs the entirety of sndr on sched's execution resource. When sndr completes, execution transfers back to the scheduler that started the operation (the “start scheduler”), and the completion is delivered there.

    This is the principal difference between on and starts_on_tstarts_on stays on sched; on returns home.

  2. Closure-insertion form: on(sndr, sched, closure) — runs sndr on its current scheduler, then transfers to sched, applies closure (a sender-adaptor closure) to the result, runs that on sched, and finally transfers back to the original completion scheduler. Useful for inserting a CPU-bound transform into an otherwise I/O-bound pipeline (or vice versa) without permanently changing context.

    This form also has a pipe shorthand: sndr | on(sched, closure).

// Form 1: run sndr on sched, return to start scheduler.
auto s1 = stdexec::on(sched, sndr);

// Form 2: run sndr in place, hop to sched for closure, hop back.
auto s2 = stdexec::on(sndr, sched, stdexec::then([](int x){ return x*2; }));
auto s3 = sndr | stdexec::on(sched, stdexec::then([](int x){ return x*2; }));

See [exec.on] in the C++26 working draft for the normative specification.

The round trip.

What distinguishes on from starts_on and continues_on is the restoration of the original scheduler:

Adaptor

Where work runs

Where completion is delivered

starts_on(sch,s)

on sch

on sch

continues_on(s,sch)

on s's sched

on sch

on(sch,s)

on sch

on the start scheduler

on(s,sch,closure)

mixed (see above)

on the start scheduler

The “start scheduler” is read from the receiver’s environment via get_start_scheduler (form 1) or get_completion_scheduler<set_value_t> of sndr's environment (form 2).

Completion signatures.

Form 1 (on(sched, sndr)): essentially sndr's completion signatures, with possible additional set_error_t completions from the two scheduling hops.

Form 2 (on(sndr, sched, closure)): the completion signatures of closure(continues_on(sndr, sched)) after the final transfer back, again with possible additional set_error_t completions from the scheduling hops.

If scheduling onto sched (or back) fails, an error completion is delivered on an unspecified execution agent.

Cancellation.

Cancellation flows through the scheduling hops normally; a stop request observed between hops typically results in set_stopped.

Example.

#include <stdexec/execution.hpp>

int main() {
  using namespace stdexec;

  auto gpu = get_parallel_scheduler();   // pretend: GPU

  // Compute on the GPU, but stay on the start scheduler afterwards:
  auto sndr =
    just(21)
    | on(gpu, then([](int x) { return x * 2; }));

  auto [v] = sync_wait(std::move(sndr)).value();
  (void)v;  // == 42; sync_wait sees the result on its starting context
}

See also

stdexec::schedule — the primitive that produces a schedule-sender

See also

stdexec::starts_on — begin on a scheduler and stay there

See also

stdexec::continues_on — transfer to a scheduler after a sender completes

Public Functions

template<scheduler _Scheduler, sender _Sender>
inline constexpr auto operator()(_Scheduler &&__sched, _Sender &&__sndr) const -> __well_formed_sender auto

Form 1: run __sndr on __sched, then return to the start scheduler.

Template Parameters:
Parameters:
  • __sched – The scheduler whose execution resource will host __sndr.

  • __sndr – The sender to run on __sched.

Returns:

A sender that, when connected and started, runs __sndr on __sched then transfers execution back to the start scheduler (taken from the receiver’s environment via get_start_scheduler) before forwarding __sndr's completion to the receiver.

template<sender _Sender, scheduler _Scheduler, __sender_adaptor_closure_for<_Sender> _Closure>
inline constexpr auto operator()(_Sender &&__sndr, _Scheduler &&__sched, _Closure &&__clsur) const -> __well_formed_sender auto

Form 2: run __sndr in place, hop to __sched, apply __clsur there, then hop back.

Template Parameters:
  • _Sender – A type satisfying the stdexec::sender concept.

  • _Scheduler – A type satisfying the stdexec::scheduler concept.

  • _Closure – A sender-adaptor closure suitable for chaining onto _Sender.

Parameters:
  • __sndr – The predecessor sender (runs on its own scheduler).

  • __sched – The scheduler to transition to before applying __clsur.

  • __clsur – The adaptor closure (e.g. then(...), bulk(...)) to apply on __sched.

Returns:

A sender that completes on the original completion scheduler of __sndr, with the result of __clsur(continues_on(__sndr, __sched)).

template<scheduler _Scheduler, __sender_adaptor_closure _Closure>
inline constexpr auto operator()(_Scheduler &&__sched, _Closure &&__clsur) const

Pipe form of Form 2: construct a sender-adaptor closure that, when applied to a sender, produces on(sndr, __sched, __clsur).

Template Parameters:
  • _Scheduler – A type satisfying the stdexec::scheduler concept.

  • _Closure – A sender-adaptor closure.

Parameters:
  • __sched – The scheduler to transition to.

  • __clsur – The adaptor closure to apply on __sched.

Returns:

A sender-adaptor closure capturing __sched and __clsur.

on_t const stdexec::on

The customization point object for the on sender adaptor.

on is an instance of on_t. See on_t for the full description, the distinction between on, starts_on, and continues_on, and usage examples.

Composition adaptors

These adaptors combine multiple senders into one. They are the building blocks for parallel and fan-out patterns.

when_all — run senders concurrently and concatenate values

Takes one or more senders and produces a sender that, when started, runs all of them concurrently and completes when every input has completed. The resulting value-completion is the concatenation of every input’s value datums. If any input fails or is stopped, the others are cancelled and the result is the first error/stopped completion observed. Each input must have exactly one value-completion shape; for senders that can succeed in more than one way, see when_all_with_variant — like when_all for multi-completion senders.

struct when_all_t

A variadic sender factory that runs multiple senders concurrently and completes when all of them have completed, concatenating their value datums.

when_all is the canonical parallel composition primitive in the sender model. You give it one or more senders; it returns a single sender that, when connected and started, starts all of the input senders concurrently. When every input has completed, when_all's sender completes with a value tuple that is the concatenation of every input’s value datums.

If any one input fails or is stopped, when_all requests stop on the others (via an internal inplace_stop_source) and completes with that error (or with set_stopped). This makes when_all naturally fail-fast: as soon as one branch has gone bad, the rest are asked to wind down.

auto s = stdexec::when_all(
  stdexec::just(1),
  stdexec::just(2.5),
  stdexec::just(std::string{"x"}));
auto [i, d, str] = stdexec::sync_wait(std::move(s)).value();
// i == 1, d == 2.5, str == "x"

See [exec.when.all] in the C++26 working draft for the normative specification.

Single value-completion requirement.

when_all requires that each input sender have exactly one set_value_t completion signature — otherwise the output value signature would be a combinatorial explosion of all possible concatenations. The constraint is enforced at connect time with a diagnostic pointing at when_all_with_variant_t, which lifts the restriction (at the cost of producing a variant per input).

Completion signatures.

Given inputs sndr_i with completion signatures

// For each i in 1..n:
set_value_t(Vi...)        // exactly one such signature per input
set_error_t(Eij)...       // zero or more per input
set_stopped_t()           // optional per input

the resulting sender has completion signatures:

set_value_t(V1..., V2..., ..., Vn...)   // concatenation of every input
set_error_t(Eij)...                     // union across all inputs
set_error_t(std::exception_ptr)         // added if any decay-copy may throw
set_stopped_t()                         // added if any input has it,
                                        //   or if cancellation may happen

The value datums of each input are decay-copied into the resulting sender’s state while it waits for the slowest input to finish; the final tuple is built from those decay-copies. If any decay-copy throws, the operation transitions to the error path.

Concurrency.

“Concurrently” here means the inputs are not sequenced relative to each otherwhen_all starts every child operation in a fold expression before returning from start. Whether they actually execute in parallel depends on the schedulers they’re attached to: independent starts_on / continues_on branches across different schedulers truly run in parallel; multiple just-rooted branches all complete synchronously inside when_all's start.

Error and stop semantics.

At most one completion is delivered to the downstream receiver. If several children produce errors or stopped completions, the first one observed wins; subsequent failures are dropped. Concretely:

  • First child to call set_error wins; its error becomes the result.

  • First child to call set_stopped wins (if no error has been seen).

  • On either, the internal stop source is signalled so the remaining children can wind down promptly.

Cancellation.

when_all chains the receiver’s stop token to its internal stop-source, so an outer stop request propagates to every child.

Example.

#include <stdexec/execution.hpp>

int main() {
  using namespace stdexec;
  auto sched = get_parallel_scheduler();

  auto pipeline = when_all(
    starts_on(sched, just(10) | then([](int x){ return x * 2; })),
    starts_on(sched, just(5)  | then([](int x){ return x + 1; })));

  auto [a, b] = sync_wait(std::move(pipeline)).value();
  // a == 20, b == 6, computed in parallel on `sched`
}

See also

stdexec::when_all_with_variant — for inputs with multiple value-completion shapes

See also

stdexec::transfer_when_allwhen_all + scheduler transfer (stdexec extension)

See also

stdexec::spawn_futurestart a sender eagerly and observe via a sender

Public Functions

template<sender... _Senders>
inline constexpr auto operator()(_Senders&&... __sndrs) const -> __well_formed_sender auto

Compose __sndrs… into a sender that completes when every input has completed.

Template Parameters:

_Senders – A pack of types each satisfying stdexec::sender. Must be non-empty. Each must have exactly one set_value_t completion signature in the ambient environment.

Parameters:

__sndrs – The senders to compose. Forwarded into the result.

Returns:

A sender that, when connected and started, concurrently starts every input and value-completes with the concatenation of the input’s value datums.

when_all_t const stdexec::when_all

The customization point object for the when_all sender factory.

when_all is an instance of when_all_t. See when_all_t for the full description, completion signatures, error/stop semantics, and a usage example.

when_all_with_variant — like when_all for multi-completion senders

Like when_all, but lifts the “exactly one value-completion per input” restriction by wrapping each input in stdexec::into_variant. Each output value-completion position is a std::variant<std::tuple<...>, ...> of that input’s possible shapes.

struct when_all_with_variant_t

A variadic sender factory like when_all that lifts the “exactly one value completion per input” restriction by wrapping each input in into_variant.

when_all_with_variant is for the case where one or more of the inputs can complete with more than one value-completion shape. Where when_all would refuse to compile, when_all_with_variant transforms each input via stdexec::into_variant first (collapsing that input’s multiple shapes into a single std::variant<std::tuple<...>, …> value), and then composes the results with the ordinary when_all rules.

// sndr_a value-completes with either int or std::string;
// sndr_b value-completes with float.
auto s = stdexec::when_all_with_variant(sndr_a, sndr_b);
auto [variant_a, variant_b] = stdexec::sync_wait(std::move(s)).value();
//   variant_a: std::variant<std::tuple<int>, std::tuple<std::string>>
//   variant_b: std::variant<std::tuple<float>>

See [exec.when.all] in the C++26 working draft for the normative specification (where when_all_with_variant is specified alongside when_all).

Equivalence.

when_all_with_variant(sndrs...) is specified as expression-equivalent to when_all(into_variant(sndrs)...) (after transform_sender), so all of when_all's concurrency, error, and cancellation semantics carry over unchanged.

See also

stdexec::when_all — single-value-completion variant

See also

stdexec::into_variant — the adaptor that lifts each input

Public Functions

template<sender... _Senders>
inline constexpr auto operator()(_Senders&&... __sndrs) const -> __well_formed_sender auto

Compose __sndrs… into a sender that completes when every input has completed, with each input wrapped in into_variant.

Template Parameters:

_Senders – A non-empty pack of types each satisfying stdexec::sender. Inputs may have multiple value-completion shapes.

Returns:

A sender equivalent to when_all(into_variant(__sndrs)...).

when_all_with_variant_t const stdexec::when_all_with_variant

The customization point object for the when_all_with_variant sender factory.

when_all_with_variant is an instance of when_all_with_variant_t. See when_all_with_variant_t for the full description.

into_variant — collapse multi-completion senders into one

Reshapes a sender that may value-complete in more than one way into a sender that always value-completes with a single std::variant-of-tuples datum. It is the building block behind when_all_with_variant and sync_wait_with_variant, and is occasionally useful on its own when a downstream algorithm requires the single-value-completion form.

struct into_variant_t

A pipeable sender adaptor that collapses a sender’s multiple value-completion signatures into a single std::variant-of-tuples value completion.

into_variant takes a sender whose set_value_t completion can be one of several shapes — e.g. set_value_t(int) or set_value_t(std::string) — and produces a sender that always value-completes with exactly one shape: a single std::variant<std::tuple<Vs1...>, std::tuple<Vs2...>, ...> datum whose alternatives match the input’s possible value completions.

This is the building block behind when_all_with_variant: it lifts any sender into the single-value-completion category that when_all and sync_wait require.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::into_variant(sndr);    // direct invocation
auto s2 = sndr | stdexec::into_variant(); // pipe syntax (no args)

Completion signatures.

Given a predecessor sender sndr with value-completion signatures

set_value_t(Vs1...)              // possibly several
set_value_t(Vs2...)
set_error_t(Es)...               // forwarded unchanged
set_stopped_t()                  // forwarded unchanged

the sender produced by into_variant(sndr) has completion signatures

set_value_t(std::variant<std::tuple<Vs1...>, std::tuple<Vs2...>, ...>)
set_error_t(Es)...               // unchanged
set_error_t(std::exception_ptr)  // added if variant construction may throw
set_stopped_t()                  // unchanged

When sndr value-completes with arguments matching Vsi… the resulting sender value-completes with a variant engaged on the corresponding alternative.

Exception behavior.

If constructing the variant alternative (which involves decay-copying the original value arguments) throws, the exception is delivered through set_error_t(std::exception_ptr).

Cancellation.

into_variant does not interact with the stop token; it only reshapes the value channel.

Example.

#include <stdexec/execution.hpp>

// Suppose sndr can value-complete with either int or std::string.
auto wrapped = stdexec::into_variant(sndr);
auto [v]     = stdexec::sync_wait(std::move(wrapped)).value();
//  v: std::variant<std::tuple<int>, std::tuple<std::string>>
std::visit([](auto&& tup) { use(tup); }, v);

See also

stdexec::when_all_with_variant — applies into_variant to each input internally

See also

stdexec::sync_wait_with_variant — variant-aware top-level wait

See also

stdexec::sync_wait — requires a single value-completion shape

Public Functions

template<sender _Sender>
inline constexpr auto operator()(_Sender &&__sndr) const -> __well_formed_sender auto

Construct a sender that value-completes with a std::variant of the possible value-completion tuples of __sndr.

Template Parameters:

_Sender – A type satisfying stdexec::sender.

Parameters:

__sndr – The predecessor sender. Forwarded into the result.

Returns:

A sender with a single set_value_t completion whose argument is a std::variant of std::tuple<Vs...> alternatives.

inline constexpr auto operator()() const noexcept

Construct a sender-adaptor closure that, when applied to a sender, produces into_variant(sndr).

This overload enables the pipe syntax: sndr | into_variant() is equivalent to into_variant(sndr).

Returns:

A sender-adaptor closure object.

into_variant_t const stdexec::into_variant

The customization point object for the into_variant sender adaptor.

into_variant is an instance of into_variant_t. See into_variant_t for the full description and a usage example.

transfer_when_all (deprecated)

Deprecated

This adaptor is not part of the C++26 working draft and is retained only for backwards compatibility. Write when_all(sndrs...) | continues_on(sch) instead — the behavior is identical.

struct transfer_when_all_t

Like when_all, but transfers execution to a scheduler before delivering the combined completion.

Deprecated:

transfer_when_all is deprecated. It is not part of the C++26 working draft and is retained only for backwards compatibility. Write when_all(sndrs...) | continues_on(sch) instead; the behavior is identical.

Composition of when_all with continues_on: transfer_when_all(sch, sndrs...) is expression-equivalent to continues_on(when_all(sndrs...), sch). The inputs run concurrently (wherever their respective schedulers run them), and once all have completed, the combined result is delivered on sch's execution resource.

See also

stdexec::when_all — without the scheduler transfer

See also

stdexec::continues_on — the underlying transfer primitive

Public Functions

template<scheduler _Scheduler, sender... _Senders>
inline constexpr auto operator()(_Scheduler __sched, _Senders&&... __sndrs) const -> __well_formed_sender auto

Compose __sndrs… and deliver the combined completion on __sched's execution resource.

Template Parameters:
constexpr transfer_when_all_t stdexec::transfer_when_all

The customization point object for the transfer_when_all sender factory.

Deprecated:

See transfer_when_all_t. Use when_all(...) | continues_on(sch) instead.

transfer_when_all_with_variant (deprecated)

Deprecated

This adaptor is not part of the C++26 working draft and is retained only for backwards compatibility. Write when_all_with_variant(sndrs...) | continues_on(sch) instead.

struct transfer_when_all_with_variant_t

Like when_all_with_variant, but transfers execution to a scheduler before delivering the combined completion.

Deprecated:

transfer_when_all_with_variant is deprecated. It is not part of the C++26 working draft and is retained only for backwards compatibility. Write when_all_with_variant(sndrs...) | continues_on(sch) instead; the behavior is identical.

Composition of when_all_with_variant and continues_on: transfer_when_all_with_variant(sch, sndrs...) is expression-equivalent to continues_on(when_all_with_variant(sndrs...), sch).

Public Functions

template<scheduler _Scheduler, sender... _Senders>
inline constexpr auto operator()(_Scheduler &&__sched, _Senders&&... __sndrs) const -> __well_formed_sender auto

Compose __sndrs… (each wrapped in into_variant) and deliver the combined completion on __sched's execution resource.

constexpr transfer_when_all_with_variant_t stdexec::transfer_when_all_with_variant

The customization point object for the transfer_when_all_with_variant sender factory.

Deprecated:

See transfer_when_all_with_variant_t. Use when_all_with_variant(...) | continues_on(sch) instead.

Parallel-loop adaptors

The bulk family invokes a callable over an integer index space, under a given execution policy. They are the parallel-loop primitives of the sender model — the entry point for GPU/parallel-scheduler customizations to take over.

bulk — apply a function to each index

Invokes f(i, vs...) for every i in [0, shape) under the given execution policy. Lowers to bulk_chunked internally so that domain customizations of bulk_chunked apply transparently. See bulk — apply a function over an index space for a worked example and policy discussion.

struct bulk_t : public stdexec::__bulk::__generic_bulk_t<bulk_t>

A pipeable sender adaptor that applies a function to each index in [0, shape) under a given execution policy.

bulk is the parallel-loop primitive of the sender model. You give it a sender, an execution policy (e.g. stdexec::par), an integral shape, and a callable; you get back a sender that, when started, invokes f(i, vs...) for every i in [0, shape) — where vs… are the predecessor’s value-completion datums. The execution policy controls whether the invocations may run in parallel.

See [exec.bulk] in the C++26 working draft.

The signature of the operator overloads (inherited from a detail base) is:

template <sender Sender, /* execution_policy */ Policy,
          integral Shape, copy_constructible Fun>
  auto operator()(Sender&& sndr, Policy&& pol,
                  Shape shape, Fun fun) const -> sender auto;   // direct

template <class Policy, integral Shape, copy_constructible Fun>
  auto operator()(Policy&& pol, Shape shape, Fun fun) const;    // closure

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::bulk(sndr, stdexec::par, 1024, fn);
auto s2 = sndr | stdexec::bulk(stdexec::par, 1024, fn);

Completion signatures.

bulk forwards the predecessor’s completion signatures, optionally adding set_error_t(std::exception_ptr) if invoking fn may throw (or if internal allocation may throw):

set_value_t(Vs...)               // forwarded unchanged from sndr
set_error_t(Es)...               // forwarded unchanged from sndr
set_error_t(std::exception_ptr)  // added if fn may throw
set_stopped_t()                  // forwarded unchanged (if present)

fn is invoked with the value-completion datums of sndr preserved across all invocations — every iteration sees the same vs

Execution policy.

The policy argument follows the <execution> conventions: stdexec::seq for sequenced execution, stdexec::par for permitted-parallel, stdexec::par_unseq for permitted parallel and vectorized. A custom scheduler’s domain may interpret these differently — e.g. a GPU domain may lower par to a CUDA kernel launch.

Implementation note: lowering to bulk_chunked **.**

Internally, bulk is implemented in terms of bulk_chunked_t — its transform_sender member rewrites bulk(sndr, pol, n, f) into a bulk_chunked over the same shape with f wrapped in a per-chunk loop. If a domain customizes bulk_chunked, bulk picks up that customization automatically.

See also

stdexec::bulk_chunked — explicit-chunk variant

See also

stdexec::bulk_unchunked — strict per-index variant (no chunking allowed)

See also

stdexec::when_all — concurrent composition without an index space

constexpr bulk_t stdexec::bulk

The customization point object for the bulk sender adaptor.

bulk is an instance of bulk_t. See bulk_t for the full description, the lowering to bulk_chunked, and a usage example.

bulk_chunked — apply a function per chunk of indices

Invokes f(begin, end, vs...) for chunks of the index space. The implementation may split into any number of chunks (one, shape, anything between).

struct bulk_chunked_t : public stdexec::__bulk::__generic_bulk_t<bulk_chunked_t>

A pipeable sender adaptor that invokes a function with chunked sub-ranges of an integer index space.

Where bulk_t passes a single index to its callable, bulk_chunked passes a half-open range [begin, end) covering some subset of [0, shape). The implementation may split [0, shape) into any number of chunks (including one chunk equal to the whole range, or shape chunks of one element each) — the only guarantee is that every index in [0, shape) is covered by exactly one chunk.

See [exec.bulk] in the C++26 working draft.

The signature of the operator overloads (inherited from a detail base) is:

template <sender Sender, /* execution_policy */ Policy,
          integral Shape, copy_constructible Fun>
  auto operator()(Sender&& sndr, Policy&& pol,
                  Shape shape, Fun fun) const -> sender auto;   // direct

template <class Policy, integral Shape, copy_constructible Fun>
  auto operator()(Policy&& pol, Shape shape, Fun fun) const;    // closure

The callable is invoked as fun(begin, end, vs...), where vs… are the predecessor’s value-completion datums (shared across all chunks).

When to use bulk_chunked vs. bulk **:**

Use bulk when the per-iteration body is small and the loop is the payload — bulk's lowering to bulk_chunked will let the runtime pick chunk sizes for you. Use bulk_chunked directly when the body benefits from per-chunk amortization (allocations, accumulators, vectorization setup) that you want to do once per chunk rather than once per index.

See also

stdexec::bulk — index-at-a-time variant (lowers to this)

See also

stdexec::bulk_unchunked — strict per-index variant (no chunking)

constexpr bulk_chunked_t stdexec::bulk_chunked

The customization point object for the bulk_chunked sender adaptor.

bulk_chunked is an instance of bulk_chunked_t. See bulk_chunked_t for the full description.

bulk_unchunked — apply a function per index, no chunking allowed

Like bulk but forbids the implementation from combining multiple indices into a single call. Use when per-iteration state (thread-local accumulators, per-index hardware resources) prevents batching.

struct bulk_unchunked_t : public stdexec::__bulk::__generic_bulk_t<bulk_unchunked_t>

A pipeable sender adaptor that invokes a function once per index in [0, shape), without permission to chunk.

bulk_unchunked has the same per-index invocation pattern as bulk_tfun(i, vs...) for every i — but explicitly forbids the implementation from combining multiple indices into a single call. Spec-recommended (but not required) practice is for each iteration to run on a distinct execution agent.

Use this only when the body of the loop has per-iteration state or synchronization that cannot be batched — e.g., per-thread-local accumulators, per-index hardware resources, observable side effects that must be one-per-index. For ordinary parallel loops, prefer bulk_t (which lowers to bulk_chunked and lets the runtime make chunk-size decisions).

See [exec.bulk] in the C++26 working draft.

The signature of the operator overloads (inherited from a detail base) is:

template <sender Sender, /* execution_policy */ Policy,
          integral Shape, copy_constructible Fun>
  auto operator()(Sender&& sndr, Policy&& pol,
                  Shape shape, Fun fun) const -> sender auto;   // direct

template <class Policy, integral Shape, copy_constructible Fun>
  auto operator()(Policy&& pol, Shape shape, Fun fun) const;    // closure

See also

stdexec::bulk — chunking-permitted index-at-a-time variant

See also

stdexec::bulk_chunked — explicit-chunk variant

constexpr bulk_unchunked_t stdexec::bulk_unchunked

The customization point object for the bulk_unchunked sender adaptor.

bulk_unchunked is an instance of bulk_unchunked_t. See bulk_unchunked_t for the full description and when to reach for it.

Stopped-channel translator adaptors

These adaptors don’t change the behavior of a pipeline; they translate one completion channel into another, exposing a friendlier shape to downstream code.

stopped_as_error — translate stopped into an error

Converts a set_stopped completion into a set_error completion carrying a caller-supplied error datum. The resulting sender no longer has a stopped channel.

struct stopped_as_error_t

A pipeable sender adaptor that converts a predecessor’s stopped completion into an error completion carrying a user-supplied error value.

stopped_as_error is a “translator” adaptor: it doesn’t change what happens in the value channel, but it rewrites a set_stopped completion into a set_error completion with a specific datum. This is useful when downstream code needs to distinguish a cancellation from a real error and you want the cancellation to be delivered as an error of a particular type (typically because the consumer can’t or won’t handle the stopped channel).

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::stopped_as_error(sndr, my_error);
auto s2 = sndr | stdexec::stopped_as_error(my_error);

Equivalence.

stopped_as_error(sndr, err) is implemented (and is observationally equivalent to) let_stopped(sndr, [err]{ return just_error(err); }). Use this adaptor whenever you would have written that pattern by hand — it is shorter, clearer at the call site, and the implementation can be specialized more efficiently in the future.

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(Vs...)    // forwarded unchanged
set_error_t(Es)...    // forwarded unchanged
set_stopped_t()       // consumed

the sender produced by stopped_as_error(sndr, err) has completion signatures

set_value_t(Vs...)              // forwarded unchanged
set_error_t(Es)...              // forwarded unchanged
set_error_t(std::decay_t<E>)    // the supplied error, decay-copied

The original set_stopped_t completion is replaced; the resulting sender will never deliver set_stopped.

Example.

using namespace stdexec;

auto sndr = just_stopped()
          | stopped_as_error(std::runtime_error{"cancelled"});

try {
  sync_wait(std::move(sndr));    // throws std::runtime_error
} catch (std::runtime_error const& e) {
  // e.what() == "cancelled"
}

See also

stdexec::stopped_as_optional — convert stopped into a value-channel std::nullopt

See also

stdexec::upon_stopped — handle stopped synchronously

See also

stdexec::let_stopped — handle stopped with a sender-returning callback

Public Functions

template<sender _Sender, __movable_value _Error>
inline constexpr auto operator()(_Sender &&__sndr, _Error __err) const -> __well_formed_sender auto

Construct a sender that translates __sndr's set_stopped completion into a set_error completion carrying __err.

Template Parameters:
  • _Sender – A type satisfying stdexec::sender.

  • _Error – A decayed, move-constructible error datum type (satisfying the internal __movable_value concept).

Parameters:
  • __sndr – The predecessor sender. Forwarded into the result.

  • __err – The error datum to deliver if __sndr is stopped. Decay-copied into the resulting sender.

template<__movable_value _Error>
inline constexpr auto operator()(_Error __err) const noexcept(__nothrow_move_constructible<_Error>)

Construct a sender-adaptor closure for the pipe form.

sndr | stopped_as_error(__err) is equivalent to stopped_as_error(sndr, __err).

stopped_as_error_t const stdexec::stopped_as_error

The customization point object for the stopped_as_error sender adaptor.

stopped_as_error is an instance of stopped_as_error_t. See stopped_as_error_t for the full description and a usage example.

stopped_as_optional — translate stopped into a value-channel nullopt

Converts a set_stopped completion into a value-channel std::optional<T>{std::nullopt}, wrapping the predecessor’s value in std::optional<T>. Requires the predecessor to have exactly one value completion with one argument.

struct stopped_as_optional_t

A pipeable sender adaptor that converts a predecessor’s stopped completion into a value-channel std::nullopt, wrapping the value-completion datum in a std::optional.

stopped_as_optional is the value-channel mirror of stopped_as_error_t. Where stopped_as_error turns cancellation into an error, stopped_as_optional turns cancellation into a “no value” signal on the value channel. The resulting sender value-completes with a std::optional<T>: engaged if the predecessor produced a value, disengaged if the predecessor was stopped.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::stopped_as_optional(sndr);
auto s2 = sndr | stdexec::stopped_as_optional();

Use this when downstream code prefers branching on a std::optional (a familiar idiom) over branching on an empty std::optional<std::tuple<...>> from sync_wait or handling the set_stopped channel via an adaptor.

Single value-completion requirement.

stopped_as_optional requires the predecessor to have exactly one value-completion signature with exactly one argument. (How would we wrap multiple values in a single std::optional

?) If the predecessor has multiple value completions, or zero/multiple value arguments, the program is ill-formed with a focused diagnostic (“the sender must have exactly one value completion with one

argument”).

Completion signatures.

Given a predecessor sender sndr with completion signatures

set_value_t(T)        // exactly one value-completion with one argument
set_error_t(Es)...    // zero or more
set_stopped_t()       // consumed

the sender produced by stopped_as_optional(sndr) has completion signatures

set_value_t(std::optional<std::decay_t<T>>)
set_error_t(Es)...                  // forwarded unchanged
set_error_t(std::exception_ptr)     // added if wrapping may throw
                                    // (no set_stopped_t in the output)

The original set_stopped_t completion is consumed: the resulting sender will never deliver set_stopped.

Exception behavior.

If constructing the std::optional from the predecessor’s value throws (e.g., the value type’s copy constructor throws), the exception is delivered through set_error_t(std::exception_ptr).

Example.

using namespace stdexec;

auto sndr = just(42) | stopped_as_optional();
auto [opt] = sync_wait(std::move(sndr)).value();
// opt == std::optional<int>{42}

auto sndr2 = just_stopped() | stopped_as_optional();
// ...but to make stopped_as_optional well-formed here we need to give
// the predecessor a value-shape; in practice you compose it on a
// sender that may either succeed or be stopped:
auto pipeline =
  /* some sender that produces an int or is stopped */
  | stopped_as_optional();

See also

stdexec::stopped_as_error — convert stopped into an error

See also

stdexec::upon_stopped — handle stopped synchronously

See also

stdexec::let_stopped — handle stopped with a sender-returning callback

See also

stdexec::sync_wait — also uses an outer std::optional to signal stop

Public Functions

template<sender _Sender>
inline constexpr auto operator()(_Sender &&__sndr) const -> __well_formed_sender auto

Construct a sender that wraps __sndr's value completion in a std::optional and reroutes set_stopped to a disengaged optional on the value channel.

Template Parameters:

_Sender – A type satisfying stdexec::sender whose completion signatures include exactly one set_value_t(T) signature.

Parameters:

__sndr – The predecessor sender. Forwarded into the result.

inline auto operator()() const noexcept

Construct a sender-adaptor closure for the pipe form.

sndr | stopped_as_optional() is equivalent to stopped_as_optional(sndr). The empty parentheses are required by the pipe-closure convention; there are no captured arguments.

stopped_as_optional_t const stdexec::stopped_as_optional

The customization point object for the stopped_as_optional sender adaptor.

stopped_as_optional is an instance of stopped_as_optional_t. See stopped_as_optional_t for the full description and a usage example.

Environment adaptors

write_env — inject values into the environment

Augments the environment seen by a predecessor sender with additional queries. The inverse of read_env: read_env exposes environment values into the value channel, write_env injects environment values into a child sender’s environment.

__write_env_t const stdexec::write_env

A pipeable sender adaptor that augments the environment seen by a predecessor sender with additional queries.

write_env is the inverse of read_env. Where read_env reads a value from the receiver’s environment and exposes it on the value channel, write_env injects values into the environment a child sender sees — overriding or augmenting what the eventual receiver exposes.

You give it a sender and an environment (typically built with stdexec::env and stdexec::prop); you get back a sender that, when connected, presents the union of the supplied environment and the connected receiver’s environment to its predecessor. Anything the predecessor reaches for via get_env / read_env sees the merged view.

Both call syntaxes are supported (the second is the pipeable form):

auto s1 = stdexec::write_env(sndr, env);
auto s2 = sndr | stdexec::write_env(env);

The supplied environment shadows the receiver’s environment for any query the supplied environment can answer; queries it cannot answer fall through to the receiver’s environment unchanged.

Common uses.

  • Injecting a stop token: sndr | write_env(prop{get_stop_token, my_token}) so a sub-pipeline observes a different cancellation signal than the outer pipeline.

  • Supplying an allocator: sndr | write_env(prop{get_allocator, my_alloc}) so child operations allocate via my_alloc.

  • Hooking domain customization: a custom scheduler may inject its domain into the environment for senders that don’t have a scheduler in their chain.

Completion signatures.

write_env preserves the predecessor’s completion signatures unchanged. (The predecessor may compute different signatures depending on what’s in its environment — so the supplied env may influence which signatures the framework computes — but write_env does not itself add or remove any.)

Example.

using namespace stdexec;

auto inner_sndr = read_env(get_stop_token)
                | then([](auto tok) { return tok.stop_requested(); });

stop_source src;
auto pipeline = inner_sndr
              | write_env(prop{get_stop_token, src.get_token()});

auto [requested] = sync_wait(std::move(pipeline)).value();
// requested == src.stop_requested(), regardless of the outer
// pipeline's stop token.

See also

stdexec::read_env — read a value from the environment

See also

stdexec::env — construct an environment from properties

See also

stdexec::prop — bind a query CPO to a value

See also

stdexec::get_env — the CPO that exposes the merged environment

Sender Consumers

A sender consumer takes a sender, connects it to a receiver, and starts the resulting operation. Consumers sit at the tail of a sender pipeline — they are the point at which asynchronous work actually runs. They fall into two broad families:

See Picking a consumer for a side-by-side comparison and guidance on which consumer to reach for.

sync_wait — block until the sender completes

Synchronously waits for a single-value-completion sender to complete on the calling thread. Returns an engaged std::optional<std::tuple<...>> on success, an empty optional on stopped, and throws on error.

struct sync_wait_t

A sender consumer that synchronously blocks the calling thread until a sender completes and returns its result.

sync_wait is the bridge from the asynchronous sender world back into synchronous code. You give it a sender; it connects the sender to a built-in receiver, starts the resulting operation, then drives an internal run_loop on the calling thread until the operation completes. The result is returned as a std::optional of a tuple of the value-completion datums.

This is the most common way to “run” a sender in a top-level program or a test — it’s what you reach for in a main() or when synchronously waiting on a single sub-pipeline. For fire-and-forget execution, prefer exec::start_detached or stdexec::spawn.

auto [v] = stdexec::sync_wait(stdexec::just(42)).value();
// v == 42

See [exec.sync.wait] in the C++26 working draft for the normative specification.

Completion behavior.

Given an input sender sndr that, in some environment, completes with exactly one of:

Sender completion

What sync_wait does

set_value_t(Vs...)

Returns std::optional<std::tuple<Vs...>> engaged.

set_error_t(std::exception_ptr)

Rethrows the exception via std::rethrow_exception.

set_error_t(std::error_code)

Throws std::system_error(error_code).

set_error_t(E)

Throws E directly.

set_stopped_t()

Returns an empty (disengaged) std::optional.

Single-value-completion requirement.

sync_wait mandates that its argument sender have exactly one set_value_t completion signature. A sender that can succeed in more than one way (e.g. just(1) | when_all(just(std::string{"x"})) yielding two distinct tuples) requires sync_wait_with_variant instead. The static assertion in sync_wait will point this out at compile time, with a hint to use the variant form.

Delegation scheduler.

The internal run_loop is exposed via get_delegation_scheduler on the receiver’s environment, so senders that need to enqueue work back onto the waiting thread (e.g. continuations after an I/O wait) can do so safely. This is what enables algorithms like continues_on to return execution to the calling thread of sync_wait.

When to use sync_wait **:**

  • On any thread that participates in an event loop or executor — you will block it. sync_wait is for top-level synchronization (main, tests, leaf utilities), not pipeline composition.

  • When you don’t need the result. Use exec::start_detached or stdexec::spawn for fire-and-forget.

See also

stdexec::sync_wait_with_variantsync_wait for multi-completion senders

See also

exec::start_detached — fire-and-forget consumer (no result)

See also

stdexec::spawn — fire-and-forget into a scope

See also

stdexec::spawn_futurespawn into a scope and observe via a sender

Public Functions

template<stdexec::sender_in<stdexec::__sync_wait::__env> _CvSender>
inline auto operator()(_CvSender &&__sndr) const

Connect __sndr to an internal receiver, start the operation, and drive a run_loop until completion.

Template Parameters:

_CvSender – A type satisfying stdexec::sender_in for the built-in sync_wait environment.

Parameters:

__sndr – The sender to drive to completion. Must have exactly one set_value_t completion signature.

Throws:

The – error datum, if __sndr completes with set_error (rethrown via std::rethrow_exception for std::exception_ptr, via std::system_error for std::error_code, or directly otherwise).

Returns:

std::optional<std::tuple<Vs...>> where Vs… are the value-completion datum types of __sndr. The optional is engaged on set_value, disengaged on set_stopped.

Pre:

__sndr must have exactly one set_value_t completion signature, otherwise the program is ill-formed with a diagnostic pointing at sync_wait_with_variant.

sync_wait_t const stdexec::sync_wait

The customization point object for the sync_wait sender consumer.

sync_wait is an instance of sync_wait_t. See sync_wait_t for the full description, completion-behavior table, and a usage example.

sync_wait_with_variant — block until a multi-completion sender completes

Like sync_wait but for senders that may complete with more than one value-completion shape. Returns an engaged std::optional<std::variant<std::tuple<...>...>> on success.

struct sync_wait_with_variant_t

A sender consumer that synchronously blocks the calling thread until a multi-value-completion sender completes, returning the result as a variant of tuples.

sync_wait_with_variant is the multi-completion sibling of sync_wait_t. A sender that can succeed in more than one way — for example, an algorithm that may complete with either an int or a std::string — cannot be passed to sync_wait, because the latter returns a single fixed tuple type. sync_wait_with_variant accepts such senders and returns the result as a std::variant of all the possible value-tuple shapes.

// sndr completes with either set_value_t(int) or set_value_t(std::string).
auto opt = stdexec::sync_wait_with_variant(std::move(sndr));
if (opt) {
  std::visit([](auto&& tup) {
    // tup is either std::tuple<int> or std::tuple<std::string>.
  }, *opt);
}

See [exec.sync.wait.var] in the C++26 working draft for the normative specification.

Completion behavior.

Given an input sender sndr with value-completion signatures set_value_t(Vs1...), set_value_t(Vs2...), ..., the return type is

std::optional<std::variant<std::tuple<Vs1...>, std::tuple<Vs2...>, ...>>

The handling of set_error_t and set_stopped_t matches sync_wait_t : errors are thrown, set_stopped yields a disengaged optional.

When to use sync_wait_with_variant vs. sync_wait **:** Use sync_wait when the sender has exactly one value-completion shape; use sync_wait_with_variant otherwise. sync_wait's static assertion will steer you here if needed.

See also

stdexec::sync_wait — for single-value-completion senders

See also

stdexec::into_variant — adaptor that collapses multi-completion senders into a variant

Public Functions

template<stdexec::sender_in<stdexec::__sync_wait::__env> _CvSender>
inline auto operator()(_CvSender &&__sndr) const -> decltype(auto)

Connect __sndr, start the operation, drive a run_loop until completion, and return the result as a variant of tuples.

Template Parameters:

_CvSender – A type satisfying stdexec::sender_in for the sync_wait environment.

Parameters:

__sndr – The sender to drive to completion. May have any number of set_value_t completion signatures.

Throws:

The – error datum, if __sndr completes with set_error, using the same rules as sync_wait_t.

Returns:

std::optional<std::variant<std::tuple<Vs1...>, …>> engaged on set_value, disengaged on set_stopped.

sync_wait_with_variant_t const stdexec::sync_wait_with_variant

The customization point object for the sync_wait_with_variant sender consumer.

sync_wait_with_variant is an instance of sync_wait_with_variant_t. See sync_wait_with_variant_t for the full description and a usage example.

start_detached (extension) — fire and forget

Eagerly starts a sender and discards its result. The operation state is heap-allocated and cleans itself up on completion. stdexec extension — not part of the C++26 working draft. For the standardized scope-tracked equivalent, see spawn.

struct start_detached_t

A sender consumer that eagerly starts a sender and forgets it.

start_detached connects its argument sender to a built-in receiver and starts the resulting operation immediately, allocating the operation state on the heap (using an allocator from the optional environment) so it can outlive the call. The completion of the sender deallocates the operation state and discards the result; nothing is returned to the caller.

Use start_detached for top-level fire-and-forget work that has no caller waiting on its result and no enclosing async scope — for example, kicking off a logging or telemetry pipeline from main(), or a one-shot background task at program startup. For fire-and-forget work that should be tracked by a scope (so the scope can be joined at shutdown), prefer stdexec::spawn. For top-level waiting, use stdexec::sync_wait.

exec::start_detached(stdexec::just(42) | stdexec::then([](int x) {
  std::println("background work produced {}", x);
}));

Completion behavior.

The sender must complete via set_value or set_stopped — both are accepted and the result is discarded. The sender must not complete via set_error: there is no caller to deliver the error to, and an error completion is therefore considered a contract violation. The implementation enforces this with a static assertion when possible; if the sender’s completion signatures include set_error_t the program is ill-formed.

Allocator support.

The two-argument overload accepts an environment from which an allocator can be queried (via stdexec::get_allocator). That allocator is used to allocate the operation state, so callers can avoid the default global new for hot paths.

Cancellation.

start_detached does not arrange for cancellation of the spawned work. If the operation observes a stop token via the environment, it can self-cancel; otherwise the work runs to natural completion.

See also

stdexec::sync_wait — top-level synchronous wait that returns the result

See also

stdexec::spawn — fire-and-forget into a scope (standardized in C++26)

See also

stdexec::spawn_future — spawn into a scope and observe via a sender

Note

start_detached is an stdexec extension. It is not part of the C++26 working draft. The standardized way to spawn fire-and-forget work is stdexec::spawn (see [exec.spawn]), which requires an async scope to take ownership of the operation.

Public Functions

template<stdexec::sender_in<stdexec::__root_env> _Sender>
inline void operator()(_Sender &&__sndr) const

Eagerly start __sndr; allocate its operation state on the heap with the default allocator.

Template Parameters:

_Sender – A sender type with no set_error_t completions.

Parameters:

__sndr – The sender to launch. Forwarded into the heap-allocated operation state.

Pre:

__sndr must not be able to complete with set_error.

template<class _Env, stdexec::sender_in<stdexec::__as_root_env_t<_Env>> _Sender>
inline void operator()(_Sender &&__sndr, _Env &&__env) const

Eagerly start __sndr; use the allocator from __env to allocate the operation state.

Template Parameters:
  • _Env – An environment type. Queried with stdexec::get_allocator for an allocator; falls back to std::allocator if absent.

  • _Sender – A sender type with no set_error_t completions.

Parameters:
  • __sndr – The sender to launch.

  • __env – The environment used both for the allocator query and for the receiver’s environment.

Pre:

__sndr must not be able to complete with set_error.

constexpr start_detached_t experimental::execution::start_detached

The customization point object for the start_detached sender consumer.

start_detached is an instance of start_detached_t. See start_detached_t for the full description and a usage example.

spawn — fire and forget into an async scope

Eagerly starts a sender and ties its lifetime to a given async scope. The argument sender must not be able to complete with set_error. The standardized way to launch fire-and-forget work whose lifetime should be tracked.

Warning

doxygenstruct: Cannot find class “stdexec::spawn_t” in doxygen xml output for project “stdexec” from directory: /home/runner/work/stdexec/stdexec/build/docs/build/doxygen/xml

constexpr spawn_t stdexec::spawn

The customization point object for the spawn sender consumer.

spawn is an instance of spawn_t. See spawn_t for the full description, scope semantics, and a usage example.

spawn_future — fire and observe via a sender

Like spawn but additionally returns a sender that completes when the spawned operation completes. The returned sender is a one-shot observer of work that is already running, not a re-runnable handle.

Warning

doxygenstruct: Cannot find class “stdexec::spawn_future_t” in doxygen xml output for project “stdexec” from directory: /home/runner/work/stdexec/stdexec/build/docs/build/doxygen/xml

constexpr spawn_future_t stdexec::spawn_future

The customization point object for the spawn_future sender consumer.

spawn_future is an instance of spawn_future_t. See spawn_future_t for the full description, the eager-start semantics, and a usage example.

Utilities

TODO: Add utilities section