9 Declarations [dcl.dcl]

9.5 Function definitions [dcl.fct.def]

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

The parameter-declaration-clause of the coroutine shall not terminate with an ellipsis that is not part of a parameter-declaration.
[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&&...) { // OK, ellipsis is a pack expansion int i = co_await f(); std::cout << "f() => " << i << std::endl; } task<void> g3(int a, ...) { // error: variable parameter list not allowed int i = co_await f(); std::cout << "f() => " << i << std::endl; } — end example]
The promise type of a coroutine is std​::​coroutine_traits<R, P, , P>​::​promise_type, where R 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 a non-static member function, and denotes the function parameter otherwise.
For a non-static 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 its function-body were replaced by:
{
   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
  • the await-expression containing the call to initial_suspend is the initial await expression, and
  • the await-expression containing the call to final_suspend is the final await expression, and
  • initial-await-resume-called is initially false and is set to true immediately 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
  • the label denoted by the name final-suspend is defined for exposition only ([stmt.return.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().
If searches for the names return_void and return_value in the scope of the promise type each find any declarations, the program is ill-formed.
[Note 1: 
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_object is sequenced before the call to initial_suspend and 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]).
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 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_failure in 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-throwing noexcept-specifier.
If the allocation function returns nullptr, the coroutine returns 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 T is the promise type.
[Example 2: #include <iostream> #include <coroutine> // ​::​operator new(size_t, nothrow_t) will be used if allocation is needed 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]).
The storage for the coroutine state is released by calling a non-array deallocation function ([basic.stc.dynamic.deallocation]).
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, after initializing its parameters ([expr.call]), a copy is created for each coroutine parameter.
For a parameter of type cv T, the copy is a variable of type cv T with automatic storage duration that is direct-initialized from an xvalue of type T referring to the parameter.
[Note 2: 
An original parameter object is never a const or volatile object ([basic.type.qualifier]).
— end note]
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 3: 
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]).