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 are the sequence of types of the function parameters, preceded by the type of the implicit object parameter ([over.match.funcs]) 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 *this and denotes the function parameter for a non-static member function, and denotes the function parameter otherwise.
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 suspend point, and
  • the await-expression containing the call to final_­suspend is the final suspend point, 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 suspend point, 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 with lvalues .
    If a viable constructor is found ([over.match.viable]), then promise-constructor-arguments is (p, , p), otherwise promise-constructor-arguments is empty.
The unqualified-ids return_­void and return_­value are looked up in the scope of the promise type.
If both are found, the program is ill-formed.
[Note 1:
If the unqualified-id 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 glvalue result 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 function 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 in the scope of the promise type.
If this lookup fails, the allocation function's name is looked up in the global scope.
If the lookup finds an allocation function in the scope of the promise type, overload resolution is performed on a function call created by assembling an argument list.
The first argument is the amount of space requested, and has type std​::​size_­t.
The lvalues are the succeeding 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 an argument of type std​::​size_­t.
The unqualified-id get_­return_­object_­on_­allocation_­failure is looked up in the scope of the promise type by class member access lookup ([basic.lookup.classref]).
If any declarations are found, 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 objects with automatic storage duration that are in scope at the suspend point are destroyed in the reverse order of the construction.
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 in the scope of the promise type.
If this lookup fails, the deallocation function's name is looked up in the global scope.
If deallocation function lookup finds both a usual deallocation function with only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter, 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.
The expression co_­await promise.final_­suspend() shall not be potentially-throwing ([except.spec]).