" << i << std::endl; } template task g2(Args&&...) { int i = co_await f(); std::cout << "f() => " << i << std::endl; } task g3(int a, ...) { int i = co_await f(); std::cout << "f() => " << i << std::endl; } — end example]">

[dcl.fct.def.coroutine] (original) (raw)

9 Declarations [dcl]

9.6 Function definitions [dcl.fct.def]

9.6.4 Coroutine definitions [dcl.fct.def.coroutine]

[Example 1: task<int> f(); task<void> g1() { int i = co_await f(); std::cout << "f() => " << i << std::endl;} template <typename... Args>task<void> g2(Args&&...) { int i = co_await f(); std::cout << "f() => " << i << std::endl;}task<void> g3(int a, ...) { int i = co_await f(); std::cout << "f() => " << i << std::endl;} — _end example_]

The promise type of a coroutine isstd​::​coroutine_traits<R, P, …, P>​::​promise_type, whereR is the return type of the function, and is the sequence of types of the non-object function parameters, preceded by the type of the object parameter ([dcl.fct]) if the coroutine is a non-static member function.

The promise type shall be a class type.

In the following, is an lvalue of type , where denotes the object parameter and denotes the non-object function parameter for an implicit object member function, and denotes the function parameter otherwise.

For an implicit object member function, is an lvalue that denotes *this; any other is an lvalue that denotes the parameter copy corresponding to , as described below.

A coroutine behaves as if the top-level cv-qualifiers in allparameter-declarations in the declarator of its function-definitionwere removed and its function-body were replaced by the following replacement body:

{
promise-type promise promise-constructor-arguments ;
try {
co_await promise.initial_suspend() ;
function-body
} catch ( ... ) {
if (!initial-await-resume-called)
throw ;
promise.unhandled_exception() ;
}
final-suspend :
co_await promise.final_suspend() ;
}

where

  • _initial-await-resume-called_is initially false and is set to trueimmediately before the evaluation of the await-resume expression ([expr.await]) of the initial await expression, and
  • promise-type denotes the promise type, and
  • the object denoted by the exposition-only name _promise_is the promise object of the coroutine, and
  • promise-constructor-arguments is determined as follows: overload resolution is performed on a promise constructor call created by assembling an argument list .
    If a viable constructor is found ([over.match.viable]), then_promise-constructor-arguments_ is(, …, ), otherwise_promise-constructor-arguments_ is empty, and
  • a coroutine is suspended at the initial suspend point if it is suspended at the initial await expression, and
  • a coroutine is suspended at a final suspend point if it is suspended
    • at a final await expression or
    • due to an exception exiting from unhandled_exception().

[Note 1:

An odr-use of a non-reference parameter in a postcondition assertion of a coroutine is ill-formed ([dcl.contract.func]).

— _end note_]

If searches for the names return_void and return_valuein the scope of the promise type each find any declarations, the program is ill-formed.

[Note 2:

If return_void is found, flowing off the end of a coroutine is equivalent to a co_return with no operand.

Otherwise, flowing off the end of a coroutine results in undefined behavior ([stmt.return.coroutine]).

— _end note_]

The expression promise.get_return_object() is used to initialize the returned reference or prvalue result object of a call to a coroutine.

The call to get_return_objectis sequenced before the call to initial_suspendand is invoked at most once.

A suspended coroutine can be resumed to continue execution by invoking a resumption member function ([coroutine.handle.resumption]) of a coroutine handle ([coroutine.handle]) that refers to the coroutine.

The evaluation that invoked a resumption member function is called the resumer.

Invoking a resumption member function for a coroutine that is not suspended results in undefined behavior.

An implementation may need to allocate additional storage for a coroutine.

This storage is known as the coroutine state and is obtained by calling a non-array allocation function ([basic.stc.dynamic.allocation]) as part of the replacement body.

