Files
cppdraft_translate/cppdraft/exec/snd.md
2025-10-25 03:02:53 +03:00

198 KiB
Raw Blame History

[exec.snd]

33 Execution control library [exec]

33.9 Senders [exec.snd]

33.9.1 General [exec.snd.general]

1

#

Subclauses [exec.factories] and [exec.adapt] define customizable algorithms that return senders.

Each algorithm has a default implementation.

Let sndr be the result of an invocation of such an algorithm or an object equal to the result ([concepts.equality]), and let Sndr be decltype((sndr)).

Let rcvr be a receiver of type Rcvr with associated environment env of type Env such that sender_to<Sndr, Rcvr> is true.

For the default implementation of the algorithm that produced sndr, connecting sndr to rcvr and starting the resulting operation state ([exec.async.ops]) necessarily results in the potential evaluation ([basic.def.odr]) of a set of completion operations whose first argument is a subexpression equal to rcvr.

Let Sigs be a pack of completion signatures corresponding to this set of completion operations, and let CS be the type of the expression get_completion_signatures<Sndr, Env>().

Then CS is a specialization of the class template completion_signatures ([exec.cmplsig]), the set of whose template arguments is Sigs.

If none of the types in Sigs are dependent on the type Env, then the expression get_completion_signatures() is well-formed and its type is CS.

If a user-provided implementation of the algorithm that produced sndr is selected instead of the default:

  • (1.1)

    Any completion signature that is in the set of types denoted by completion_signatures_of_t<Sndr, Env> and that is not part of Sigs shall correspond to error or stopped completion operations, unless otherwise specified.

  • (1.2)

    If none of the types in Sigs are dependent on the type Env, thencompletion_signatures_of_t andcompletion_signatures_of_t<Sndr, Env> shall denote the same type.

33.9.2 Exposition-only entities [exec.snd.expos]

1

#

Subclause [exec.snd] makes use of the following exposition-only entities.

2

#

For a queryable object env,FWD-ENV(env) is an expression whose type satisfies queryable such that for a query object q and a pack of subexpressions as, the expression FWD-ENV(env).query(q, as...) is ill-formed if forwarding_query(q) is false; otherwise, it is expression-equivalent to env.query(q, as...).

The type FWD-ENV-T(Env) isdecltype(FWD-ENV(declval())).

3

#

For a query object q and a subexpression v,MAKE-ENV(q, v) is an expression env whose type satisfies queryable such that the result of env.query(q) has a value equal to v ([concepts.equality]).

Unless otherwise stated, the object to which env.query(q) refers remains valid while env remains valid.

4

#

For two queryable objects env1 and env2, a query object q, and a pack of subexpressions as,JOIN-ENV(env1, env2) is an expression env3 whose type satisfies queryable such that env3.query(q, as...) is expression-equivalent to:

env1.query(q, as...) if that expression is well-formed,

otherwise, env2.query(q, as...) if that expression is well-formed,

otherwise, env3.query(q, as...) is ill-formed.

5

#

The results of FWD-ENV, MAKE-ENV, and JOIN-ENV can be context-dependent; i.e., they can evaluate to expressions with different types and value categories in different contexts for the same arguments.

6

#

For a scheduler sch,SCHED-ATTRS(sch) is an expression o1 whose type satisfies queryable such that o1.query(get_completion_scheduler) is an expression with the same type and value as sch where Tag is one of set_value_t or set_stopped_t, and such that o1.query(get_domain) is expression-equivalent tosch.query(get_domain).

SCHED-ENV(sch) is an expression o2 whose type satisfies queryable such that o2.query(get_scheduler) is a prvalue with the same type and value as sch, and such that o2.query(get_domain) is expression-equivalent tosch.query(get_domain).

7

#

For two subexpressions rcvr and expr,SET-VALUE(rcvr, expr) is expression-equivalent to(expr, set_value(std::move(rcvr))) if the type of expr is void; otherwise, set_value(std::move(rcvr), expr).

TRY-EVAL(rcvr, expr) is equivalent to:try { expr;} catch(...) { set_error(std::move(rcvr), current_exception());} if expr is potentially-throwing; otherwise, expr.

TRY-SET-VALUE(rcvr, expr) isTRY-EVAL(rcvr, SET-VALUE(rcvr, expr)) except that rcvr is evaluated only once.

🔗

template<class Default = default_domain, class Sndr> constexpr auto completion-domain(const Sndr& sndr) noexcept;

8

#

COMPL-DOMAIN(T) is the type of the expressionget_domain(get_completion_scheduler(get_env(sndr))).

9

#

Effects: If all of the typesCOMPL-DOMAIN(set_value_t),COMPL-DOMAIN(set_error_t), and
COMPL-DOMAIN(set_stopped_t) are ill-formed,completion-domain(sndr) is a default-constructed prvalue of type Default.

Otherwise, if they all share a common type ([meta.trans.other]) (ignoring those types that are ill-formed), then completion-domain(sndr) is a default-constructed prvalue of that type.

Otherwise, completion-domain(sndr) is ill-formed.

🔗

template<class Tag, class Env, class Default> constexpr decltype(auto) query-with-default( Tag, const Env& env, Default&& value) noexcept(see below);

10

#

Let e be the expression Tag()(env) if that expression is well-formed; otherwise, it is static_cast(std::forward(value)).

11

#

Returns: e.

12

#

Remarks: The expression in the noexcept clause is noexcept(e).

🔗

template<class Sndr> constexpr auto get-domain-early(const Sndr& sndr) noexcept;

13

#

Effects: Equivalent to:return Domain(); where Domain is the decayed type of the first of the following expressions that is well-formed:

get_domain(get_env(sndr))

completion-domain(sndr)

default_domain()

🔗

template<class Sndr, class Env> constexpr auto get-domain-late(const Sndr& sndr, const Env& env) noexcept;

14

#

Effects: Equivalent to:

If sender-for<Sndr, continues_on_t> is true, thenreturn Domain(); where Domain is the type of the following expression:[] {auto [_, sch, _] = sndr; return query-with-default(get_domain, sch, default_domain());}(); [Note 1: The continues_on algorithm works in tandem with schedule_from ([exec.schedule.from]) to give scheduler authors a way to customize both how to transition onto (continues_on) and off of (schedule_from) a given execution context. Thus, continues_on ignores the domain of the predecessor and uses the domain of the destination scheduler to select a customization, a property that is unique to continues_on. That is why it is given special treatment here. — end note]

Otherwise,return Domain(); where Domain is the first of the following expressions that is well-formed and whose type is not void:

get_domain(get_env(sndr))

completion-domain(sndr)

get_domain(env)

get_domain(get_scheduler(env))

default_domain()

15

#

