33 Execution control library [exec]

33.13 Coroutine utilities [exec.coro.util]

33.13.1 execution​::​as_awaitable [exec.as.awaitable]

as_awaitable transforms an object into one that is awaitable within a particular coroutine.
Subclause [exec.coro.util] makes use of the following exposition-only entities: namespace std::execution { template<class Sndr, class Promise> concept awaitable-sender = single-sender<Sndr, env_of_t<Promise>> && sender_to<Sndr, awaitable-receiver> && // see below requires (Promise& p) { { p.unhandled_stopped() } -> convertible_to<coroutine_handle<>>; }; template<class Sndr> concept has-queryable-await-completion-adaptor = // exposition only sender<Sndr> && requires(Sndr&& sender) { get_await_completion_adaptor(get_env(sender)); }; template<class Sndr, class Promise> class sender-awaitable; // exposition only }
The type sender-awaitable<Sndr, Promise> is equivalent to:
namespace std::execution { template<class Sndr, class Promise> class sender-awaitable { struct unit {}; // exposition only using value-type = // exposition only single-sender-value-type<Sndr, env_of_t<Promise>>; using result-type = // exposition only conditional_t<is_void_v<value-type>, unit, value-type>; struct awaitable-receiver; // exposition only variant<monostate, result-type, exception_ptr> result{}; // exposition only connect_result_t<Sndr, awaitable-receiver> state; // exposition only public: sender-awaitable(Sndr&& sndr, Promise& p); static constexpr bool await_ready() noexcept { return false; } void await_suspend(coroutine_handle<Promise>) noexcept { start(state); } value-type await_resume(); }; }
awaitable-receiver is equivalent to: struct awaitable-receiver { using receiver_concept = receiver_t; variant<monostate, result-type, exception_ptr>* result-ptr; // exposition only coroutine_handle<Promise> continuation; // exposition only // see below };
Let rcvr be an rvalue expression of type awaitable-receiver, let crcvr be a const lvalue that refers to rcvr, let vs be a pack of subexpressions, and let err be an expression of type Err.
Then:
  • If constructible_from<result-type, decltype((vs))...> is satisfied, the expression set_value(
    rcvr, vs...)
    is equivalent to: try { rcvr.result-ptr->template emplace<1>(vs...); } catch(...) { rcvr.result-ptr->template emplace<2>(current_exception()); } rcvr.continuation.resume();
    Otherwise, set_value(rcvr, vs...) is ill-formed.
  • The expression set_error(rcvr, err) is equivalent to: rcvr.result-ptr->template emplace<2>(AS-EXCEPT-PTR(err)); // see [exec.general] rcvr.continuation.resume();
  • The expression set_stopped(rcvr) is equivalent to: static_cast<coroutine_handle<>>(rcvr.continuation.promise().unhandled_stopped()).resume();
  • For any expression tag whose type satisfies forwarding-query and for any pack of subexpressions as, get_env(crcvr).query(tag, as...) is expression-equivalent to: tag(get_env(as_const(crcvr.continuation.promise())), as...)
sender-awaitable(Sndr&& sndr, Promise& p);
Effects: Initializes state with connect(std::forward<Sndr>(sndr), awaitable-receiver{addressof(result), coroutine_handle<Promise>::from_promise(p)})
value-type await_resume();
Effects: Equivalent to: if (result.index() == 2) rethrow_exception(get<2>(result)); if constexpr (!is_void_v<value-type>) return std::forward<value-type>(get<1>(result));
as_awaitable is a customization point object.
For subexpressions expr and p where p is an lvalue, Expr names the type decltype((expr)) and Promise names the type decay_t<decltype((p))>, as_awaitable(expr, p) is expression-equivalent to, except that the evaluations of expr and p are indeterminately sequenced:
  • expr.as_awaitable(p) if that expression is well-formed.
    Mandates: is-awaitable<A, Promise> is true, where A is the type of the expression above.
  • Otherwise, (void(p), expr) if is-awaitable<Expr, U> is true, where U is an unspecified class type that is not Promise and that lacks a member named await_transform.
    Preconditions: is-awaitable<Expr, Promise> is true and the expression co_await expr in a coroutine with promise type U is expression-equivalent to the same expression in a coroutine with promise type Promise.
  • Otherwise, sender-awaitable{adapted-expr, p} if has-queryable-await-completion-adaptor<Expr> and awaitable-sender<decltype((adapted-expr)), Promise> are both satisfied, where adapted-expr is get_await_completion_adaptor(get_env(expr))(expr), except that expr is evaluated only once.
  • Otherwise, sender-awaitable{expr, p} if awaitable-sender<Expr, Promise> is true.
  • Otherwise, (void(p), expr).

33.13.2 execution​::​with_awaitable_senders [exec.with.awaitable.senders]

with_awaitable_senders, when used as the base class of a coroutine promise type, makes senders awaitable in that coroutine type.
In addition, it provides a default implementation of unhandled_stopped such that if a sender completes by calling set_stopped, it is treated as if an uncatchable "stopped" exception were thrown from the await-expression.
[Note 1: 
The coroutine is never resumed, and the unhandled_stopped of the coroutine caller's promise type is called.
— end note]
namespace std::execution { template<class-type Promise> struct with_awaitable_senders { template<class OtherPromise> requires (!same_as<OtherPromise, void>) void set_continuation(coroutine_handle<OtherPromise> h) noexcept; coroutine_handle<> continuation() const noexcept { return continuation; } coroutine_handle<> unhandled_stopped() noexcept { return stopped-handler(continuation.address()); } template<class Value> see below await_transform(Value&& value); private: [[noreturn]] static coroutine_handle<> default-unhandled-stopped(void*) noexcept { // exposition only terminate(); } coroutine_handle<> continuation{}; // exposition only coroutine_handle<> (*stopped-handler)(void*) noexcept = // exposition only &default-unhandled-stopped; }; }
template<class OtherPromise> requires (!same_as<OtherPromise, void>) void set_continuation(coroutine_handle<OtherPromise> h) noexcept;
Effects: Equivalent to: continuation = h; if constexpr ( requires(OtherPromise& other) { other.unhandled_stopped(); } ) { stopped-handler = [](void* p) noexcept -> coroutine_handle<> { return coroutine_handle<OtherPromise>::from_address(p) .promise().unhandled_stopped(); }; } else { stopped-handler = &default-unhandled-stopped; }
template<class Value> call-result-t<as_awaitable_t, Value, Promise&> await_transform(Value&& value);
Effects: Equivalent to: return as_awaitable(std::forward<Value>(value), static_cast<Promise&>(*this));

33.13.3 execution​::​affine_on [exec.affine.on]

affine_on adapts a sender into one that completes on the specified scheduler.
If the algorithm determines that the adapted sender already completes on the correct scheduler it can avoid any scheduling operation.
The name affine_on denotes a pipeable sender adaptor object.
For subexpressions sch and sndr, if decltype((sch)) does not satisfy scheduler, or decltype((sndr)) does not satisfy sender, affine_on(sndr, sch) is ill-formed.
Otherwise, the expression affine_on(sndr, sch) is expression-equivalent to: transform_sender(get-domain-early(sndr), make-sender(affine_on, sch, sndr)) except that sndr is evaluated only once.
The exposition-only class template impls-for is specialized for affine_on_t as follows:
namespace std::execution { template<> struct impls-for<affine_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))); }; }; }
Let out_sndr be a subexpression denoting a sender returned from affine_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 to out_rcvr.
Calling start(op) will 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 the current execution resource is the same as the execution resource associated with sch, the completion operation on out_rcvr may be called before start(op) completes.
If scheduling onto sch fails, an error completion on out_rcvr shall be executed on an unspecified execution agent.

33.13.4 execution​::​inline_scheduler [exec.inline.scheduler]

namespace std::execution { class inline_scheduler { class inline-sender; // exposition only template<receiver R> class inline-state; // exposition only public: using scheduler_concept = scheduler_t; constexpr inline-sender schedule() noexcept { return {}; } constexpr bool operator==(const inline_scheduler&) const noexcept = default; }; }
inline_scheduler is a class that models scheduler ([exec.sched]).
All objects of type inline_scheduler are equal.
inline-sender is an exposition-only type that satisfies sender.
The type completion_signatures_of_t<inline-sender> is completion_signatures<set_value_t()>.
Let sndr be an expression of type inline-sender, let rcvr be an expression such that receiver_of<decltype((rcvr)), CS> is true where CS is completion_signatures<set_value_t()>, then:
  • the expression connect(sndr, rcvr) has type inline-state<remove_cvref_t<decltype((rcvr))>> and is potentially-throwing if and only if ((void)sndr, auto(rcvr)) is potentially-throwing, and
  • the expression get_completion_scheduler<set_value_t>(get_env(sndr)) has type​ inline_scheduler and is potentially-throwing if and only if get_env(sndr) is potentially-throwing.
Let o be a non-const lvalue of type inline-state<Rcvr>, and let REC(o) be a non-const lvalue reference to an object of type Rcvr that was initialized with the expression rcvr passed to an invocation of connect that returned o, then:
  • the object to which REC(o) refers remains valid for the lifetime of the object to which o refers, and
  • the expression start(o) is equivalent to set_value(std​::​move(REC(o))).

33.13.5 execution​::​task_scheduler [exec.task.scheduler]

namespace std::execution { class task_scheduler { class ts-sender; // exposition only template<receiver R> class state; // exposition only public: using scheduler_concept = scheduler_t; template<class Sch, class Allocator = allocator<void>> requires (!same_as<task_scheduler, remove_cvref_t<Sch>>) && scheduler<Sch> explicit task_scheduler(Sch&& sch, Allocator alloc = {}); ts-sender schedule(); friend bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept; template<class Sch> requires (!same_as<task_scheduler, Sch>) && scheduler<Sch> friend bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept; private: shared_ptr<void> sch_; // exposition only }; }
task_scheduler is a class that models scheduler ([exec.sched]).
Given an object s of type task_scheduler, let SCHED(s) be the object owned by s.sch_.
template<class Sch, class Allocator = allocator<void>> requires(!same_as<task_scheduler, remove_cvref_t<Sch>>) && scheduler<Sch> explicit task_scheduler(Sch&& sch, Allocator alloc = {});
Effects: Initialize sch_ with allocate_shared<remove_cvref_t<Sch>>(alloc,​ std​::​forward<Sch>(sch)).
Recommended practice: Implementations should avoid the use of dynamically allocated memory for small scheduler objects.
Remarks: Any allocations performed by construction of ts-sender or state objects resulting from calls on *this are performed using a copy of alloc.
ts-sender schedule();
Effects: Returns an object of type ts-sender containing a sender initialized with schedule(SCHED(*this)).
bool operator==(const task_scheduler& lhs, const task_scheduler& rhs) noexcept;
Effects: Equivalent to: return lhs == SCHED(rhs);
template<class Sch> requires (!same_as<task_scheduler, Sch>) && scheduler<Sch> bool operator==(const task_scheduler& lhs, const Sch& rhs) noexcept;
Returns: false if type of SCHED(lhs) is not Sch, otherwise SCHED(lhs) == rhs;
namespace std::execution { class task_scheduler::ts-sender { // exposition only public: using sender_concept = sender_t; template<receiver Rcvr> state<Rcvr> connect(Rcvr&& rcvr); }; } ts-sender is an exposition-only class that models sender ([exec.snd]) and for which completion_signatures_of_t<ts-sender> denotes: completion_signatures< set_value_t(), set_error_t(error_code), set_error_t(exception_ptr), set_stopped_t()>
Let sch be an object of type task_scheduler and let sndr be an object of type ts-sender obtained from schedule(sch).
Then get_completion_scheduler<set_value_t>(get_env(sndr)) == sch is true.
The object SENDER(sndr) is the sender object contained by sndr or an object move constructed from it.
template<receiver Rcvr> state<Rcvr> connect(Rcvr&& rcvr);
Effects: Let r be an object of a type that models receiver and whose completion handlers result in invoking the corresponding completion handlers of rcvr or copy thereof.
Returns an object of type state<Rcvr> containing an operation state object initialized with connect(SENDER(*this), std​::​move(r)).
namespace std::execution { template<receiver R> class task_scheduler::state { // exposition only public: using operation_state_concept = operation_state_t; void start() & noexcept; }; } state is an exposition-only class template whose specializations model operation_state ([exec.opstate]).
void start() & noexcept;
Effects: Equivalent to start(st) where st is the operation state object contained by *this.

33.13.6 execution​::​task [exec.task]

33.13.6.1 task overview [task.overview]

The task class template represents a sender that can be used as the return type of coroutines.
The first template parameter T defines the type of the value completion datum ([exec.async.ops]) if T is not void.
Otherwise, there are no value completion datums.
Inside coroutines returning task<T, E> the operand of co_return (if any) becomes the argument of set_value.
The second template parameter Environment is used to customize the behavior of task.

33.13.6.2 Class template task [task.class]

namespace std::execution { template<class T, class Environment> class task { // [task.state] template<receiver Rcvr> class state; // exposition only public: using sender_concept = sender_t; using completion_signatures = see below; using allocator_type = see below; using scheduler_type = see below; using stop_source_type = see below; using stop_token_type = decltype(declval<stop_source_type>().get_token()); using error_types = see below; // [task.promise] class promise_type; task(task&&) noexcept; ~task(); template<receiver Rcvr> state<Rcvr> connect(Rcvr&& rcvr); private: coroutine_handle<promise_type> handle; // exposition only }; }
task<T, E> models sender ([exec.snd]) if T is void, a reference type, or a cv-unqualified non-array object type and E is a class type.
Otherwise a program that instantiates the definition of task<T, E> is ill-formed.
The nested types of task template specializations are determined based on the Environment parameter:
  • allocator_type is Environment​::​allocator_type if that qualified-id is valid and denotes a type, allocator<byte> otherwise.
  • scheduler_type is Environment​::​scheduler_type if that qualified-id is valid and denotes a type, task_scheduler otherwise.
  • stop_source_type is Environment​::​stop_source_type if that qualified-id is valid and denotes a type, inplace_stop_source otherwise.
  • error_types is Environment​::​error_types if that qualified-id is valid and denotes a type, completion_signatures<set_error_t(exception_ptr)> otherwise.
A program is ill-formed if error_types is not a specialization of completion_signatures<ErrorSigs...> or ErrorSigs contains an element which is not of the form set_error_t(E) for some type E.
The type alias completion_signatures is a specialization of execution​::​completion_signatures with the template arguments (in unspecified order):
  • set_value_t() if T is void, and set_value_t(T) otherwise;
  • template arguments of the specialization of execution​::​completion_signatures denoted by error_types; and
  • set_stopped_t().
allocator_type shall meet the Cpp17Allocator requirements.

33.13.6.3 task members [task.members]

task(task&& other) noexcept;
Effects: Initializes handle with exchange(other.handle, {}).
~task();
Effects: Equivalent to: if (handle) handle.destroy();
template<receiver Rcvr> state<Rcvr> connect(Rcvr&& recv);
Preconditions: bool(handle) is true.
Effects: Equivalent to: return state<Rcvr>(exchange(handle, {}), std::forward<Rcvr>(recv));

33.13.6.4 Class template task​::​state [task.state]

namespace std::execution { template<class T, class Environment> template<receiver Rcvr> class task<T, Environment>::state { // exposition only public: using operation_state_concept = operation_state_t; template<class R> state(coroutine_handle<promise_type> h, R&& rr); ~state(); void start() & noexcept; private: using own-env-t = see below; // exposition only coroutine_handle<promise_type> handle; // exposition only remove_cvref_t<Rcvr> rcvr; // exposition only own-env-t own-env; // exposition only Environment environment; // exposition only }; }
The type own-env-t is Environment​::​template env_type<decltype(get_env(​declval​<Rcvr>()))> if that qualified-id is valid and denotes a type, env<> otherwise.
template<class R> state(coroutine_handle<promise_type> h, R&& rr);
Effects: Initializes
  • handle with std​::​move(h);
  • rcvr with std​::​forward<R>(rr);
  • own-env with own-env-t(get_env(rcvr)) if that expression is valid and own-env-t() otherwise.
    If neither of these expressions is valid, the program is ill-formed.
  • environment with Environment(own-env) if that expression is valid, otherwise Environment(​get_env(rcvr)) if this expression is valid, otherwise Environment().
    If neither of these expressions is valid, the program is ill-formed.
~state();
Effects: Equivalent to: if (handle) handle.destroy();
void start() & noexcept;
Effects: Let prom be the object handle.promise().
Associates STATE(prom), RCVR(prom), and SCHED(prom) with *this as follows:
  • STATE(prom) is *this.
  • RCVR(prom) is rcvr.
  • SCHED(prom) is the object initialized with scheduler_type(get_scheduler(get_env(rcvr))) if that expression is valid and scheduler_type() otherwise.
    If neither of these expressions is valid, the program is ill-formed.
Let st be get_stop_token(get_env(rcvr)).
Initializes prom.token and prom.source such that
  • prom.token.stop_requested() returns st.stop_requested();
  • prom.token.stop_possible() returns st.stop_possible(); and
  • for types Fn and Init such that both invocable<Fn> and constructible_from<Fn, Init> are modeled, stop_token_type​::​callback_type<Fn> models stoppable-callback-for<Fn,​ stop_token_type, Init>.
After that invokes handle.resume().

33.13.6.5 Class task​::​promise_type [task.promise]

namespace std::execution { template<class T, class Environment> class task<T, Environment>::promise_type { public: template<class... Args> promise_type(const Args&... args); task get_return_object() noexcept; auto initial_suspend() noexcept; auto final_suspend() noexcept; void uncaught_exception(); coroutine_handle<> unhandled_stopped(); void return_void(); // present only if is_void_v<T> is true; template<class V> void return_value(V&& value); // present only if is_void_v<T> is false; template<class E> unspecified yield_value(with_error<E> error); template<class A> auto await_transform(A&& a); template<class Sch> auto await_transform(change_coroutine_scheduler<Sch> sch); unspecified get_env() const noexcept; template<class... Args> void* operator new(size_t size, Args&&... args); void operator delete(void* pointer, size_t size) noexcept; private: using error-variant = see below; // exposition only allocator_type alloc; // exposition only stop_source_type source; // exposition only stop_token_type token; // exposition only optional<T> result; // exposition only; present only if is_void_v<T> is false; error-variant errors; // exposition only }; }
Let prom be an object of promise_type and let tsk be the task object created by prom.get_return_object().
The description below refers to objects STATE(prom), RCVR(prom), and SCHED(prom) associated with tsk during evaluation of task​::​state<Rcvr>​::​start for some receiver Rcvr.
error-variant is a variant<monostate, remove_cvref_t<E>...>, with duplicate types removed, where E... are template arguments of the specialization of execution​::​completion_signatures denoted by error_types.
template<class... Args> promise_type(const Args&... args);
Mandates: The first parameter of type allocator_arg_t (if any) is not the last parameter.
Effects: If Args contains an element of type allocator_arg_t then alloc is initialized with the corresponding next element of args.
Otherwise, alloc is initialized with allocator_type().
task get_return_object() noexcept;
Returns: A task object whose member handle is coroutine_handle<promise_type>​::​​from_promise​(*this).
auto initial_suspend() noexcept;
Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for
  • the calling coroutine to be suspended,
  • the coroutine to be resumed on an execution agent of the execution resource associated with SCHED(*this).
auto final_suspend() noexcept;
Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the completion of the asynchronous operation associated with STATE(*this) by invoking:
  • set_error(std​::​move(RCVR(*this)), std​::​move(e)) if errors.index() is greater than zero and e is the value held by errors, otherwise
  • set_value(std​::​move(RCVR(*this))) if is_void<T> is true, and otherwise
  • set_value(std​::​move(RCVR(*this)), *result).
template<class Err> auto yield_value(with_error<Err> err);
Mandates: std​::​move(err.error) is convertible to exactly one of the set_error_t argument types of error_types.
Let Cerr be that type.
Returns: An awaitable object of unspecified type ([expr.await]) whose member functions arrange for the calling coroutine to be suspended and then completes the asynchronous operation associated with STATE(*this) by invoking set_error(std​::​move(RCVR(*this)), Cerr(std​::​move(err.error))).
template<sender Sender> auto await_transform(Sender&& sndr) noexcept;
Returns: If same_as<inline_scheduler, scheduler_type> is true returns as_awaitable(​std​::​​forward<Sender>(sndr), *this); otherwise returns as_awaitable(affine_on(​std​::​​forward<Sender>(sndr), SCHED(*this)), *this).
template<class Sch> auto await_transform(change_coroutine_scheduler<Sch> sch) noexcept;
Effects: Equivalent to: return await_transform(just(exchange(SCHED(*this), scheduler_type(sch.scheduler))), *this);
void uncaught_exception();
Effects: If the signature set_error_t(exception_ptr) is not an element of error_types, calls terminate() ([except.terminate]).
Otherwise, stores current_exception() into errors.
coroutine_handle<> unhandled_stopped();
Effects: Completes the asynchronous operation associated with STATE(*this) by invoking set_stopped(std​::​move(RCVR(*this))).
Returns: noop_coroutine().
unspecified get_env() const noexcept;
Returns: An object env such that queries are forwarded as follows:
  • env.query(get_scheduler) returns scheduler_type(SCHED(*this)).
  • env.query(get_allocator) returns alloc.
  • env.query(get_stop_token) returns token.
  • For any other query q and arguments a... a call to env.query(q, a...) returns STATE(*this).
    environment.query(q, a...) if this expression is well-formed and forwarding_query(q) is well-formed and is true.
    Otherwise env.query(q, a...) is ill-formed.
template<class... Args> void* operator new(size_t size, const Args&... args);
If there is no parameter with type allocator_arg_t then let alloc be allocator_type().
Otherwise, let arg_next be the parameter following the first allocator_arg_t parameter, and let alloc be allocator_type(arg_next).
Let PAlloc be allocator_traits<allocator_type>​::​template rebind_alloc<U>, where U is an unspecified type whose size and alignment are both __STDCPP_DEFAULT_NEW_ALIGNMENT__.
Mandates:
  • The first parameter of type allocator_arg_t (if any) is not the last parameter.
  • allocator_type(arg_next) is a valid expression if there is a parameter of type allocator_arg_t.
  • allocator_traits<PAlloc>​::​pointer is a pointer type.
Effects: Initializes an allocator palloc of type PAlloc with alloc.
Uses palloc to allocate storage for the smallest array of U sufficient to provide storage for a coroutine state of size size, and unspecified additional state necessary to ensure that operator delete can later deallocate this memory block with an allocator equal to palloc.
Returns: A pointer to the allocated storage.
void operator delete(void* pointer, size_t size) noexcept;
Preconditions: pointer was returned from an invocation of the above overload of operator new with a size argument equal to size.
Effects: Deallocates the storage pointed to by pointer using an allocator equal to that used to allocate it.