The allocation function's name is looked up by searching for it in the scope of the promise type.

  • If the search finds any declarations, overload resolution is performed on a function call created by assembling an argument list.
    The first argument is the amount of space requested, and is a prvalue of type std​::​size_t.
    The lvalues with their original types (including cv-qualifiers) are the successive arguments.
    If no viable function is found ([over.match.viable]), overload resolution is performed again on a function call created by passing just the amount of space required as a prvalue of type std​::​size_t.
  • If the search finds no declarations, a search is performed in the global scope.
    Overload resolution is performed on a function call created by passing the amount of space required as a prvalue of type std​::​size_t.

If a search for the name get_return_object_on_allocation_failurein the scope of the promise type ([class.member.lookup]) finds any declarations, then the result of a call to an allocation function used to obtain storage for the coroutine state is assumed to return nullptr if it fails to obtain storage, and if a global allocation function is selected, the ​::​operator new(size_t, nothrow_t) form is used.

The allocation function used in this case shall have a non-throwingnoexcept-specifier.

If the allocation function returns nullptr, the coroutine transfers control to the caller of the coroutine and the return value is obtained by a call to T​::​get_return_object_on_allocation_failure(), where Tis the promise type.

[Example 2: #include <iostream> #include <coroutine> struct generator { struct promise_type;using handle = std::coroutine_handle<promise_type>;struct promise_type { int current_value;static auto get_return_object_on_allocation_failure() { return generator{nullptr}; } auto get_return_object() { return generator{handle::from_promise(*this)}; } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void unhandled_exception() { std::terminate(); } void return_void() {} auto yield_value(int value) { current_value = value;return std::suspend_always{};} };bool move_next() { return coro ? (coro.resume(), !coro.done()) : false; } int current_value() { return coro.promise().current_value; } generator(generator const&) = delete; generator(generator && rhs) : coro(rhs.coro) { rhs.coro = nullptr; } ~generator() { if (coro) coro.destroy(); } private: generator(handle h) : coro(h) {} handle coro;}; generator f() { co_yield 1; co_yield 2; } int main() { auto g = f();while (g.move_next()) std::cout << g.current_value() << std::endl;} — _end example_]

The coroutine state is destroyed when control flows off the end of the coroutine or the destroy member function ([coroutine.handle.resumption]) of a coroutine handle ([coroutine.handle]) that refers to the coroutine is invoked.

In the latter case, control in the coroutine is considered to be transferred out of the function ([stmt.dcl]).

If destroy is called for a coroutine that is not suspended, the program has undefined behavior.

The deallocation function's name is looked up by searching for it in the scope of the promise type.

If nothing is found, a search is performed in the global scope.

If both a usual deallocation function with only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter are found, then the selected deallocation function shall be the one with two parameters.

Otherwise, the selected deallocation function shall be the function with one parameter.

If no usual deallocation function is found, the program is ill-formed.

The selected deallocation function shall be called with the address of the block of storage to be reclaimed as its first argument.

If a deallocation function with a parameter of type std​::​size_t is used, the size of the block is passed as the corresponding argument.

When a coroutine is invoked, a copy is created for each coroutine parameter at the beginning of the replacement body.

For a parameter whose original declaration specified the type cv T,

  • if T is a reference type, the copy is a reference of typecv Tbound to the same object as a parameter;
  • otherwise, the copy is a variable of type cv Twith automatic storage duration that is direct-initialized from an xvalue of type T referring to the parameter.

The initialization and destruction of each parameter copy occurs in the context of the called coroutine.

Initializations of parameter copies are sequenced before the call to the coroutine promise constructor and indeterminately sequenced with respect to each other.

The lifetime of parameter copies ends immediately after the lifetime of the coroutine promise object ends.

[Note 4:

If a coroutine has a parameter passed by reference, resuming the coroutine after the lifetime of the entity referred to by that parameter has ended is likely to result in undefined behavior.

— _end note_]

If the evaluation of the expression_promise_.unhandled_exception() exits via an exception, the coroutine is considered suspended at the final suspend point and the exception propagates to the caller or resumer.

The expression co_await promise.final_suspend()shall not be potentially-throwing ([except.spec]).