template<callable Fun>requires is_nothrow_move_constructible_vstruct emplace-from { Fun fun; // exposition onlyusing type = call-result-t; constexpr operator type() && noexcept(nothrow-callable) {return std::move(fun)(); }constexpr type operator()() && noexcept(nothrow-callable) {return std::move(fun)(); }};

[Note 2:

emplace-from is used to emplace non-movable types into tuple, optional, variant, and similar types.

— end note]

16

#

struct on-stop-request { inplace_stop_source& stop-src; // exposition onlyvoid operator()() noexcept { stop-src.request_stop(); }};

17

#

template<class T0, class T1, …, class Tn>struct product-type { // exposition only T0 t0; // exposition only T1 t1; // exposition only ⋮ Tn tn; // exposition onlytemplate<size_t I, class Self>constexpr decltype(auto) get(this Self&& self) noexcept; // exposition onlytemplate<class Self, class Fn>constexpr decltype(auto) apply(this Self&& self, Fn&& fn) // exposition onlynoexcept(see below);};

18

#

[Note 3:

product-type is presented here in pseudo-code form for the sake of exposition.

It can be approximated in standard C++ with a tuple-like implementation that takes care to keep the type an aggregate that can be used as the initializer of a structured binding declaration.

— end note]

[Note 4:

An expression of type product-type is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).

— end note]

🔗

template<size_t I, class Self> constexpr decltype(auto) get(this Self&& self) noexcept;

19

#

Effects: Equivalent to:auto& [...ts] = self;return std::forward_like(ts...[I]);

template<class Self, class Fn>constexpr decltype(auto) apply(this Self&& self, Fn&& fn) noexcept(see below);

20

#

Constraints: The expression in the return statement below is well-formed.

21

#

Effects: Equivalent to:auto& [...ts] = self;return std::forward(fn)(std::forward_like(ts)...);

22

#

Remarks: The expression in the noexcept clause is true if the return statement above is not potentially throwing; otherwise, false.

23

#

Let valid-specialization be the following concept:namespace std::execution {template<template<class...> class T, class... Args>concept valid-specialization = // exposition onlyrequires { typename T<Args...>; };}

🔗

template<class Tag, class Data = see below, class... Child> constexpr auto make-sender(Tag tag, Data&& data, Child&&... child);

24

#

Mandates: The following expressions are true:

semiregular

movable-value

(sender && ...)

dependent_sender || sender_in, where Sndr is basic-sender<Tag, Data,
Child...> as defined below. Recommended practice: If evaluation of sender_in results in an uncaught exception from the evaluation of get_completion_signatures(), the implementation should include information about that exception in the resulting diagnostic.

25

#

Returns: A prvalue of type basic-sender<Tag, decay_t, decay_t...> that has been direct-list-initialized with the forwarded arguments, where basic-sender is the following exposition-only class template except as noted below.

26

#

Remarks: The default template argument for the Data template parameter denotes an unspecified empty trivially copyable class type that models semiregular.

namespace std::execution {templateconcept completion-tag = // exposition onlysame_as<Tag, set_value_t> || same_as<Tag, set_error_t> || same_as<Tag, set_stopped_t>; struct default-impls { // exposition onlystatic constexpr auto get-attrs = see below; // exposition onlystatic constexpr auto get-env = see below; // exposition onlystatic constexpr auto get-state = see below; // exposition onlystatic constexpr auto start = see below; // exposition onlystatic constexpr auto complete = see below; // exposition onlytemplate<class Sndr, class... Env>static consteval void check-types(); // exposition only}; templatestruct impls-for : default-impls {}; // exposition onlytemplate<class Sndr, class Rcvr> // exposition onlyusing state-type = decay_t<call-result-t<decltype(impls-for<tag_of_t>::get-state), Sndr, Rcvr&>>; template<class Index, class Sndr, class Rcvr> // exposition onlyusing env-type = call-result-t<decltype(impls-for<tag_of_t>::get-env), Index, state-type<Sndr, Rcvr>&, const Rcvr&>; templateusing data-type = decltype(declval().template get<1>()); // exposition onlytemplate<class Sndr, size_t I = 0>using child-type = decltype(declval().template get<I+2>()); // exposition onlytemplateusing indices-for = remove_reference_t::indices-for; // exposition onlytemplate<class Sndr, class Rcvr>struct basic-state { // exposition only**basic-state(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below): rcvr(std::move(rcvr)) , state(impls-for<tag_of_t>::get-state(std::forward(sndr), rcvr)) { } Rcvr rcvr; // exposition only**state-type<Sndr, Rcvr> state; // exposition only}; template<class Sndr, class Rcvr, class Index>requires valid-specialization<env-type, Index, Sndr, Rcvr>struct basic-receiver { // exposition onlyusing receiver_concept = receiver_t; using tag-t = tag_of_t; // exposition onlyusing state-t = state-type<Sndr, Rcvr>; // exposition onlystatic constexpr const auto& complete = impls-for<tag-t>::complete; // exposition onlytemplate<class... Args>requires callable<decltype(complete), Index, state-t&, Rcvr&, set_value_t, Args...>void set_value(Args&&... args) && noexcept {complete(Index(), op->state, op->rcvr, set_value_t(), std::forward(args)...); }templaterequires callable<decltype(complete), Index, state-t&, Rcvr&, set_error_t, Error>void set_error(Error&& err) && noexcept {complete(Index(), op->state, op->rcvr, set_error_t(), std::forward(err)); }void set_stopped() && noexceptrequires callable<decltype(complete), Index, state-t&, Rcvr&, set_stopped_t> {complete(Index(), op->state, op->rcvr, set_stopped_t()); }auto get_env() const noexcept -> env-type<Index, Sndr, Rcvr> {return impls-for::get-env(Index(), op->state, op->rcvr); }basic-state<Sndr, Rcvr>* op; // exposition only}; constexpr auto connect-all = see below; // exposition onlytemplate<class Sndr, class Rcvr>using connect-all-result = call-result-t< // exposition onlydecltype(connect-all), basic-state<Sndr, Rcvr>*, Sndr, indices-for>; template<class Sndr, class Rcvr>requires valid-specialization<state-type, Sndr, Rcvr> &&valid-specialization<connect-all-result, Sndr, Rcvr>struct basic-operation : basic-state<Sndr, Rcvr> { // exposition onlyusing operation_state_concept = operation_state_t; using tag-t = tag_of_t; // exposition only**connect-all-result<Sndr, Rcvr> inner-ops; // exposition only**basic-operation(Sndr&& sndr, Rcvr&& rcvr) noexcept(see below) // exposition only: basic-state<Sndr, Rcvr>(std::forward(sndr), std::move(rcvr)), inner-ops(connect-all(this, std::forward(sndr), indices-for())){}void start() & noexcept {auto& [...ops] = inner-ops; impls-for::start(this->state, this->rcvr, ops...); }}; template<class Tag, class Data, class... Child>struct basic-sender : product-type<Tag, Data, Child...> { // exposition onlyusing sender_concept = sender_t; using indices-for = index_sequence_for<Child...>; // exposition onlydecltype(auto) get_env() const noexcept {auto& [_, data, ...child] = *this; return impls-for::get-attrs(data, child...); }template<decays-to<basic-sender> Self, receiver Rcvr>auto connect(this Self&& self, Rcvr rcvr) noexcept(see below)-> basic-operation<Self, Rcvr> {return {std::forward(self), std::move(rcvr)}; }template<decays-to<basic-sender> Self, class... Env>static constexpr auto get_completion_signatures(); };}

27

#

It is unspecified whether a specialization of basic-sender is an aggregate.

28

#

An expression of type basic-sender is usable as the initializer of a structured binding declaration ([dcl.struct.bind]).

29

#

The expression in the noexcept clause of the constructor of basic-state isis_nothrow_move_constructible_v &&nothrow-callable<decltype(impls-for<tag_of_t>::get-state), Sndr, Rcvr&> &&(same_as<state-type<Sndr, Rcvr>, get-state-result> || is_nothrow_constructible_v<state-type<Sndr, Rcvr>, get-state-result>) where get-state-result iscall-result-t<decltype(impls-for<tag_of_t>::get-state), Sndr, Rcvr&>.

30

#

The object connect-all is initialized with a callable object equivalent to the following lambda:

🔗

[]<class Sndr, class Rcvr, size_t... Is>( basic-state<Sndr, Rcvr>* op, Sndr&& sndr, index_sequence<Is...>) noexcept(see below) -> decltype(auto) { auto& [_, data, ...child] = sndr; return product-type{connect( std::forward_like<Sndr>(child), basic-receiver<Sndr, Rcvr, integral_constant<size_t, Is>>{op})...}; }

31

#

Constraints: The expression in the return statement is well-formed.

32

#

Remarks: The expression in the noexcept clause is true if the return statement is not potentially throwing; otherwise, false.

33

#

The expression in the noexcept clause of the constructor of basic-operation is:is_nothrow_constructible_v<basic-state<Self, Rcvr>, Self, Rcvr> &&noexcept(connect-all(this, std::forward(sndr), indices-for()))

34

#

The expression in the noexcept clause of the connect member function of basic-sender is:is_nothrow_constructible_v<basic-operation<Self, Rcvr>, Self, Rcvr>

35

#

The member default-impls::get-attrs is initialized with a callable object equivalent to the following lambda:[](const auto&, const auto&... child) noexcept -> decltype(auto) {if constexpr (sizeof...(child) == 1)return (FWD-ENV(get_env(child)), ...); elsereturn env<>();}

36

#

The member default-impls::get-env is initialized with a callable object equivalent to the following lambda:[](auto, auto&, const auto& rcvr) noexcept -> decltype(auto) {return FWD-ENV(get_env(rcvr));}

37

#

The member default-impls::get-state is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept -> decltype(auto) {auto& [_, data, ...child] = sndr; return allocator-aware-forward(std::forward_like(data), rcvr);}

38

#

The member default-impls::start is initialized with a callable object equivalent to the following lambda:[](auto&, auto&, auto&... ops) noexcept -> void {(execution::start(ops), ...);}

39

#

The member default-impls::complete is initialized with a callable object equivalent to the following lambda:[]<class Index, class Rcvr, class Tag, class... Args>( Index, auto& state, Rcvr& rcvr, Tag, Args&&... args) noexcept-> void requires callable<Tag, Rcvr, Args...> {static_assert(Index::value == 0); Tag()(std::move(rcvr), std::forward(args)...);}

🔗

template<class Sndr, class... Env> static consteval void default-impls::check-types();

40

#

Let Is be the pack of integral template arguments of the integer_sequence specialization denoted byindices-for.

41

#

Effects: Equivalent to:(get_completion_signatures<child-type<Sndr, Is>, FWD-ENV-T(Env)...>(), ...);

42

#

[Note 5:

For any types T and S, and pack E, let e be the expressionimpls-for::check-types<S, E...>().

Then exactly one of the following is true:

e is ill-formed, or

the evaluation of e exits with an exception, or

e is a core constant expression.

When e is a core constant expression, the pack S, E... uniquely determines a set of completion signatures.

— end note]

🔗

template<class Tag, class Data, class... Child> template<class Sndr, class... Env> constexpr auto basic-sender<Tag, Data, Child...>::get_completion_signatures();

43

#

Let Rcvr be the type of a receiver whose environment has type E, whereE is the first type in the list Env..., env<>.

Let CHECK-TYPES() be the expressionimpls-for::template check-types<
Sndr, E>(), and let CS be a type determined as follows:

  • (43.1)

    If CHECK-TYPES() is a core constant expression, let op be an lvalue subexpression whose type is connect_result_t<Sndr, Rcvr>. Then CS is the specialization of completion_signatures the set of whose template arguments correspond to the set of completion operations that are potentially evaluated ([basic.def.odr]) as a result of evaluating op.start().

  • (43.2)

    Otherwise, CS is completion_signatures<>.

44

#

Constraints: CHECK-TYPES() is a well-formed expression.

45

#

Effects: Equivalent to:CHECK-TYPES();return CS();

46

#

template<class... Fns>struct overload-set : Fns... {using Fns::operator()...;};

47

#

struct not-a-sender {using sender_concept = sender_t; templatestatic consteval auto get_completion_signatures() -> completion_signatures<> {throw unspecified-exception(); }}; where unspecified-exception is a type derived from exception.

48

#

constexpr void decay-copyable-result-datums(auto cs) { cs.for-each([]<class Tag, class... Ts>(Tag(*)(Ts...)) {if constexpr (!(is_constructible_v<decay_t, Ts> &&...))throw unspecified-exception(); });} where unspecified-exception is a type derived from exception.

🔗

template<class T, class Context> decltype(auto) allocator-aware-forward(T&& obj, Context&& context); // exposition only

49

#

allocator-aware-forward is an exposition-only function template used to either create a new object of type remove_cvref_t from obj or forward obj depending on whether an allocator is available.

If the environment associated with context provides an allocator (i.e., the expression get_allocator(get_env(context)) is valid), let alloc be the result of this expression and let P be remove_cvref_t.

50

#

Returns:

If alloc is not defined, returns std::forward(obj),

otherwise if P is a specialization of product-type, returns an object of type P whose elements are initialized usingmake_obj_using_allocator<decltype(e)>(std::forward_like(e), alloc) where e is the corresponding element of obj,

otherwise, returns make_obj_using_allocator

(std::forward(obj), alloc).

33.9.3 Sender concepts [exec.snd.concepts]

1

#

The sender concept defines the requirements for a sender type ([exec.async.ops]).

The sender_in concept defines the requirements for a sender type that can create asynchronous operations given an associated environment type.

The sender_to concept defines the requirements for a sender type that can connect with a specific receiver type.

The get_env customization point object is used to access a sender's associated attributes.

The connect customization point object is used to connect ([exec.async.ops]) a sender and a receiver to produce an operation state.

🔗

namespace std::execution {templateconcept is-constant = true; // exposition onlytemplateconcept is-sender = // exposition onlyderived_from<typename Sndr::sender_concept, sender_t>; templateconcept enable-sender = // exposition onlyis-sender ||is-awaitable<Sndr, env-promise<env<>>>; // [exec.awaitable]templateinline constexpr bool enable_sender = enable-sender; templateconsteval bool is-dependent-sender-helper() try { // exposition only get_completion_signatures(); return false; } catch (dependent_sender_error&) {return true; }templateconcept sender = enable_sender<remove_cvref_t> &&requires (const remove_cvref_t& sndr) {{ get_env(sndr) } -> queryable; } &&move_constructible<remove_cvref_t> &&constructible_from<remove_cvref_t, Sndr>; template<class Sndr, class... Env>concept sender_in =sender &&(sizeof...(Env) <= 1) &&(queryable &&...) &&is-constant<get_completion_signatures<Sndr, Env...>()>; templateconcept dependent_sender =sender && bool_constant<is-dependent-sender-helper()>::value; template<class Sndr, class Rcvr>concept sender_to =sender_in<Sndr, env_of_t> &&receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t>> &&requires (Sndr&& sndr, Rcvr&& rcvr) { connect(std::forward(sndr), std::forward(rcvr)); };}

2

#

For a type Sndr, ifsender is true anddependent_sender is false, then Sndr is a non-dependent sender ([exec.async.ops]).

3

#

Given a subexpression sndr, let Sndr be decltype((sndr)) and let rcvr be a receiver with an associated environment whose type is Env.

A completion operation is a permissible completion for Sndr and Env if its completion signature appears in the argument list of the specialization of completion_signatures denoted bycompletion_signatures_of_t<Sndr, Env>.

Sndr and Env model sender_in<Sndr, Env> if all the completion operations that are potentially evaluated by connecting sndr to rcvr and starting the resulting operation state are permissible completions for Sndr and Env.

4

#

Remarks: Pursuant to [namespace.std], users may specialize enable_sender totrue for cv-unqualified program-defined types that model sender, andfalse for types that do not.

Such specializations shall be usable in constant expressions ([expr.const]) and have type const bool.

5

#

The exposition-only conceptssender-of and sender-in-of define the requirements for a sender type that completes with a given unique set of value result types.

namespace std::execution {template<class... As>using value-signature = set_value_t(As...); // exposition onlytemplate<class Sndr, class SetValue, class... Env>concept sender-in-of-impl = // exposition onlysender_in<Sndr, Env...> &&MATCHING-SIG(SetValue, // see [exec.general]gather-signatures<set_value_t, // see [exec.cmplsig] completion_signatures_of_t<Sndr, Env...>, value-signature, type_identity_t>); template<class Sndr, class Env, class... Values>concept sender-in-of = // exposition onlysender-in-of-impl<Sndr, set_value_t(Values...), Env>; template<class Sndr, class... Values>concept sender-of = // exposition onlysender-in-of-impl<Sndr, set_value_t(Values...)>;}

6

#

Let sndr be an expression such that decltype((sndr)) is Sndr.

The type tag_of_t is as follows:

If the declarationauto&& [tag, data, ...children] = sndr; would be well-formed, tag_of_t is an alias for decltype(auto(tag)).

Otherwise, tag_of_t is ill-formed.

7

#

Let sender-for be an exposition-only concept defined as follows:namespace std::execution {template<class Sndr, class Tag>concept sender-for =sender &&same_as<tag_of_t, Tag>;}

8

#

For a type T,SET-VALUE-SIG(T) denotes the type set_value_t() if T is cv void; otherwise, it denotes the type set_value_t(T).

9

#

Library-provided sender types

always expose an overload of a member connect that accepts an rvalue sender and

only expose an overload of a member connect that accepts an lvalue sender if they model copy_constructible.

33.9.4 Awaitable helpers [exec.awaitable]

1

#

The sender concepts recognize awaitables as senders.

For [exec], an awaitable is an expression that would be well-formed as the operand of a co_await expression within a given context.

2

#

For a subexpression c, let GET-AWAITER(c, p) be expression-equivalent to the series of transformations and conversions applied to c as the operand of an await-expression in a coroutine, resulting in lvalue e as described by [expr.await], where p is an lvalue referring to the coroutine's promise, which has type Promise.

[Note 1:

This includes the invocation of the promise type's await_transform member if any, the invocation of the operator co_await picked by overload resolution if any, and any necessary implicit conversions and materializations.

— end note]

Let GET-AWAITER(c) be expression-equivalent to GET-AWAITER(c, q) where q is an lvalue of an unspecified empty class type none-such that lacks an await_transform member, and where coroutine_handle<none-such> behaves ascoroutine_handle.

3

#

Let is-awaitable be the following exposition-only concept:namespace std {templateconcept await-suspend-result = see below; // exposition onlytemplate<class A, class... Promise>concept is-awaiter = // exposition onlyrequires (A& a, coroutine_handle<Promise...> h) { a.await_ready() ? 1 : 0; { a.await_suspend(h) } -> await-suspend-result; a.await_resume(); }; template<class C, class... Promise>concept is-awaitable = // exposition onlyrequires (C (*fc)() noexcept, Promise&... p) {{ GET-AWAITER(fc(), p...) } -> is-awaiter<Promise...>; };}

await-suspend-result is true if and only if one of the following is true:

T is void, or

T is bool, or

T is a specialization of coroutine_handle.

4

#

For a subexpression c such that decltype((c)) is type C, and an lvalue p of type Promise,await-result-
type
<C, Promise> denotes the type decltype(GET-AWAITER(c, p).await_resume()) andawait-result-type denotes the type decltype(GET-AWAITER(c).await_resume()).

5

#

Let with-await-transform be the exposition-only class template:namespace std::execution {template<class T, class Promise>concept has-as-awaitable = // exposition onlyrequires (T&& t, Promise& p) {{ std::forward(t).as_awaitable(p) } -> is-awaitable<Promise&>; }; templatestruct with-await-transform { // exposition onlytemplate T&& await_transform(T&& value) noexcept {return std::forward(value); }template<has-as-awaitable T>auto await_transform(T&& value)noexcept(noexcept(std::forward(value).as_awaitable(declval<Derived&>())))-> decltype(std::forward(value).as_awaitable(declval<Derived&>())) {return std::forward(value).as_awaitable(static_cast<Derived&>(*this)); }};}

6

#

Let env-promise be the exposition-only class template:namespace std::execution {templatestruct env-promise : with-await-transform<env-promise> { // exposition only**unspecified get_return_object() noexcept; unspecified initial_suspend() noexcept; unspecified final_suspend() noexcept; void unhandled_exception() noexcept; void return_void() noexcept; coroutine_handle<> unhandled_stopped() noexcept; const Env& get_env() const noexcept; };}

[Note 2:

Specializations of env-promise are used only for the purpose of type computation; its members need not be defined.

— end note]

33.9.5 execution::default_domain [exec.domain.default]

1

#

namespace std::execution {struct default_domain {template<sender Sndr, queryable... Env>requires (sizeof...(Env) <= 1)static constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env)noexcept(see below); template<sender Sndr, queryable Env>static constexpr queryable decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept; template<class Tag, sender Sndr, class... Args>static constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args)noexcept(see below); };}

🔗

template<[sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]")... Env> requires (sizeof...(Env) <= 1) constexpr [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") decltype(auto) transform_sender(Sndr&& sndr, const Env&... env) noexcept(see below);

2

#

Let e be the expressiontag_of_t().transform_sender(std::forward(sndr), env...) if that expression is well-formed; otherwise, std::forward(sndr).

3

#

Returns: e.

4

#

Remarks: The exception specification is equivalent to noexcept(e).

🔗

template<[sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]") Env> constexpr [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]") decltype(auto) transform_env(Sndr&& sndr, Env&& env) noexcept;

5

#

Let e be the expressiontag_of_t().transform_env(std::forward(sndr), std::forward(env)) if that expression is well-formed; otherwise, FWD-ENV(std::forward(env)).

6

#

Mandates: noexcept(e) is true.

7

#

Returns: e.

🔗

template<class Tag, [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, class... Args> constexpr decltype(auto) apply_sender(Tag, Sndr&& sndr, Args&&... args) noexcept(see below);

8

#

Let e be the expression Tag().apply_sender(std::forward(sndr), std::forward(args)...)

9

#

Constraints: e is a well-formed expression.

10

#

Returns: e.

11

#

Remarks: The exception specification is equivalent to noexcept(e).

33.9.6 execution::transform_sender [exec.snd.transform]

🔗

namespace std::execution { template<class Domain, [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]")... Env> requires (sizeof...(Env) <= 1) constexpr [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") decltype(auto) transform_sender(Domain dom, Sndr&& sndr, const Env&... env) noexcept(see below); }

1

#

Let transformed-sndr be the expressiondom.transform_sender(std::forward(sndr), env...) if that expression is well-formed; otherwise,default_domain().transform_sender(std::forward(sndr), env...)

Let final-sndr be the expression transformed-sndr if transformed-sndr and sndr have the same type ignoring cv-qualifiers; otherwise, it is the expression transform_sender(dom, transformed-sndr, env...).

2

#

Returns: final-sndr.

3

#

Remarks: The exception specification is equivalent tonoexcept(final-sndr).

33.9.7 execution::transform_env [exec.snd.transform.env]

🔗

namespace std::execution { template<class Domain, [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]") Env> constexpr [queryable](exec.queryable.concept#concept:queryable "33.2.2queryable concept[exec.queryable.concept]") decltype(auto) transform_env(Domain dom, Sndr&& sndr, Env&& env) noexcept; }

1

#

Let e be the expressiondom.transform_env(std::forward(sndr), std::forward(env)) if that expression is well-formed; otherwise,default_domain().transform_env(std::forward(sndr), std::forward(env))

2

#

Mandates: noexcept(e) is true.

3

#

Returns: e.

33.9.8 execution::apply_sender [exec.snd.apply]

🔗

namespace std::execution { template<class Domain, class Tag, [sender](#concept:sender "33.9.3Sender concepts[exec.snd.concepts]") Sndr, class... Args> constexpr decltype(auto) apply_sender(Domain dom, Tag, Sndr&& sndr, Args&&... args) noexcept(see below); }

1

#

Let e be the expressiondom.apply_sender(Tag(), std::forward(sndr), std::forward(args)...) if that expression is well-formed; otherwise,default_domain().apply_sender(Tag(), std::forward(sndr), std::forward(args)...)

2

#

Constraints: The expression e is well-formed.

3

#

Returns: e.

4

#

Remarks: The exception specification is equivalent to noexcept(e).

33.9.9 execution::get_completion_signatures [exec.getcomplsigs]

🔗

template<class Sndr, class... Env> consteval auto get_completion_signatures() -> [valid-completion-signatures](execution.syn#concept:valid-completion-signatures "33.4Header <execution> synopsis[execution.syn]") auto;

1

#

Let except be an rvalue subexpression of an unspecified class type Except such thatmove_constructible && derived_from<Except, exception> is true.

Let CHECKED-COMPLSIGS(e) be e if e is a core constant expression whose type satisfies valid-completion-signatures; otherwise, it is the following expression:(e, throw except, completion_signatures())

Let get-complsigs<Sndr, Env...>() be expression-equivalent toremove_reference_t::template get_completion_signatures<Sndr, Env...>().

Let NewSndr be Sndr if sizeof...(Env) == 0 is true; otherwise, decltype(s) where s is the following expression:transform_sender(get-domain-late(declval(), declval()...), declval(), declval()...)

2

#

Constraints: sizeof...(Env) <= 1 is true.

3

#

Effects: Equivalent to: return e; where e is expression-equivalent to the following:

  • (3.1)

    CHECKED-COMPLSIGS(get-complsigs<NewSndr, Env...>()) if get-complsigs<NewSndr, Env
    ...>() is a well-formed expression.

  • (3.2)

    Otherwise,CHECKED-COMPLSIGS(get-complsigs()) if get-complsigs() is a well-formed expression.

  • (3.3)

    Otherwise,completion_signatures<SET-VALUE-SIG(await-result-type<NewSndr, env-promise...>), // [exec.snd.concepts] set_error_t(exception_ptr), set_stopped_t()> if is-awaitable<NewSndr, env-promise...> is true.

  • (3.4)

    Otherwise,(throw dependent-sender-error(), completion_signatures()) if sizeof...(
    Env) == 0 is true, where dependent-sender-error isdependent_sender_error or an unspecified type derived publicly and unambiguously fromdependent_sender_error.

  • (3.5)

    Otherwise,(throw except, completion_signatures()).

4

#

Given a type Env, ifcompletion_signatures_of_t andcompletion_signatures_of_t<Sndr, Env> are both well-formed, they shall denote the same type.

5

#

Let rcvr be an rvalue whose type Rcvr models receiver, and let Sndr be the type of a sender such that sender_in<Sndr, env_of_t> is true.

Let Sigs... be the template arguments of the completion_signatures specialization named by completion_signatures_of_t<Sndr, env_of_t>.

Let CSO be a completion function.

If sender Sndr or its operation state cause the expression CSO(rcvr, args...) to be potentially evaluated ([basic.def.odr]) then there shall be a signature Sig in Sigs... such thatMATCHING-SIG(decayed-typeof(decltype(args)...), Sig) is true ([exec.general]).

33.9.10 execution::connect [exec.connect]

1

#

connect connects ([exec.async.ops]) a sender with a receiver.

2

#

The name connect denotes a customization point object.

For subexpressions sndr and rcvr, let Sndr be decltype((sndr)) andRcvr be decltype((rcvr)), let new_sndr be the expressiontransform_sender(decltype(get-domain-late(sndr, get_env(rcvr))){}, sndr, get_env(rcvr)) and let DS and DR bedecay_t<decltype((new_sndr))> and decay_t, respectively.

3

#

Let connect-awaitable-promise be the following exposition-only class:

namespace std::execution {struct connect-awaitable-promise : with-await-transform<connect-awaitable-promise> {connect-awaitable-promise(DS&, DR& rcvr) noexcept : rcvr(rcvr) {} suspend_always initial_suspend() noexcept { return {}; }noreturn suspend_always final_suspend() noexcept { terminate(); }noreturn void unhandled_exception() noexcept { terminate(); }noreturn void return_void() noexcept { terminate(); } coroutine_handle<> unhandled_stopped() noexcept { set_stopped(std::move(rcvr)); return noop_coroutine(); }operation-state-task get_return_object() noexcept {return operation-state-task{ coroutine_handle<connect-awaitable-promise>::from_promise(*this)}; } env_of_t get_env() const noexcept {return execution::get_env(rcvr); }private: DR& rcvr; // exposition only};}

4

#

Let operation-state-task be the following exposition-only class:namespace std::execution {struct operation-state-task { // exposition onlyusing operation_state_concept = operation_state_t; using promise_type = connect-awaitable-promise; explicit operation-state-task(coroutine_handle<> h) noexcept : coro(h) {}operation-state-task(operation-state-task&&) = delete; ~operation-state-task() { coro.destroy(); }void start() & noexcept {coro.resume(); }private: coroutine_handle<> coro; // exposition only};}

5

#

Let V name the typeawait-result-type<DS, connect-awaitable-promise>, let Sigs name the typecompletion_signatures<SET-VALUE-SIG(V), // see [exec.snd.concepts] set_error_t(exception_ptr), set_stopped_t()> and let connect-awaitable be an exposition-only coroutine defined as follows:namespace std::execution {template<class Fun, class... Ts>auto suspend-complete(Fun fun, Ts&&... as) noexcept { // exposition onlyauto fn = &, fun noexcept { fun(std::forward(as)...); }; struct awaiter {decltype(fn) fn; // exposition onlystatic constexpr bool await_ready() noexcept { return false; }void await_suspend(coroutine_handle<>) noexcept { fn(); }noreturn void await_resume() noexcept { unreachable(); }}; return awaiter{fn}; }operation-state-task connect-awaitable(DS sndr, DR rcvr) requires receiver_of<DR, Sigs> { exception_ptr ep; try {if constexpr (same_as<V, void>) {co_await std::move(sndr); co_await suspend-complete(set_value, std::move(rcvr)); } else {co_await suspend-complete(set_value, std::move(rcvr), co_await std::move(sndr)); }} catch(...) { ep = current_exception(); }co_await suspend-complete(set_error, std::move(rcvr), std::move(ep)); }}

6

#

The expression connect(sndr, rcvr) is expression-equivalent to:

  • (6.1)

    new_sndr.connect(rcvr) if that expression is well-formed. Mandates: The type of the expression above satisfies operation_state.

  • (6.2)

    Otherwise, connect-awaitable(new_sndr, rcvr).

Except that rcvr is evaluated only once.

Mandates: The following are true:

sender_in<Sndr, env_of_t>

receiver_of<Rcvr, completion_signatures_of_t<Sndr, env_of_t>>

33.9.11 Sender factories [exec.factories]

33.9.11.1 execution::schedule [exec.schedule]

1

#

schedule obtains a schedule sender ([exec.async.ops]) from a scheduler.

2

#

The name schedule denotes a customization point object.

For a subexpression sch, the expression schedule(sch) is expression-equivalent tosch.schedule().

3

#

Mandates: The type of sch.schedule() satisfies sender.

4

#

If the expressionget_completion_scheduler<set_value_t>(get_env(sch.schedule())) == sch is ill-formed or evaluates to false, the behavior of calling schedule(sch) is undefined.

33.9.11.2 execution::just, execution::just_error, execution::just_stopped [exec.just]

1

#

just, just_error, and just_stopped are sender factories whose asynchronous operations complete synchronously in their start operation with a value completion operation, an error completion operation, or a stopped completion operation, respectively.

2

#

The names just, just_error, and just_stopped denote customization point objects.

Let just-cpo be one ofjust, just_error, or just_stopped.

For a pack of subexpressions ts, let Ts be the pack of types decltype((ts)).

The expression just-cpo(ts...) is ill-formed if

(movable-value &&...) is false, or

just-cpo is just_error andsizeof...(ts) == 1 is false, or

just-cpo is just_stopped andsizeof...(ts) == 0 is false.

Otherwise, it is expression-equivalent tomake-sender(just-cpo, product-type{ts...}).

For just, just_error, and just_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for just-cpo as follows:namespace std::execution {template<>struct impls-for<decayed-typeof<just-cpo>> : default-impls {static constexpr auto start =[](auto& state, auto& rcvr) noexcept -> void {auto& [...ts] = state; set-cpo(std::move(rcvr), std::move(ts)...); }; };}

33.9.11.3 execution::read_env [exec.read.env]

1

#

read_env is a sender factory for a sender whose asynchronous operation completes synchronously in its start operation with a value completion result equal to a value read from the receiver's associated environment.

2

#

read_env is a customization point object.

For some query object q, the expression read_env(q) is expression-equivalent tomake-sender(read_env, q).

3

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for read_env as follows:

🔗

namespace std::execution {template<>struct impls-for<decayed-typeof<read_env>> : default-impls {static constexpr auto start =[](auto query, auto& rcvr) noexcept -> void {TRY-SET-VALUE(rcvr, query(get_env(rcvr))); }; }; template<class Sndr, class Env>static consteval void check-types();}

🔗

template<class Sndr, class Env> static consteval void check-types();

4

#

Let Q be decay_t<data-type>.

5

#

Throws: An exception of an unspecified type derived from exception if the expression Q()(env) is ill-formed or has type void, whereenv is an lvalue subexpression whose type is Env.

33.9.12 Sender adaptors [exec.adapt]

33.9.12.1 General [exec.adapt.general]

1

#

Subclause [exec.adapt] specifies a set of sender adaptors.

2

#

The bitwise inclusive or operator is overloaded for the purpose of creating sender chains.

The adaptors also support function call syntax with equivalent semantics.

3

#

Unless otherwise specified:

  • (3.1)

    A sender adaptor is prohibited from causing observable effects, apart from moving and copying its arguments, before the returned sender is connected with a receiver using connect, and start is called on the resulting operation state.

  • (3.2)

    A parent sender ([exec.async.ops]) with a single child sender sndr has an associated attribute object equal toFWD-ENV(get_env(sndr)) ([exec.fwd.env]).

  • (3.3)

    A parent sender with more than one child sender has an associated attributes object equal to env<>{}.

  • (3.4)

    When a parent sender is connected to a receiver rcvr, any receiver used to connect a child sender has an associated environment equal to FWD-ENV(get_env(rcvr)).

  • (3.5)

    An adaptor whose child senders are all non-dependent ([exec.async.ops]) is itself non-dependent.

  • (3.6)

    These requirements apply to any function that is selected by the implementation of the sender adaptor.

  • (3.7)

    Recommended practice: Implementations should use the completion signatures of the adaptors to communicate type errors to users and to propagate any such type errors from child senders.

4

#

If a sender returned from a sender adaptor specified in [exec.adapt] is specified to include set_error_t(Err) among its set of completion signatures where decay_t denotes the type exception_ptr, but the implementation does not potentially evaluate an error completion operation with an exception_ptr argument, the implementation is allowed to omit the exception_ptr error completion signature from the set.

33.9.12.2 Closure objects [exec.adapt.obj]

1

#

A pipeable sender adaptor closure object is a function object that accepts one or more sender arguments and returns a sender.

For a pipeable sender adaptor closure object c and an expression sndr such that decltype((sndr)) models sender, the following expressions are equivalent and yield a sender:c(sndr) sndr | c

Given an additional pipeable sender adaptor closure object d, the expression c | d produces another pipeable sender adaptor closure object e:

e is a perfect forwarding call wrapper ([func.require]) with the following properties:

  • (1.1)

    Its target object is an object d2 of type decltype(auto(d)) direct-non-list-initialized with d.

  • (1.2)

    It has one bound argument entity, an object c2 of type decltype(auto(c)) direct-non-list-initialized with c.

  • (1.3)

    Its call pattern is d2(c2(arg)), where arg is the argument used in a function call expression of e.

The expression c | d is well-formed if and only if the initializations of the state entities ([func.def]) of e are all well-formed.

2

#

An object t of type T is a pipeable sender adaptor closure object if T models derived_from<sender_adaptor_closure>,T has no other base classes of type sender_adaptor_closure for any other type U, andT does not satisfy sender.

3

#

The template parameter D for sender_adaptor_closure can be an incomplete type.

Before any expression of type cv D appears as an operand to the | operator,D shall be complete and model derived_from<sender_adaptor_closure>.

The behavior of an expression involving an object of type cv D as an operand to the | operator is undefined if overload resolution selects a program-defined operator| function.

4

#

A pipeable sender adaptor object is a customization point object that accepts a sender as its first argument and returns a sender.

If a pipeable sender adaptor object accepts only one argument, then it is a pipeable sender adaptor closure object.

5

#

If a pipeable sender adaptor object adaptor accepts more than one argument, then let sndr be an expression such that decltype((sndr)) models sender, let args... be arguments such that adaptor(sndr, args...) is a well-formed expression as specified below, and let BoundArgs be a pack that denotes decltype(auto(args))....

The expression adaptor(args...) produces a pipeable sender adaptor closure object f that is a perfect forwarding call wrapper with the following properties:

  • (5.1)

    Its target object is a copy of adaptor.

  • (5.2)

    Its bound argument entities bound_args consist of objects of types BoundArgs... direct-non-list-initialized withstd::forward<decltype((args))>(args)..., respectively.

  • (5.3)

    Its call pattern is adaptor(rcvr, bound_args...), where rcvr is the argument used in a function call expression of f.

The expression adaptor(args...) is well-formed if and only if the initializations of the bound argument entities of the result, as specified above, are all well-formed.

33.9.12.3 execution::write_env [exec.write.env]

1

#

write_env is a sender adaptor that accepts a sender and a queryable object, and that returns a sender that, when connected with a receiver rcvr, connects the adapted sender with a receiver whose execution environment is the result of joining the queryable object to the result of get_env(rcvr).

2

#

write_env is a customization point object.

For some subexpressions sndr and env, if decltype((sndr)) does not satisfy sender or if decltype((env)) does not satisfy queryable, the expression write_env(sndr, env) is ill-formed.

Otherwise, it is expression-equivalent tomake-sender(write_env, env, sndr).

3

#

Let write-env-t denote the type decltype(auto(write_env)).

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for write-env-t as follows:

🔗

template<>struct impls-for<write-env-t> : default-impls {static constexpr auto join-env(const auto& state, const auto& env) noexcept {return see below; }static constexpr auto get-env =[](auto, const auto& state, const auto& rcvr) noexcept {return join-env(state, FWD-ENV(get_env(rcvr))); }; template<class Sndr, class... Env>static consteval void check-types();};

4

#

Invocation ofimpls-for<write-env-t>::join-env returns an object e such that

decltype(e) models queryable and

given a query object q, the expression e.query(q) is expression-equivalent to state.query(q) if that expression is valid, otherwise, e.query(q) is expression-equivalent to env.query(q).

5

#

For a type Sndr and a pack of types Env, let State be data-type and let JoinEnv be the packdecltype(join-env(declval(), FWD-ENV(declval()))).

Then impls-for<write-env-t>::check-types<Sndr, Env...>() is expression-equivalent toget_completion_signatures<child-
type
, JoinEnv...>().

33.9.12.4 execution::unstoppable [exec.unstoppable]

1

#

unstoppable is a sender adaptor that connects its inner sender with a receiver that has the execution environment of the outer receiver but with an object of type never_stop_token as the result of the get_stop_token query.

2

#

For a subexpression sndr,unstoppable(sndr) is expression-equivalent towrite_env(sndr, prop(get_stop_token, never_stop_token{})).

33.9.12.5 execution::starts_on [exec.starts.on]

1

#

starts_on adapts an input sender into a sender that will start on an execution agent belonging to a particular scheduler's associated execution resource.

2

#

The name starts_on denotes a customization point object.

For subexpressions sch and sndr, if decltype((
sch)) does not satisfy scheduler, ordecltype((sndr)) does not satisfy sender,starts_on(sch, sndr) is ill-formed.

3

#

Otherwise, the expression starts_on(sch, sndr) is expression-equivalent to:transform_sender(query-with-default(get_domain, sch, default_domain()), make-sender(starts_on, sch, sndr)) except that sch is evaluated only once.

4

#

Let out_sndr and env be subexpressions such that OutSndr is decltype((out_sndr)).

If sender-for<OutSndr, starts_on_t> is false, then the expressions starts_on.transform_env(out_sndr, env) andstarts_on.transform_sender(out_sndr, env) are ill-formed; otherwise

starts_on.transform_env(out_sndr, env) is equivalent to:auto&& [_, sch, _] = out_sndr;return JOIN-ENV(SCHED-ENV(sch), FWD-ENV(env));

starts_on.transform_sender(out_sndr, env) is equivalent to:auto&& [_, sch, sndr] = out_sndr;return let_value( schedule(sch), sndr = std::forward_like(sndr) mutablenoexcept(is_nothrow_move_constructible_v<decay_t>) {return std::move(sndr); });

5

#

Let out_sndr be a subexpression denoting a sender returned from starts_on(sch, sndr) or one equal to such, and let OutSndr be the type decltype((out_sndr)).

Let out_rcvr be a subexpression denoting a receiver that has an environment of type Env such that sender_in<OutSndr, Env> is true.

Let op be an lvalue referring to the operation state that results from connecting out_sndr with out_rcvr.

Calling start(op) shall start sndr on an execution agent of the associated execution resource of sch.

If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

33.9.12.6 execution::continues_on [exec.continues.on]

1

#

continues_on adapts a sender into one that completes on the specified scheduler.

2

#

The name continues_on denotes a pipeable sender adaptor object.

For subexpressions sch and sndr, if decltype((sch)) does not satisfy scheduler, ordecltype((sndr)) does not satisfy sender,continues_on(sndr, sch) is ill-formed.

3

#

Otherwise, the expression continues_on(sndr, sch) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(continues_on, sch, sndr)) except that sndr is evaluated only once.

4

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for continues_on_t as follows:namespace std::execution {template<>struct impls-for<continues_on_t> : default-impls {static constexpr auto get-attrs =[](const auto& data, const auto& child) noexcept -> decltype(auto) {return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child))); }; };}

5

#

Let sndr and env be subexpressions such that Sndr is decltype((sndr)).

If sender-for<Sndr, continues_on_t> is false, then the expression continues_on.transform_sender(sndr, env) is ill-formed; otherwise, it is equal to:auto [_, data, child] = sndr;return schedule_from(std::move(data), std::move(child));

[Note 1:

This causes the continues_on(sndr, sch) sender to becomeschedule_from(sch, sndr) when it is connected with a receiver whose execution domain does not customize continues_on.

— end note]

6

#

Let out_sndr be a subexpression denoting a sender returned from continues_on(sndr, sch) or one equal to such, and let OutSndr be the type decltype((out_sndr)).

Let out_rcvr be a subexpression denoting a receiver that has an environment of type Env such that sender_in<OutSndr, Env> is true.

Let op be an lvalue referring to the operation state that results from connecting out_sndr with out_rcvr.

Calling start(op) shall start sndr on the current execution agent and execute completion operations on out_rcvr on an execution agent of the execution resource associated with sch.

If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

33.9.12.7 execution::schedule_from [exec.schedule.from]

1

#

schedule_from schedules work dependent on the completion of a sender onto a scheduler's associated execution resource.

[Note 1:

schedule_from is not meant to be used in user code; it is used in the implementation of continues_on.

— end note]

2

#

The name schedule_from denotes a customization point object.

For some subexpressions sch and sndr, let Sch be decltype((sch)) andSndr be decltype((sndr)).

If Sch does not satisfy scheduler, orSndr does not satisfy sender,schedule_from(sch, sndr) is ill-formed.

3

#

Otherwise, the expression schedule_from(sch, sndr) is expression-equivalent to:transform_sender(query-with-default(get_domain, sch, default_domain()), make-sender(schedule_from, sch, sndr)) except that sch is evaluated only once.

4

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for schedule_from_t as follows:

🔗

namespace std::execution {template<>struct impls-for<schedule_from_t> : default-impls {static constexpr auto get-attrs = see below; static constexpr auto get-state = see below; static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types(); };}

5

#

The member impls-for<schedule_from_t>::get-attrs is initialized with a callable object equivalent to the following lambda:[](const auto& data, const auto& child) noexcept -> decltype(auto) {return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));}

6

#

The member impls-for<schedule_from_t>::get-state is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below)requires sender_in<child-type, FWD-ENV-T(env_of_t)> {auto& [_, sch, child] = sndr; using sched_t = decltype(auto(sch)); using variant_t = see below; using receiver_t = see below; using operation_t = connect_result_t<schedule_result_t<sched_t>, receiver_t>; constexpr bool nothrow = noexcept(connect(schedule(sch), receiver_t{nullptr})); struct state-type { Rcvr& rcvr; // exposition only variant_t async-result; // exposition only operation_t op-state; // exposition onlyexplicit state-type(sched_t sch, Rcvr& rcvr) noexcept(nothrow): rcvr(rcvr), op-state(connect(schedule(sch), receiver_t{this})) {}}; return state-type{sch, rcvr};}

🔗

template<class Sndr, class... Env> static consteval void check-types();

7

#

Effects: Equivalent to:get_completion_signatures<schedule_result_t<data-type>, FWD-ENV-T(Env)...>();auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>();decay-copyable-result-datums(cs); // see [exec.snd.expos]

8

#

Objects of the local class state-type can be used to initialize a structured binding.

9

#

Let Sigs be a pack of the arguments to the completion_signatures specialization named by completion_signatures_of_t<child-type, FWD-ENV-T(env_of_t)>.

Let as-tuple be an alias template such thatas-tuple<Tag(Args...)> denotes the type decayed-tuple<Tag, Args...>, and let is-nothrow-decay-copy-sig be a variable template such thatauto(is-nothrow-decay-copy-sig<Tag(Args...
)>) is a constant expression of type bool and equal to (is_nothrow_constructible_v<decay_t, Args> && ...).

Let error-completion be a pack consisting of the type set_error_t(exception_ptr) if (is-nothrow-decay-copy-sig &&...) is false, and an empty pack otherwise.

Then variant_t denotes the type variant<monostate, as-tuple..., error-completion...>, except with duplicate types removed.

10

#

receiver_t is an alias for the following exposition-only class:namespace std::execution {struct receiver-type {using receiver_concept = receiver_t; state-type* state; // exposition onlyvoid set_value() && noexcept { visit([this](Tuple& result) noexcept -> void {if constexpr (same_as<monostate, Tuple>) {auto& [tag, ...args] = result; tag(std::move(state->rcvr), std::move(args)...); }}, state->async-result); }templatevoid set_error(Error&& err) && noexcept { execution::set_error(std::move(state->rcvr), std::forward(err)); }void set_stopped() && noexcept { execution::set_stopped(std::move(state->rcvr)); }decltype(auto) get_env() const noexcept {return FWD-ENV(execution::get_env(state->rcvr)); }};}

11

#

The expression in the noexcept clause of the lambda is true if the construction of the returned state-type object is not potentially throwing; otherwise, false.

12

#

The member impls-for<schedule_from_t>::complete is initialized with a callable object equivalent to the following lambda:[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept-> void {using result_t = decayed-tuple<Tag, Args...>; constexpr bool nothrow = (is_nothrow_constructible_v<decay_t, Args> && ...); try { state.async-result.template emplace<result_t>(Tag(), std::forward(args)...); } catch (...) {if constexpr (!nothrow) state.async-result.template emplace<tuple<set_error_t, exception_ptr>>(set_error, current_exception()); } start(state.op-state);};

13

#

Let out_sndr be a subexpression denoting a sender returned from schedule_from(sch, sndr) or one equal to such, and let OutSndr be the type decltype((out_sndr)).

Let out_rcvr be a subexpression denoting a receiver that has an environment of type Env such that sender_in<OutSndr, Env> is true.

Let op be an lvalue referring to the operation state that results from connecting out_sndr with out_rcvr.

Calling start(op) shall start sndr on the current execution agent and execute completion operations on out_rcvr on an execution agent of the execution resource associated with sch.

If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

33.9.12.8 execution::on [exec.on]

1

#

The on sender adaptor has two forms:

  • (1.1)

    on(sch, sndr), which starts a sender sndr on an execution agent belonging to a scheduler sch's associated execution resource and that, upon sndr's completion, transfers execution back to the execution resource on which the on sender was started.

  • (1.2)

    on(sndr, sch, closure), which upon completion of a sender sndr, transfers execution to an execution agent belonging to a scheduler sch's associated execution resource, then executes a sender adaptor closure closure with the async results of the sender, and that then transfers execution back to the execution resource on which sndr completed.

2

#

The name on denotes a pipeable sender adaptor object.

For subexpressions sch and sndr,on(sch, sndr) is ill-formed if any of the following is true:

decltype((sch)) does not satisfy scheduler, or

decltype((sndr)) does not satisfy sender andsndr is not a pipeable sender adaptor closure object ([exec.adapt.obj]), or

decltype((sndr)) satisfies sender andsndr is also a pipeable sender adaptor closure object.

3

#

Otherwise, if decltype((sndr)) satisfies sender, the expression on(sch, sndr) is expression-equivalent to:transform_sender(query-with-default(get_domain, sch, default_domain()), make-sender(on, sch, sndr)) except that sch is evaluated only once.

4

#

For subexpressions sndr, sch, and closure, if

decltype((sch)) does not satisfy scheduler, or

decltype((sndr)) does not satisfy sender, or

closure is not a pipeable sender adaptor closure object ([exec.adapt.obj]),

the expression on(sndr, sch, closure) is ill-formed; otherwise, it is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(on, product-type{sch, closure}, sndr)) except that sndr is evaluated only once.

5

#

Let out_sndr and env be subexpressions, let OutSndr be decltype((out_sndr)), and let Env be decltype((env)).

If sender-for<OutSndr, on_t> is false, then the expressions on.transform_env(out_sndr, env) andon.transform_sender(out_sndr, env) are ill-formed.

6

#

Otherwise: Let not-a-scheduler be an unspecified empty class type.

7

#

The expression on.transform_env(out_sndr, env) has effects equivalent to:auto&& [_, data, _] = out_sndr;if constexpr (scheduler<decltype(data)>) {return JOIN-ENV(SCHED-ENV(std::forward_like(data)), FWD-ENV(std::forward(env)));} else {return std::forward(env);}

8

#

The expression on.transform_sender(out_sndr, env) has effects equivalent to:auto&& [_, data, child] = out_sndr;if constexpr (scheduler<decltype(data)>) {auto orig_sch =query-with-default(get_scheduler, env, not-a-scheduler()); if constexpr (same_as<decltype(orig_sch), not-a-scheduler>) {return not-a-sender{}; } else {return continues_on( starts_on(std::forward_like(data), std::forward_like(child)), std::move(orig_sch)); }} else {auto& [sch, closure] = data; auto orig_sch = query-with-default( get_completion_scheduler<set_value_t>, get_env(child), query-with-default(get_scheduler, env, not-a-scheduler())); if constexpr (same_as<decltype(orig_sch), not-a-scheduler>) {return not-a-sender{}; } else {return write_env( continues_on( std::forward_like(closure)( continues_on( write_env(std::forward_like(child), SCHED-ENV(orig_sch)), sch)), orig_sch), SCHED-ENV(sch)); }}

9

#

Let out_sndr be a subexpression denoting a sender returned from on(sch, sndr) or one equal to such, and let OutSndr be the type decltype((out_sndr)).

Let out_rcvr be a subexpression denoting a receiver that has an environment of type Env such that sender_in<OutSndr, Env> is true.

Let op be an lvalue referring to the operation state that results from connecting out_sndr with out_rcvr.

Calling start(op) shall

remember the current scheduler, get_scheduler(get_env(rcvr));

start sndr on an execution agent belonging tosch's associated execution resource;

upon sndr's completion, transfer execution back to the execution resource associated with the scheduler remembered in step 1; and

forward sndr's async result to out_rcvr.

If any scheduling operation fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

10

#

Let out_sndr be a subexpression denoting a sender returned from on(sndr, sch, closure) or one equal to such, and let OutSndr be the type decltype((out_sndr)).

Let out_rcvr be a subexpression denoting a receiver that has an environment of type Env such that sender_in<OutSndr, Env> is true.

Let op be an lvalue referring to the operation state that results from connecting out_sndr with out_rcvr.

Calling start(op) shall

remember the current scheduler, which is the first of the following expressions that is well-formed:

get_completion_scheduler<set_value_t>(get_env(sndr))

get_scheduler(get_env(rcvr));

start sndr on the current execution agent;

upon sndr's completion, transfer execution to an agent owned by sch's associated execution resource;

forward sndr's async result as if by connecting and starting a sender closure(S), where S is a sender that completes synchronously with sndr's async result; and

upon completion of the operation started in the previous step, transfer execution back to the execution resource associated with the scheduler remembered in step 1 and forward the operation's async result to out_rcvr.

If any scheduling operation fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

33.9.12.9 execution::then, execution::upon_error, execution::upon_stopped [exec.then]

1

#

then attaches an invocable as a continuation for an input sender's value completion operation.

upon_error and upon_stopped do the same for the error and stopped completion operations, respectively, sending the result of the invocable as a value completion.

2

#

The names then, upon_error, and upon_stopped denote pipeable sender adaptor objects.

Let the expression then-cpo be one ofthen, upon_error, or upon_stopped.

For subexpressions sndr and f, if decltype((sndr)) does not satisfy sender, ordecltype((f)) does not satisfy movable-value,then-cpo(sndr, f) is ill-formed.

3

#

Otherwise, the expression then-cpo(sndr, f) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(then-cpo, f, sndr)) except that sndr is evaluated only once.

4

#

For then, upon_error, and upon_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for then-cpo as follows:

🔗

namespace std::execution {template<>struct impls-for<decayed-typeof<then-cpo>> : default-impls {static constexpr auto complete =[]<class Tag, class... Args>(auto, auto& fn, auto& rcvr, Tag, Args&&... args) noexcept -> void {if constexpr (same_as<Tag, decayed-typeof>) {TRY-SET-VALUE(rcvr, invoke(std::move(fn), std::forward(args)...)); } else { Tag()(std::move(rcvr), std::forward(args)...); }}; template<class Sndr, class... Env>static consteval void check-types(); };}

🔗

template<class Sndr, class... Env> static consteval void check-types();

5

#

Effects: Equivalent to:auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>();auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {if constexpr (invocable<remove_cvref_t<data-type>, Ts...>)throw unspecified-exception();}; cs.for-each(overload-set{fn, {}}); where unspecified-exception is a type derived from exception.

6

#

The expression then-cpo(sndr, f) has undefined behavior unless it returns a sender out_sndr that

invokes f or a copy of such with the value, error, or stopped result datums of sndr for then, upon_error, and upon_stopped, respectively, using the result value of f as out_sndr's value completion, and

forwards all other completion operations unchanged.

33.9.12.10 execution::let_value, execution::let_error, execution::let_stopped [exec.let]

1

#

let_value, let_error, and let_stopped transform a sender's value, error, and stopped completions, respectively, into a new child asynchronous operation by passing the sender's result datums to a user-specified callable, which returns a new sender that is connected and started.

2

#

For let_value, let_error, and let_stopped, let set-cpo beset_value, set_error, and set_stopped, respectively.

Let the expression let-cpo be one oflet_value, let_error, or let_stopped.

For a subexpression sndr, let let-env(sndr) be expression-equivalent to the first well-formed expression below:

SCHED-ENV(get_completion_scheduler<decayed-typeof<set-cpo>>(get_env(sndr)))

MAKE-ENV(get_domain, get_domain(get_env(sndr)))

(void(sndr), env<>{})

3

#

The names let_value, let_error, and let_stopped denote pipeable sender adaptor objects.

For subexpressions sndr and f, let F be the decayed type of f.

If decltype((sndr)) does not satisfy sender or if decltype((f)) does not satisfy movable-value, the expression let-cpo(sndr, f) is ill-formed.

If F does not satisfy invocable, the expression let_stopped(sndr, f) is ill-formed.

4

#

Otherwise, the expression let-cpo(sndr, f) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr)) except that sndr is evaluated only once.

5

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for let-cpo as follows:

🔗

namespace std::execution {template<class State, class Rcvr, class... Args>void let-bind(State& state, Rcvr& rcvr, Args&&... args); // exposition onlytemplate<>struct impls-for<decayed-typeof<let-cpo>> : default-impls {static constexpr auto get-state = see below; static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types(); };}

6

#

Let receiver2 denote the following exposition-only class template:namespace std::execution {template<class Rcvr, class Env>struct receiver2 {using receiver_concept = receiver_t; template<class... Args>void set_value(Args&&... args) && noexcept { execution::set_value(std::move(rcvr), std::forward(args)...); }templatevoid set_error(Error&& err) && noexcept { execution::set_error(std::move(rcvr), std::forward(err)); }void set_stopped() && noexcept { execution::set_stopped(std::move(rcvr)); }decltype(auto) get_env() const noexcept {return see below; } Rcvr& rcvr; // exposition only Env env; // exposition only};}

Invocation of the function receiver2::get_env returns an object e such that

decltype(e) models queryable and

given a query object q, the expression e.query(q) is expression-equivalent to env.query(q) if that expression is valid; otherwise, if the type of q satisfies forwarding-query,e.query(q) is expression-equivalent to get_env(rcvr).query(q); otherwise,e.query(q) is ill-formed.

🔗

template<class Sndr, class... Env> static consteval void check-types();

7

#

Effects: Equivalent to:using LetFn = remove_cvref_t<data-type>;auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>();auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {if constexpr (!is-valid-let-sender) // see belowthrow unspecified-exception();}; cs.for-each(overload-set(fn, {})); where unspecified-exception is a type derived from exception, and where is-valid-let-sender is true if and only if all of the following are true:

(constructible_from<decay_t, Ts> &&...)

invocable<LetFn, decay_t&...>

sender<invoke_result_t<LetFn, decay_t&...>>

sizeof...(Env) == 0 || sender_in<invoke_result_t<LetFn, decay_t&...>, env-t
...>

where env-t is the packdecltype(let-cpo.transform_env(declval(), declval())).

8

#

impls-for<decayed-typeof<let-cpo>>::get-state is initialized with a callable object equivalent to the following:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {auto& [_, fn, child] = sndr; using fn_t = decay_t<decltype(fn)>; using env_t = decltype(let-env(child)); using args_variant_t = see below; using ops2_variant_t = see below; struct state-type { fn_t fn; // exposition only env_t env; // exposition only args_variant_t args; // exposition only ops2_variant_t ops2; // exposition only}; return state-type{allocator-aware-forward(std::forward_like(fn), rcvr), let-env(child), {}, {}};}

9

#

Let Sigs be a pack of the arguments to the completion_signatures specialization named bycompletion_signatures_of_t<child-type, FWD-ENV-T(env_of_t)>.

Let LetSigs be a pack of those types in Sigs with a return type of decayed-typeof<set-cpo>.

Let as-tuple be an alias template such that as-tuple<Tag(Args...)> denotes the type decayed-tuple<Args...>.

Then args_variant_t denotes the type variant<monostate, as-tuple...> except with duplicate types removed.

10

#

Given a type Tag and a pack Args, let as-sndr2 be an alias template such that as-sndr2<Tag(Args...)> denotes the type call-result-t<F, decay_t&...>.

Then ops2_variant_t denotes the typevariant<monostate, connect_result_t<as-sndr2, receiver2<Rcvr, env_t>>...> except with duplicate types removed.

11

#

The requires-clause constraining the above lambda is satisfied if and only if the types args_variant_t and ops2_variant_t are well-formed.

12

#

The exposition-only function template let-bind has effects equivalent to:using args_t = decayed-tuple<Args...>;auto mkop2 = [&] {return connect( apply(std::move(state.fn), state.args.template emplace<args_t>(std::forward(args)...)), receiver2{rcvr, std::move(state.env)});}; start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));

13

#

impls-for<decayed-typeof>::complete is initialized with a callable object equivalent to the following:[]<class Tag, class... Args>(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void {if constexpr (same_as<Tag, decayed-typeof<set-cpo>>) {TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward(args)...)); } else { Tag()(std::move(rcvr), std::forward(args)...); }}

14

#

Let sndr and env be subexpressions, and let Sndr be decltype((sndr)).

Ifsender-for<Sndr, decayed-typeof<let-cpo>> is false, then the expression let-cpo.transform_env(sndr, env) is ill-formed.

Otherwise, it is equal to:auto& [_, _, child] = sndr;return JOIN-ENV(let-env(child), FWD-ENV(env));

15

#

Let the subexpression out_sndr denote the result of the invocation let-cpo(sndr, f) or an object equal to such, and let the subexpression rcvr denote a receiver such that the expression connect(out_sndr, rcvr) is well-formed.

The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([exec.async.ops]) that, when started:

invokes f when set-cpo is called with sndr's result datums,

makes its completion dependent on the completion of a sender returned by f, and

propagates the other completion operations sent by sndr.

33.9.12.11 execution::bulk, execution::bulk_chunked, and execution::bulk_unchunked [exec.bulk]

1

#

bulk, bulk_chunked, and bulk_unchunked run a task repeatedly for every index in an index space.

2

#

The names bulk, bulk_chunked, and bulk_unchunked denote pipeable sender adaptor objects.

Let bulk-algo be eitherbulk, bulk_chunked, or bulk_unchunked.

For subexpressions sndr, policy, shape, and f, letPolicy be remove_cvref_t<decltype(policy)>,Shape be decltype(auto(shape)), andFunc be decay_t<decltype((f))>.

If

decltype((sndr)) does not satisfy sender, or

is_execution_policy_v is false, or

Shape does not satisfy integral, or

Func does not model copy_constructible,

bulk-algo(sndr, policy, shape, f) is ill-formed.

3

#

Otherwise, the expression bulk-algo(sndr, policy, shape, f) is expression-equivalent to:

transform_sender(get-domain-early(sndr), make-sender(bulk-algo, product-type<see below, Shape, Func>{policy, shape, f}, sndr)) except that sndr is evaluated only once.

The first template argument of product-type is Policy if Policy models copy_constructible, andconst Policy& otherwise.

4

#

Let sndr and env be subexpressions such thatSndr is decltype((sndr)).

If sender-for<Sndr, bulk_t> is false, then the expression bulk.transform_sender(sndr, env) is ill-formed; otherwise, it is equivalent to:auto [_, data, child] = sndr;auto& [policy, shape, f] = data;auto new_f = [func = std::move(f)](Shape begin, Shape end, auto&&... vs)noexcept(noexcept(f(begin, vs...))) {while (begin != end) func(begin++, vs...);}return bulk_chunked(std::move(child), policy, shape, std::move(new_f));

[Note 1:

This causes the bulk(sndr, policy, shape, f) sender to be expressed in terms of bulk_chunked(sndr, policy, shape, f) when it is connected to a receiver whose execution domain does not customize bulk.

— end note]

5

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for bulk_chunked_t as follows:

🔗

namespace std::execution {template<>struct impls-for<bulk_chunked_t> : default-impls {static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types(); };}

The member impls-for<bulk_chunked_t>::complete is initialized with a callable object equivalent to the following lambda:[]<class Index, class State, class Rcvr, class Tag, class... Args>(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept-> void requires see below {if constexpr (same_as<Tag, set_value_t>) {auto& [policy, shape, f] = state; constexpr bool nothrow = noexcept(f(auto(shape), auto(shape), args...)); TRY-EVAL(rcvr, & noexcept(nothrow) { f(static_cast<decltype(auto(shape))>(0), auto(shape), args...); Tag()(std::move(rcvr), std::forward(args)...); }()); } else { Tag()(std::move(rcvr), std::forward(args)...); }}

The expression in the requires-clause of the lambda above istrue if and only if Tag denotes a type other than set_value_t or if the expression f(auto(shape), auto(shape), args...) is well-formed.

6

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for bulk_unchunked_t as follows:namespace std::execution {template<>struct impls-for<bulk_unchunked_t> : default-impls {static constexpr auto complete = see below; };}

The member impls-for<bulk_unchunked_t>::complete is initialized with a callable object equivalent to the following lambda:[]<class Index, class State, class Rcvr, class Tag, class... Args>(Index, State& state, Rcvr& rcvr, Tag, Args&&... args) noexcept-> void requires see below {if constexpr (same_as<Tag, set_value_t>) {auto& [shape, f] = state; constexpr bool nothrow = noexcept(f(auto(shape), args...)); TRY-EVAL(rcvr, & noexcept(nothrow) {for (decltype(auto(shape)) i = 0; i < shape; ++i) { f(auto(i), args...); } Tag()(std::move(rcvr), std::forward(args)...); }()); } else { Tag()(std::move(rcvr), std::forward(args)...); }}

The expression in the requires-clause of the lambda above is true if and only if Tag denotes a type other than set_value_t or if the expression f(auto(shape), args...) is well-formed.

🔗

template<class Sndr, class... Env> static consteval void check-types();

7

#

Effects: Equivalent to:auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>();auto fn = []<class... Ts>(set_value_t(*)(Ts...)) {if constexpr (invocable<remove_cvref_t<data-type>, Ts&...>)throw unspecified-exception();}; cs.for-each(overload-set(fn, {})); where unspecified-exception is a type derived from exception.

8

#

Let the subexpression out_sndr denote the result of the invocationbulk-algo(sndr, policy, shape, f) or an object equal to such, and let the subexpression rcvr denote a receiver such that the expression connect(out_sndr, rcvr) is well-formed.

The expression connect(out_sndr, rcvr) has undefined behavior unless it creates an asynchronous operation ([exec.async.ops]) that, when started:

  • (8.1)

    If sndr has a successful completion, whereargs is a pack of lvalue subexpressions referring to the value completion result datums of sndr, or decayed copies of those values if they model copy_constructible, then:

    • (8.1.1)

      If out_sndr also completes successfully, then:

      • [(8.1.1.1)](#exec.bulk-8.1.1.1)
        for bulk,
        

invokes f(i, args...) for every i of type Shape from 0 to shape;

+
      [(8.1.1.2)](#exec.bulk-8.1.1.2)
      for bulk_unchunked,

invokes f(i, args...) for every i of type Shape from 0 to shape; Recommended practice: The underlying scheduler should execute each iteration on a distinct execution agent.

+
      [(8.1.1.3)](#exec.bulk-8.1.1.3)
      for bulk_chunked,

invokes f(b, e, args...) zero or more times with pairs of b and e of type Shape in range [0, shape], such that b<e and for every i of type Shape from 0 to shape, there is exactly one invocation with a pair b and e, such that i is in the range [b, e).

  • (8.1.2)

    If out_sndr completes with set_error(rcvr, eptr), then the asynchronous operation may invoke a subset of the invocations of f before the error completion handler is called, and eptr is an exception_ptr containing either: + (8.1.2.1) an exception thrown by an invocation of f, or

    • [(8.1.2.2)](#exec.bulk-8.1.2.2)
      

a bad_alloc exception if the implementation fails to allocate required resources, or

+
      [(8.1.2.3)](#exec.bulk-8.1.2.3)

an exception derived from runtime_error.

  • (8.1.3)

    If out_sndr completes with set_stopped(rcvr), then the asynchronous operation may invoke a subset of the invocations of f before the stopped completion handler.

  • (8.2)

    If sndr does not complete with set_value, then the completion is forwarded to recv.

  • (8.3)

    For bulk-algo, the parameter policy describes the manner in which the execution of the asynchronous operations corresponding to these algorithms may be parallelized and the manner in which they apply f. Permissions and requirements on parallel algorithm element access functions ([algorithms.parallel.exec]) apply to f.

9

#

[Note 2:

The asynchronous operation corresponding tobulk-algo(sndr, policy, shape, f) can complete with set_stopped if cancellation is requested or ignore cancellation requests.

— end note]

33.9.12.12 execution::when_all [exec.when.all]

1

#

when_all and when_all_with_variant both adapt multiple input senders into a sender that completes when all input senders have completed.

when_all only accepts senders with a single value completion signature and on success concatenates all the input senders' value result datums into its own value completion operation.

when_all_with_variant(sndrs...) is semantically equivalent to when_all(into_variant(sndrs)...), where sndrs is a pack of subexpressions whose types model sender.

2

#

The names when_all and when_all_with_variant denote customization point objects.

Let sndrs be a pack of subexpressions, let Sndrs be a pack of the types decltype((sndrs))..., and let CD be the type common_type_t<decltype(get-domain-early(sndrs))...>.

Let CD2 be CD if CD is well-formed, anddefault_domain otherwise.

The expressions when_all(sndrs...) andwhen_all_with_variant(sndrs...) are ill-formed if any of the following is true:

sizeof...(sndrs) is 0, or

(sender && ...) is false.

3

#

The expression when_all(sndrs...) is expression-equivalent to:transform_sender(CD2(), make-sender(when_all, {}, sndrs...))

4

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for when_all_t as follows:

🔗

namespace std::execution {template<>struct impls-for<when_all_t> : default-impls {static constexpr auto get-attrs = see below; static constexpr auto get-env = see below; static constexpr auto get-state = see below; static constexpr auto start = see below; static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types(); };}

5

#

Let make-when-all-env be the following exposition-only function template:templateconstexpr auto make-when-all-env(inplace_stop_source& stop_src, // exposition only Env&& env) noexcept {return see below;}

Returns an object e such that

decltype(e) models queryable, and

e.query(get_stop_token) is expression-equivalent tostate.stop-src.get_token(), and

given a query object q with type other than cv stop_token_t and whose type satisfies forwarding-query,e.query(q) is expression-equivalent to get_env(rcvr).query(q).

6

#

Let when-all-env be an alias template such thatwhen-all-env denotes the typedecltype(make-
when-all-env
(declval<inplace_stop_source&>(), declval())).

🔗

template<class Sndr, class... Env> static consteval void check-types();

7

#

Let Is be the pack of integral template arguments of the integer_sequence specialization denoted byindices-for.

8

#

Effects: Equivalent to:auto fn = []() {auto cs = get_completion_signatures<Child, when-all-env...>(); if constexpr (cs.count-of(set_value) >= 2)throw unspecified-exception(); decay-copyable-result-datums(cs); // see [exec.snd.expos]};(fn.template operator()<child-type<Sndr, Is>>(), ...); where unspecified-exception is a type derived from exception.

9

#

Throws: Any exception thrown as a result of evaluating the Effects, or an exception of an unspecified type derived from exception when CD is ill-formed.

10

#

The member impls-for<when_all_t>::get-attrs is initialized with a callable object equivalent to the following lambda expression:[](auto&&, auto&&... child) noexcept {if constexpr (same_as<CD, default_domain>) {return env<>(); } else {return MAKE-ENV(get_domain, CD()); }}

11

#

The member impls-for<when_all_t>::get-env is initialized with a callable object equivalent to the following lambda expression:[]<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept {return make-when-all-env(state.stop-src, get_env(rcvr));}

12

#

The member impls-for<when_all_t>::get-state is initialized with a callable object equivalent to the following lambda expression:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(noexcept(e)) -> decltype(e) {return e;} where e is the expressionstd::forward(sndr).apply(make-state()) and where make-state is the following exposition-only class template:enum class disposition { started, error, stopped }; // exposition onlytemplatestruct make-state {template<class... Sndrs>auto operator()(auto, auto, Sndrs&&... sndrs) const {using values_tuple = see below; using errors_variant = see below; using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t>, on-stop-request>; struct state-type {void arrive(Rcvr& rcvr) noexcept { // exposition onlyif (0 == --count) {complete(rcvr); }}void complete(Rcvr& rcvr) noexcept; // exposition only atomic<size_t> count{sizeof...(sndrs)}; // exposition only inplace_stop_source stop_src{}; // exposition only atomic<disposition> disp{disposition::started}; // exposition only errors_variant errors{}; // exposition only values_tuple values{}; // exposition only optional<stop_callback> on_stop{nullopt}; // exposition only}; return state-type{}; }};

13

#

Let copy-fail be exception_ptr if decay-copying any of the child senders' result datums can potentially throw; otherwise, none-such, where none-such is an unspecified empty class type.

14

#

The alias values_tuple denotes the typetuple<value_types_of_t<Sndrs, FWD-ENV-T(env_of_t), decayed-tuple, optional>...> if that type is well-formed; otherwise, tuple<>.

15

#

The alias errors_variant denotes the type variant<none-such, copy-fail, Es...> with duplicate types removed, where Es is the pack of the decayed types of all the child senders' possible error result datums.

16

#

The membervoid state-type::complete(Rcvr& rcvr) noexcept behaves as follows:

If disp is equal to disposition::started, evaluates:auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); };auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); };

*on_stop*.reset();

apply([&](auto&... opts) noexcept { apply(set, tuple_cat(tie(*opts)...)); }, values);

Otherwise, if disp is equal to disposition::error, evaluates:on_stop.reset(); visit([&](Error& error) noexcept {if constexpr (same_as<Error, none-such>) { set_error(std::move(rcvr), std::move(error)); }}, errors);

Otherwise, evaluates:on_stop.reset(); set_stopped(std::move(rcvr));

17

#

The member impls-for<when_all_t>::start is initialized with a callable object equivalent to the following lambda expression:[]<class State, class Rcvr, class... Ops>( State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void { state.on_stop.emplace( get_stop_token(get_env(rcvr)), on-stop-request{state.stop_src}); if (state.stop_src.stop_requested()) { state.*on_stop.*reset(); set_stopped(std::move(rcvr)); } else {(start(ops), ...); }}

18

#

The member impls-for<when_all_t>::complete** is initialized with a callable object equivalent to the following lambda expression:[]<class Index, class State, class Rcvr, class Set, class... Args>(this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void {if constexpr (same_as<Set, set_error_t>) {if (disposition::error != state.disp.exchange(disposition::error)) { state.stop_src.request_stop(); TRY-EMPLACE-ERROR(state.errors, std::forward(args)...); }} else if constexpr (same_as<Set, set_stopped_t>) {auto expected = disposition::started; if (state.disp.compare_exchange_strong(expected, disposition::stopped)) { state.stop_src.request_stop(); }} else if constexpr (same_as<decltype(State::values), tuple<>>) {if (state.disp == disposition::started) {auto& opt = getIndex::value(state.values); TRY-EMPLACE-VALUE(complete, opt, std::forward(args)...); }} state.arrive(rcvr);} where TRY-EMPLACE-ERROR(v, e), for subexpressions v and e, is equivalent to:try { v.template emplace<decltype(auto(e))>(e);} catch (...) { v.template emplace<exception_ptr>(current_exception());} if the expression decltype(auto(e))(e) is potentially throwing; otherwise, v.template emplace<decltype(auto(e))>(e); and where TRY-EMPLACE-VALUE(c, o, as...), for subexpressions c, o, and pack of subexpressions as, is equivalent to:try { o.emplace(as...);} catch (...) { c(Index(), state, rcvr, set_error, current_exception()); return;} if the expression decayed-tuple<decltype(as)...>{as...} is potentially throwing; otherwise, o.emplace(as...).

19

#

The expression when_all_with_variant(sndrs...) is expression-equivalent to:transform_sender(CD2(), make-sender(when_all_with_variant, {}, sndrs...));

20

#

Given subexpressions sndr and env, ifsender-for<decltype((sndr)), when_all_with_variant_t> is false, then the expression when_all_with_variant.transform_sender(sndr, env) is ill-formed; otherwise, it is equivalent to:auto&& [_, _, ...child] = sndr;return when_all(into_variant(std::forward_like<decltype((sndr))>(child))...);

[Note 1:

This causes the when_all_with_variant(sndrs...) sender to become when_all(into_variant(sndrs)...) when it is connected with a receiver whose execution domain does not customize when_all_with_variant.

— end note]

33.9.12.13 execution::into_variant [exec.into.variant]

1

#

into_variant adapts a sender with multiple value completion signatures into a sender with just one value completion signature consisting of a variant of tuples.

2

#

The name into_variant denotes a pipeable sender adaptor object.

For a subexpression sndr, let Sndr be decltype((sndr)).

If Sndr does not satisfy sender,into_variant(sndr) is ill-formed.

3

#

Otherwise, the expression into_variant(sndr) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(into_variant, {}, sndr)) except that sndr is only evaluated once.

4

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for into_variant as follows:

🔗

namespace std::execution {template<>struct impls-for<into_variant_t> : default-impls {static constexpr auto get-state = see below; static constexpr auto complete = see below; template<class Sndr, class... Env>static consteval void check-types() {auto cs = get_completion_signatures<child-type, FWD-ENV-T(Env)...>(); decay-copyable-result-datums(cs); // see [exec.snd.expos]}};}

5

#

The member impls-for<into_variant_t>::get-state is initialized with a callable object equivalent to the following lambda:[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept-> type_identity<value_types_of_t<child-type, FWD-ENV-T(env_of_t)>> {return {};}

6

#

The member impls-for<into_variant_t>::complete is initialized with a callable object equivalent to the following lambda:[]<class State, class Rcvr, class Tag, class... Args>(auto, State, Rcvr& rcvr, Tag, Args&&... args) noexcept -> void {if constexpr (same_as<Tag, set_value_t>) {using variant_type = typename State::type; TRY-SET-VALUE(rcvr, variant_type(decayed-tuple<Args...>{std::forward(args)...})); } else { Tag()(std::move(rcvr), std::forward(args)...); }}

33.9.12.14 execution::stopped_as_optional [exec.stopped.opt]

1

#

stopped_as_optional maps a sender's stopped completion operation into a value completion operation as a disengaged optional.

The sender's value completion operation is also converted into an optional.

The result is a sender that never completes with stopped, reporting cancellation by completing with a disengaged optional.

2

#

The name stopped_as_optional denotes a pipeable sender adaptor object.

For a subexpression sndr, let Sndr be decltype((sndr)).

The expression stopped_as_optional(sndr) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(stopped_as_optional, {}, sndr)) except that sndr is only evaluated once.

3

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for stopped_as_optional_t as follows:

🔗

namespace std::execution {template<>struct impls-for<stopped_as_optional_t> : default-impls {template<class Sndr, class... Env>static consteval void check-types() {default-impls::check-types<Sndr, Env...>(); if constexpr (!requires {requires (same_as<void, single-sender-value-type<child-type, FWD-ENV-T(Env)...>>); })throw unspecified-exception(); }};} where unspecified-exception is a type derived from exception.

4

#

Let sndr and env be subexpressions such that Sndr is decltype((sndr)) andEnv is decltype((env)).

If sender-for<Sndr, stopped_as_optional_t> is false then the expression stopped_as_optional.transform_sender(sndr, env) is ill-formed; otherwise, if sender_in<child-type, FWD-ENV-T(Env)> is false, the expression stopped_as_optional.transform_sender(sndr, env) is equivalent to not-a-sender(); otherwise, it is equivalent to:auto&& [_, _, child] = sndr;using V = single-sender-value-type<child-type, FWD-ENV-T(Env)>;return let_stopped( then(std::forward_like(child), []<class... Ts>(Ts&&... ts) noexcept(is_nothrow_constructible_v<V, Ts...>) {return optional(in_place, std::forward(ts)...); }), noexcept { return just(optional()); });

33.9.12.15 execution::stopped_as_error [exec.stopped.err]

1

#

stopped_as_error maps an input sender's stopped completion operation into an error completion operation as a custom error type.

The result is a sender that never completes with stopped, reporting cancellation by completing with an error.

2

#

The name stopped_as_error denotes a pipeable sender adaptor object.

For some subexpressions sndr and err, let Sndr be decltype((sndr)) and let Err be decltype((err)).

If the type Sndr does not satisfy sender or if the type Err does not satisfy movable-value,stopped_as_error(sndr, err) is ill-formed.

Otherwise, the expression stopped_as_error(sndr, err) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(stopped_as_error, err, sndr)) except that sndr is only evaluated once.

3

#

Let sndr and env be subexpressions such that Sndr is decltype((sndr)) andEnv is decltype((env)).

If sender-for<Sndr, stopped_as_error_t> is false, then the expression stopped_as_error.transform_sender(sndr, env) is ill-formed; otherwise, it is equivalent to:auto&& [_, err, child] = sndr;using E = decltype(auto(err));return let_stopped( std::forward_like(child), err = std::forward_like(err) mutable noexcept(is_nothrow_move_constructible_v) {return just_error(std::move(err)); });

33.9.12.16 execution::associate [exec.associate]

1

#

associate tries to associate a sender with an async scope such that the scope can track the lifetime of any asynchronous operations created with the sender.

2

#

Let associate-data be the following exposition-only class template:

🔗

namespace std::execution {template<scope_token Token, sender Sender>struct associate-data { // exposition onlyusing wrap-sender = // exposition only remove_cvref_t<decltype(declval<Token&>().wrap(declval()))>; explicit associate-data(Token t, Sender&& s): sndr(t.wrap(std::forward(s))), token(t) {if (!token.try_associate())sndr.reset(); }associate-data(const associate-data& other)noexcept(is_nothrow_copy_constructible_v<wrap-sender> &&noexcept(other.token.try_associate())); associate-data(associate-data&& other)noexcept(is_nothrow_move_constructible_v<wrap-sender>); ~associate-data();

optional<pair<Token, wrap-sender>> release() && noexcept(is_nothrow_move_constructible_v<wrap-sender>); private: optional<wrap-sender> sndr; // exposition only Token token; // exposition only}; template<scope_token Token, sender Sender>associate-data(Token, Sender&&) -> associate-data<Token, Sender>;}

3

#

For an associate-data object a,a.sndr.has_value() is true if and only if an association was successfully made and is owned by a.

🔗

associate-data(const associate-data& other) noexcept(is_nothrow_copy_constructible_v<wrap-sender> && noexcept(other.token.try_associate()));

4

#

Constraints: copy_constructible<wrap-sender> is true.

5

#

Effects: Value-initializes sndr and initializes token with other.token.

If other.sndr.has_value() is false, no further effects; otherwise, calls token.try_associate() and, if that returns true, calls sndr.emplace(*other.sndr) and, if that exits with an exception, calls token.disassociate() before propagating the exception.

🔗

associate-data(associate-data&& other) noexcept(is_nothrow_move_constructible_v<wrap-sender>);

6

#

Effects: Initializes sndr with std::move(other.sndr) and initializes token with std::move(other.token) and then calls other.sndr.reset().

🔗

~associate-data();

7

#

Effects: If sndr.has_value() returns false then no effect; otherwise, invokes sndr.reset() before invoking token.disassociate().

🔗

optional<pair<Token, wrap-sender>> release() && noexcept(is_nothrow_move_constructible_v<wrap-sender>);

8

#

Effects: If sndr.has_value() returns false then returns an optional that does not contain a value; otherwise returns an optional containing a value of type pair<Token, wrap-sender> as if by:return optional(pair(token, std::move(*sndr)));

9

#

Postconditions: sndr does not contain a value.

10

#

The name associate denotes a pipeable sender adaptor object.

For subexpressions sndr and token:

If decltype((sndr)) does not satisfy sender, orremove_cvref_t<decltype((token))> does not satisfy scope_token, thenassociate(sndr, token) is ill-formed.

Otherwise, the expression associate(sndr, token) is expression-equivalent to:transform_sender(get-domain-early(sndr), make-sender(associate, associate-data(token, sndr))) except that sndr is evaluated only once.

11

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for associate_t as follows:

🔗

namespace std::execution {template<>struct impls-for<associate_t> : default-impls {static constexpr auto get-state = see below; // exposition onlystatic constexpr auto start = see below; // exposition onlytemplate<class Sndr, class... Env>static consteval void check-types() { // exposition onlyusing associate_data_t = remove_cvref_t<data-type>; using child_type_t = typename associate_data_t::wrap-sender; (void)get_completion_signatures<child_type_t, FWD-ENV-T(Env)...>(); }};}

12

#

The member impls-for<associate_t>::get-state is initialized with a callable object equivalent to the following lambda:

[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(see below) {auto [_, data] = std::forward(sndr); auto dataParts = std::move(data).release(); using scope_tkn = decltype(dataParts->first); using wrap_sender = decltype(dataParts->second); using op_t = connect_result_t<wrap_sender, Rcvr>; struct op_state {bool associated = false; // exposition onlyunion { Rcvr* rcvr; // exposition onlystruct { scope_tkn token; // exposition only op_t op; // exposition only} assoc; // exposition only}; explicit op_state(Rcvr& r) noexcept: rcvr(addressof(r)) {}explicit op_state(scope_tkn tkn, wrap_sender&& sndr, Rcvr& r) try: associated(true), assoc(tkn, connect(std::move(sndr), std::move(r))) {}catch (...) { tkn.disassociate(); throw; } op_state(op_state&&) = delete; ~op_state() {if (associated) {assoc.op.~op_t(); assoc.token.disassociate(); assoc.token.~scope_tkn(); }}void run() noexcept { // exposition onlyif (associated) start(assoc.op); else set_stopped(std::move(*rcvr)); }}; if (dataParts)return op_state{std::move(dataParts->first), std::move(dataParts->second), rcvr}; elsereturn op_state{rcvr};}

13

#

The expression in the noexcept clause ofimpls-for<associate_t>::get-state isis_nothrow_constructible_v<remove_cvref_t, Sndr> && is_nothrow_move_constructible_v<wrap-sender> &&nothrow-callable<connect_t, wrap-sender, Rcvr> where wrap-sender is the typeremove_cvref_t<data-type>::wrap-sender.

14

#

The member impls-for<associate_t>::start is initialized with a callable object equivalent to the following lambda:[](auto& state, auto&) noexcept -> void { state.run();}

15

#

The evaluation of associate(sndr, token) may cause side effects observable via token's associated async scope object.

33.9.12.17 Exposition-only execution::stop-when [exec.stop.when]

1

#

stop-when fuses an additional stop token t into a sender so that, upon connecting to a receiver r, the resulting operation state receives stop requests from botht and the token returned from get_stop_token(get_env(r)).

2

#

The name stop-when denotes an exposition-only sender adaptor.

For subexpressions sndr and token:

  • (2.1)

    If decltype((sndr)) does not satisfy sender, orremove_cvref_t<decltype((token))> does not satisfy stoppable_token, then stop-when(sndr, token) is ill-formed.

  • (2.2)

    Otherwise, if remove_cvref_t<decltype((token))> modelsunstoppable_token thenstop-when(sndr, token) is expression-equivalent tosndr.

  • (2.3)

    Otherwise,stop-when(sndr, token) returns a sender osndr. If osndr is connected to a receiver r, let rtoken be the result of get_stop_token(get_env(r)).

    • (2.3.1)

      If the type of rtoken models unstoppable_token then the effects of connecting osndr to r are equivalent toconnect(write_env(sndr, prop(get_stop_token, token)), r).

    • (2.3.2)

      Otherwise, the effects of connecting osndr to r are equivalent toconnect(write_env(sndr, prop(get_stop_token, stoken)), r) where stoken is an object of an exposition-only type stoken-t such that: + (2.3.2.1) stoken-t models stoppable_token;

      • [(2.3.2.2)](#exec.stop.when-2.3.2.2)
        

stoken.stop_requested() returns token.stop_requested() || rtoken.stop_reques-
ted();

+
      [(2.3.2.3)](#exec.stop.when-2.3.2.3)

stoken.stop_possible() returns token.stop_possible() || rtoken.stop_possible(); and

+
      [(2.3.2.4)](#exec.stop.when-2.3.2.4)

for types Fn and Init such that both invocable and constructible_from<Fn, Init> are modeled, stoken-t::callback_type models stoppable-callback-for<Fn, stoken-t, Init>. [Note 1: For an object fn of type Fn constructed from a value, init, of type Init, registering fn using stoken-t::callback_type(stoken, init) results in an invocation of fn when a callback registered with token or rtoken would be invoked. fn is invoked at most once. — end note]

33.9.12.18 execution::spawn_future [exec.spawn.future]

1

#

spawn_future attempts to associate the given input sender with the given token's async scope and, on success, eagerly starts the input sender; the return value is a sender that, when connected and started, completes with either the result of the eagerly-started input sender or withset_stopped if the input sender was not started.

2

#

The name spawn_future denotes a customization point object.

For subexpressions sndr, token, and env,

let Sndr be decltype((sndr)),

let Token be remove_cvref_t<decltype((token))>, and

let Env be remove_cvref_t<decltype((env))>.

If any ofsender,scope_token, orqueryable are not satisfied, the expression spawn_future(sndr, token, env) is ill-formed.

3

#

Let spawn-future-state-base be the exposition-only class template:

🔗

namespace std::execution {templatestruct spawn-future-state-base; // exposition onlytemplate<class... Sigs>struct spawn-future-state-base<completion_signatures<Sigs...>> { // exposition onlyusing variant-t = see below; // exposition only**variant-t result; // exposition onlyvirtual void complete() noexcept = 0; // exposition only};}

4

#

Let Sigs be the pack of arguments to the completion_signatures specialization provided as a parameter to the spawn-future-state-base class template.

Let as-tuple be an alias template that transforms a completion signature Tag(Args...) into the tuple specialization decayed-tuple<Tag, Args...>.

  • (4.1)

    If is_nothrow_constructible_v<decay_t, Arg> is true for every type Arg in every parameter pack Args in every completion signature Tag(Args...) in Sigs thenvariant-t denotes the typevariant<monostate, tuple<set_stopped_t>, as-tuple...>, except with duplicate types removed.

  • (4.2)

    Otherwisevariant-t denotes the typevariant<monostate, tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple...>, except with duplicate types removed.

5

#

Let spawn-future-receiver be the exposition-only class template:

🔗

namespace std::execution {templatestruct spawn-future-receiver { // exposition onlyusing receiver_concept = receiver_t; spawn-future-state-base* state; // exposition onlytemplate<class... T>void set_value(T&&... t) && noexcept {set-complete<set_value_t>(std::forward(t)...); }templatevoid set_error(E&& e) && noexcept {set-complete<set_error_t>(std::forward(e)); }void set_stopped() && noexcept {set-complete<set_stopped_t>(); }private:template<class CPO, class... T>void set-complete(T&&... t) noexcept { // exposition onlyconstexpr bool nothrow = (is_nothrow_constructible_v<decay_t, T> && ...); try {state->result.template emplace<decayed-tuple<CPO, T...>>(CPO{}, std::forward(t)...); }catch (...) {if constexpr (!nothrow) {using tuple_t = decayed-tuple<set_error_t, exception_ptr>; state->result.template emplace<tuple_t>(set_error_t{}, current_exception()); }}state->complete(); }};}

6

#

Let ssource-t be an unspecified type that models stoppable-source and let ssource be an lvalue of type ssource-t.

Let stoken-t be decltype(ssource.get_token()).

Let future-spawned-sender be the alias template:

template<sender Sender, class Env>using future-spawned-sender = // exposition onlydecltype(write_env(stop-when(declval(), declval<stoken-t>()), declval()));

7

#

Let spawn-future-state be the exposition-only class template:

🔗

namespace std::execution {template<class Alloc, scope_token Token, sender Sender, class Env>struct spawn-future-state // exposition only: spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>>> {using sigs-t = // exposition only completion_signatures_of_t<future-spawned-sender<Sender, Env>>; using receiver-t = // exposition only**spawn-future-receiver<sigs-t>; using op-t = // exposition only connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>; spawn-future-state(Alloc alloc, Sender&& sndr, Token token, Env env) // exposition only: alloc(std::move(alloc)), op(connect( write_env(stop-when(std::forward(sndr), ssource.get_token()), std::move(env)), receiver-t(this))), token(std::move(token)), associated(token.try_associate()) {if (associated) start(op); else set_stopped(receiver-t(this)); }void complete() noexcept override; // exposition onlyvoid consume(receiver auto& rcvr) noexcept; // exposition onlyvoid abandon() noexcept; // exposition onlyprivate:using alloc-t = // exposition onlytypename allocator_traits::template rebind_alloc<spawn-future-state>; alloc-t alloc; // exposition only**ssource-t ssource; // exposition only**op-t op; // exposition only Token token; // exposition onlybool associated; // exposition onlyvoid destroy() noexcept; // exposition only};}

8

#

For purposes of determining the existence of a data race,complete, consume, and abandon behave as atomic operations ([intro.multithread]).

These operations on a single object of a type that is a specialization of spawn-future-state appear to occur in a single total order.

🔗

void complete() noexcept;

9

#

Effects:

No effects if this invocation of complete happens before an invocation of consume or abandon on *this;

otherwise, if an invocation of consume on *this happens before this invocation of complete then there is a receiver, rcvr, registered and that receiver is completed as if by consume(rcvr);

otherwise,destroy is invoked.

🔗

void consume([receiver](exec.recv.concepts#concept:receiver "33.7.1Receiver concepts[exec.recv.concepts]") auto& rcvr) noexcept;

10

#

Effects:

If this invocation of consume happens before an invocation of complete on this thenrcvr is registered to be completed whencomplete* is subsequently invoked on *this;

otherwise,rcvr is completed as if by:std::move(this->result).visit([&rcvr](auto&& tuple) noexcept {if constexpr (same_as<remove_reference_t<decltype(tuple)>, monostate>) { apply([&rcvr](auto cpo, auto&&... vals) { cpo(std::move(rcvr), std::move(vals)...); }, std::move(tuple)); }});

🔗

void abandon() noexcept;

11

#

Effects:

If this invocation of abandon happens before an invocation of complete on *this then equivalent to:ssource.request_stop();

otherwise,destroy is invoked.

🔗

void destroy() noexcept;

12

#

Effects: Equivalent to:auto token = std::move(this->token);bool associated = this->associated;

{auto alloc = std::move(this->alloc);

allocator_traits<alloc-t>::destroy(alloc, this); allocator_traits<alloc-t>::deallocate(alloc, this, 1);}if (associated) token.disassociate();

13

#

The exposition-only class template impls-for ([exec.snd.expos]) is specialized for spawn_future_t as follows:

🔗

namespace std::execution {template<>struct impls-for<spawn_future_t> : default-impls {static constexpr auto start = see below; // exposition only};}

14

#

The member impls-for<spawn_future_t>::start is initialized with a callable object equivalent to the following lambda:[](auto& state, auto& rcvr) noexcept -> void { state->consume(rcvr);}

15

#

For the expression spawn_future(sndr, token, env) let new_sender be the expression token.wrap(sndr) and let alloc and senv be defined as follows:

if the expression get_allocator(env) is well-formed, thenalloc is the result of get_allocator(env) andsenv is the expression env;

otherwise, if the expression get_allocator(get_env(new_sender)) is well-formed, thenalloc is the result of get_allocator(get_env(new_sender)) andsenv is the expressionJOIN-ENV(prop(get_allocator, alloc), env);

otherwise,alloc is allocator() andsenv is the expression env.

16

#

The expression spawn_future(sndr, token, env) has the following effects:

  • (16.1)

    Uses alloc to allocate and construct an object s of a type that is a specialization of spawn-future-state from alloc, token.wrap(sndr), token, and senv. If an exception is thrown then any constructed objects are destroyed and any allocated memory is deallocated.

  • (16.2)

    Constructs an object u of a type that is a specialization of unique_ptr such that:

u.get() is equal to the address of s, and

u.get_deleter()(u.release()) is equivalent to u.release()->abandon().

  • (16.3)

    Returns make-sender(spawn_future, std::move(u)).

17

#

The expression spawn_future(sndr, token) is expression-equivalent tospawn_future(sndr, token, execution::env<>()).

33.9.13 Sender consumers [exec.consumers]

33.9.13.1 this_thread::sync_wait [exec.sync.wait]

1

#

this_thread::sync_wait and this_thread::sync_wait_with_variant are used to block the current thread of execution until the specified sender completes and to return its async result.

sync_wait mandates that the input sender has exactly one value completion signature.

2

#

Let sync-wait-env be the following exposition-only class type:namespace std::this_thread {struct sync-wait-env { execution::run_loop* loop; // exposition onlyauto query(execution::get_scheduler_t) const noexcept {return loop->get_scheduler(); }auto query(execution::get_delegation_scheduler_t) const noexcept {return loop->get_scheduler(); }};}

3

#

Let sync-wait-result-type andsync-wait-with-variant-result-type be exposition-only alias templates defined as follows:namespace std::this_thread {template<execution::sender_in<sync-wait-env> Sndr>using sync-wait-result-type = optional<execution::value_types_of_t<Sndr, sync-wait-env, decayed-tuple, type_identity_t>>; template<execution::sender_in<sync-wait-env> Sndr>using sync-wait-with-variant-result-type = optional<execution::value_types_of_t<Sndr, sync-wait-env>>;}

4

#

The name this_thread::sync_wait denotes a customization point object.

For a subexpression sndr, let Sndr be decltype((sndr)).

The expression this_thread::sync_wait(sndr) is expression-equivalent to the following, except that sndr is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait, sndr)Mandates:

  • (4.1)

    sender_in<Sndr, sync-wait-env> is true.

  • (4.2)

    The type sync-wait-result-type is well-formed.

  • (4.3)

    same_as<decltype(e), sync-wait-result-type> is true, where e is the apply_sender expression above.

5

#

Let sync-wait-state and sync-wait-receiver be the following exposition-only class templates:namespace std::this_thread {templatestruct sync-wait-state { // exposition only execution::run_loop loop; // exposition only exception_ptr error; // exposition only**sync-wait-result-type result; // exposition only}; templatestruct sync-wait-receiver { // exposition onlyusing receiver_concept = execution::receiver_t; sync-wait-state* state; // exposition onlytemplate<class... Args>void set_value(Args&&... args) && noexcept; templatevoid set_error(Error&& err) && noexcept; void set_stopped() && noexcept; sync-wait-env get_env() const noexcept { return {&state->loop}; }};}

🔗

template<class... Args> void set_value(Args&&... args) && noexcept;

6

#

Effects: Equivalent to:try {state->result.emplace(std::forward(args)...);} catch (...) {state->error = current_exception();}state->loop.finish();

🔗

template<class Error> void set_error(Error&& err) && noexcept;

7

#

Effects: Equivalent to:state->error = AS-EXCEPT-PTR(std::forward(err)); // see [exec.general]state->loop.finish();

🔗

void set_stopped() && noexcept;

8

#

Effects: Equivalent to state->loop.finish().

9

#

For a subexpression sndr, let Sndr be decltype((sndr)).

If sender_to<Sndr, sync-wait-receiver> is false, the expression sync_wait.apply_sender(sndr) is ill-formed; otherwise, it is equivalent to:sync-wait-state state;auto op = connect(sndr, sync-wait-receiver{&state}); start(op);

state.loop.run();if (state.error) { rethrow_exception(std::move(state.error));}return std::move(state.result);

10

#

The behavior of this_thread::sync_wait(sndr) is undefined unless:

  • (10.1)

    It blocks the current thread of execution ([defns.block]) with forward progress guarantee delegation ([intro.progress]) until the specified sender completes. [Note 1: The default implementation of sync_wait achieves forward progress guarantee delegation by providing a run_loop scheduler via the get_delegation_scheduler query on the sync-wait-receiver's environment. The run_loop is driven by the current thread of execution. — end note]

  • (10.2)

    It returns the specified sender's async results as follows:

    • (10.2.1)

      For a value completion, the result datums are returned in a tuple in an engaged optional object.

    • (10.2.2)

      For an error completion, an exception is thrown.

    • (10.2.3)

      For a stopped completion, a disengaged optional object is returned.

33.9.13.2 this_thread::sync_wait_with_variant [exec.sync.wait.var]

1

#

The name this_thread::sync_wait_with_variant denotes a customization point object.

For a subexpression sndr, let Sndr be decltype(into_variant(sndr)).

The expression this_thread::sync_wait_with_variant(sndr) is expression-equivalent to the following, except sndr is evaluated only once:apply_sender(get-domain-early(sndr), sync_wait_with_variant, sndr)Mandates:

  • (1.1)

    sender_in<Sndr, sync-wait-env> is true.

  • (1.2)

    The type sync-wait-with-variant-result-type is well-formed.

  • (1.3)

    same_as<decltype(e), sync-wait-with-variant-result-type> is true, where e is the apply_sender expression above.

2

#

The expression sync_wait_with_variant.apply_sender(sndr) is equivalent to:using result_type = sync-wait-with-variant-result-type;if (auto opt_value = sync_wait(into_variant(sndr))) {return result_type(std::move(get<0>(*opt_value)));}return result_type(nullopt);

3

#

The behavior of this_thread::sync_wait_with_variant(sndr) is undefined unless:

  • (3.1)

    It blocks the current thread of execution ([defns.block]) with forward progress guarantee delegation ([intro.progress]) until the specified sender completes. [Note 1: The default implementation of sync_wait_with_variant achieves forward progress guarantee delegation by relying on the forward progress guarantee delegation provided by sync_wait. — end note]

  • (3.2)

    It returns the specified sender's async results as follows:

    • (3.2.1)

      For a value completion, the result datums are returned in an engaged optional object that contains a variant of tuples.

    • (3.2.2)

      For an error completion, an exception is thrown.

    • (3.2.3)

      For a stopped completion, a disengaged optional object is returned.

33.9.13.3 execution::spawn [exec.spawn]

1

#

spawn attempts to associate the given input sender with the given token's async scope and, on success, eagerly starts the input sender.

2

#

The name spawn denotes a customization point object.

For subexpressions sndr, token, and env,

let Sndr be decltype((sndr)),

let Token be remove_cvref_t<decltype((token))>, and

let Env be remove_cvref_t<decltype((env))>.

If any ofsender,scope_token, orqueryable are not satisfied, the expression spawn(sndr, token, env) is ill-formed.

3

#

Let spawn-state-base be the exposition-only class:

🔗

namespace std::execution {struct spawn-state-base { // exposition onlyvirtual void complete() noexcept = 0; // exposition only};}

4

#

Let spawn-receiver be the exposition-only class:

🔗

namespace std::execution {struct spawn-receiver { // exposition onlyusing receiver_concept = receiver_t; spawn-state-base* state; // exposition onlyvoid set_value() && noexcept { state->complete(); }void set_stopped() && noexcept { state->complete(); }};}

5

#

Let spawn-state be the exposition-only class template:

🔗

namespace std::execution {template<class Alloc, scope_token Token, sender Sender>struct spawn-state : spawn-state-base { // exposition onlyusing op-t = connect_result_t<Sender, spawn-receiver>; // exposition only**spawn-state(Alloc alloc, Sender&& sndr, Token token); // exposition onlyvoid complete() noexcept override; // exposition onlyvoid run(); // exposition onlyprivate:using alloc-t = // exposition onlytypename allocator_traits::template rebind_alloc<spawn-state>; alloc-t alloc; // exposition only**op-t op; // exposition only Token token; // exposition onlyvoid destroy() noexcept; // exposition only};}

🔗

spawn-state(Alloc alloc, Sender&& sndr, Token token);

6

#

Effects: Initializesalloc with alloc,token with token, andop with:connect(std::move(sndr), spawn-receiver(this))

🔗

void run();

7

#

Effects: Equivalent to:if (token.try_associate()) start(op);elsedestroy();

🔗

void complete() noexcept override;

8

#

Effects: Equivalent to:auto token = std::move(this->token);

destroy(); token.disassociate();

🔗

void destroy() noexcept;

9

#

Effects: Equivalent to:auto alloc = std::move(this->alloc);

allocator_traits<alloc-t>::destroy(alloc, this); allocator_traits<alloc-t>::deallocate(alloc, this, 1);

10

#

For the expression spawn(sndr, token, env) let new_sender be the expression token.wrap(sndr) and let alloc and senv be defined as follows:

if the expression get_allocator(env) is well-formed, thenalloc is the result of get_allocator(env) andsenv is the expression env,

otherwise if the expression get_allocator(get_env(new_sender)) is well-formed, thenalloc is the result of get_allocator(get_env(new_sender)) andsenv is the expression JOIN-ENV(prop(get_allocator, alloc), env),

otherwisealloc is allocator() andsenv is the expression env.

11

#

The expression spawn(sndr, token, env) is of type void and has the following effects:

Uses alloc to allocate and construct an object o of type that is a specialization of spawn-state fromalloc, write_env(token.wrap(sndr), senv), and token and then invokes o.run(). If an exception is thrown then any constructed objects are destroyed and any allocated memory is deallocated.

12

#

The expression spawn(sndr, token) is expression-equivalent tospawn(sndr, token, execution::env<>()).