constexpr coroutines

This paper is proposing making coroutines functional during constant evaluation. Even when most of use-cases for coroutines are based on I/O and event based, coroutines are still useful for compile-time based computation, eg. std::generator.

Why would you want to do this?

Well, you just told me coroutines are the best way to solve some problems, so wouldn't I also want to use the Best Way at compile time? (quote from Jason Turner, co-author of "constexpr all the things" talk)

Simple example

When this paper is merged into the standard, users will be able to use std::generator-like coroutines to generate or calculate data.

template <typename T> constexpr auto fib() -> std::generator<T> {
    T a = 0;
    T b = 1;
    co_yield a;
    do {
        co_yield b;
        auto tmp = b;
        b += a;
        a = tmp;
    } while (true);
}

template <typename T, size_t N> constexpr auto calculate_fibonnaci() {
    auto res = std::array<T, N>{};
    std::ranges::copy(fib<T>() | std::views::take(N), res.begin());
    return res;
}

constexpr auto cached_fibonnaci = calculate_fibonnaci<unsigned, 20>();

Implementation experience

Partially implemented in clang available on my github, implementation should be ready for its presentation at Wroclaw meeting, and also will be soon available on compiler explorer (thanks Matt!).

Most of functionality needed was already present in Clang, it was mostly about removing checks in the parser.

Another part was implementing the functionality in Clang's interpreter and there I needed to add fibers (stackfull coroutines) as the interpreter recursive walks over AST. Ability to save interpreter's stack content did minimize impact of the change to only resuming, suspending, and variable storage and life-time management.

At the end of evaluation the interpret needs to check objects holding fibers if there is still any coroutine not released, if there is it report similar error as when there is an unreleased memory allocation.

Hardest problem was implementing local "stack", as createLocal function was designed around idea of having only one branch of evaluation. This I solved by providing context of currently evaluated coroutine in EvalInfo and switching it on every suspension / resume of a coroutine.

Impact on existing code

None, this is a pure extension, it allows code to be constexpr which wasn't case before.

Intention for wording changes

Remove all obstacles blocking coroutines from being constant evaluatable. Make sure all coroutines are destroyed at end of constant evaluation.

Proposed changes to wording

7.7 Constant expressions [expr.const]

Certain contexts require expressions that satisfy additional requirements as detailed in this subclause; other contexts have different semantics depending on whether or not an expression satisfies these requirements.
Expressions that satisfy these requirements, assuming that copy elision is not performed, are called constant expressions.
[Note 1: 
Constant expressions can be evaluated during translation.
— end note]
A variable or temporary object o is constant-initialized if
  • either it has an initializer or its default-initialization results in some initialization being performed, and
  • the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
    [Note 2: 
    Such a class can have a non-trivial destructor.
    Within this evaluation, std​::​is_constant_evaluated() ([meta.const.eval]) returns true.
    — end note]
A variable is potentially-constant if it is constexpr or it has reference or non-volatile const-qualified integral or enumeration type.
A constant-initialized potentially-constant variable V is usable in constant expressions at a point P if V's initializing declaration D is reachable from P and
  • V is constexpr,
  • V is not initialized to a TU-local value, or
  • P is in the same translation unit as D.
An object or reference is usable in constant expressions if it is
  • a variable that is usable in constant expressions, or
  • a template parameter object, or
  • a string literal object, or
  • a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions, or
  • a non-mutable subobject or reference member of any of the above.
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
  • this ([expr.prim.this]), except
  • a control flow that passes through a declaration of a block variable ([basic.scope.block]) with static ([basic.stc.static]) or thread ([basic.stc.thread]) storage duration, unless that variable is usable in constant expressions;
    [Example 1: constexpr char test() { static const int x = 5; static constexpr char c[] = "Hello World"; return *(c + x); } static_assert(' ' == test()); — end example]
  • an invocation of a non-constexpr function;68
  • an invocation of an undefined constexpr function;
  • an invocation of an instantiated constexpr function that is not constexpr-suitable;
  • an invocation of a virtual function ([class.virtual]) for an object whose dynamic type is constexpr-unknown;
  • an expression that would exceed the implementation-defined limits (see [implimits]);
  • an operation that would have undefined or erroneous behavior as specified in [intro] through [cpp], excluding [dcl.attr.assume] and [dcl.attr.noreturn];69
  • an lvalue-to-rvalue conversion unless it is applied to
    • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
  • an lvalue-to-rvalue conversion that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
  • an lvalue-to-rvalue conversion that is applied to an object with an indeterminate value;
  • an invocation of an implicitly-defined copy/move constructor or copy/move assignment operator for a union whose active member (if any) is mutable, unless the lifetime of the union object began within the evaluation of E;
  • in a lambda-expression, a reference to this or to a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use ([basic.def.odr], [expr.prim.lambda]);
    [Example 2: void g() { const int n = 0; [=] { constexpr int i = n; // OK, n is not odr-used here constexpr int j = *&n; // error: &n would be an odr-use of n }; } — end example]
    [Note 3: 
    If the odr-use occurs in an invocation of a function call operator of a closure type, it no longer refers to this or to an enclosing automatic variable due to the transformation ([expr.prim.lambda.capture]) of the id-expression into an access of the corresponding data member.
    [Example 3: auto monad = [](auto v) { return [=] { return v; }; }; auto bind = [](auto m) { return [=](auto fvm) { return fvm(m()); }; }; // OK to capture objects with automatic storage duration created during constant expression evaluation. static_assert(bind(monad(2))(monad)() == monad(2)()); — end example]
    — end note]
  • a conversion from a prvalue P of type “pointer to cv void” to a type “cv1 pointer to T”, where T is not cv2 void, unless P is a null pointer value or points to an object whose type is similar to T;
  • a reinterpret_cast ([expr.reinterpret.cast]);
  • a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
  • an invocation of a destructor ([class.dtor]) or a function call whose postfix-expression names a pseudo-destructor ([expr.call]), in either case for an object whose lifetime did not begin within the evaluation of E;
  • a new-expression ([expr.new]), unless either
    • the selected allocation function is a replaceable global allocation function ([new.delete.single], [new.delete.array]) and the allocated storage is deallocated within the evaluation of E, or
    • the selected allocation function is a non-allocating form ([new.delete.placement]) with an allocated type T, where
      • the placement argument to the new-expression points to an object that is pointer-interconvertible with an object of type T or, if T is an array type, with the first element of an object of type T, and
      • the placement argument points to storage whose duration began within the evaluation of E;
  • a delete-expression ([expr.delete]), unless it deallocates a region of storage allocated within the evaluation of E;
  • a call to an instance of std​::​allocator<T>​::​allocate ([allocator.members]), unless the allocated storage is deallocated within the evaluation of E;
  • a call to an instance of std​::​allocator<T>​::​deallocate ([allocator.members]), unless it deallocates a region of storage allocated within the evaluation of E;
  • an await-expression ([expr.await]);
  • a yield-expression ([expr.yield]);
  • a three-way comparison ([expr.spaceship]), relational ([expr.rel]), or equality ([expr.eq]) operator where the result is unspecified;
  • a throw-expression ([expr.throw]);
  • a dynamic_cast ([expr.dynamic.cast]) or typeid ([expr.typeid]) expression on a glvalue that refers to an object whose dynamic type is constexpr-unknown or that would throw an exception;
  • an asm-declaration ([dcl.asm]);
  • an invocation of the va_arg macro ([cstdarg.syn]);
  • a non-constant library call ([defns.nonconst.libcall]); or
  • a goto statement ([stmt.goto]).
    [Note 4: 
    A goto statement introduced by equivalence ([stmt.stmt]) is not in scope.
    For example, a while statement ([stmt.while]) can be executed during constant evaluation.
    — end note]
It is unspecified whether E is a core constant expression if E satisfies the constraints of a core constant expression, but evaluation of E would evaluate
  • an operation that has undefined behavior as specified in [library] through [exec],
  • an invocation of the va_start macro ([cstdarg.syn]),
  • a call to a function that was previously declared with the noreturn attribute ([dcl.attr.noreturn]) and that call returns to its caller, or
  • a statement with an assumption ([dcl.attr.assume]) whose converted conditional-expression, if evaluated where the assumption appears, would not disqualify E from being a core constant expression and would not evaluate to true.
    [Note 5: 
    E is not disqualified from being a core constant expression if the hypothetical evaluation of the converted conditional-expression would disqualify E from being a core constant expression.
    — end note]
[Example 4: int x; // not constant struct A { constexpr A(bool b) : m(b?42:x) { } int m; }; constexpr int v = A(true).m; // OK, constructor call initializes m with the value 42 constexpr int w = A(false).m; // error: initializer for m is x, which is non-constant constexpr int f1(int k) { constexpr int x = k; // error: x is not initialized by a constant expression // because lifetime of k began outside the initializer of x return x; } constexpr int f2(int k) { int x = k; // OK, not required to be a constant expression // because x is not constexpr return x; } constexpr int incr(int &n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // error: incr(k) is not a core constant expression // because lifetime of k began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK, incr(k) is not required to be a core constant expression return x; } constexpr int y = h(1); // OK, initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside h(1) — end example]
For the purposes of determining whether an expression E is a core constant expression, the evaluation of the body of a member function of std​::​allocator<T> as defined in [allocator.members], where T is a literal type, is ignored.
#
For the purposes of determining whether an expression E is a core constant expression, lifetimes of all coroutines created during evaluation of E shall end within the evaluation of E.
For the purposes of determining whether E is a core constant expression, the evaluation of a call to a trivial copy/move constructor or copy/move assignment operator of a union is considered to copy/move the active member of the union, if any.
[Note 6: 
The copy/move of the active member is trivial.
— end note]
During the evaluation of an expression E as a core constant expression, all id-expressions and uses of *this that refer to an object or reference whose lifetime did not begin with the evaluation of E are treated as referring to a specific instance of that object or reference whose lifetime and that of all subobjects (including all union members) includes the entire constant evaluation.
For such an object that is not usable in constant expressions, the dynamic type of the object is constexpr-unknown.
For such a reference that is not usable in constant expressions, the reference is treated as binding to an unspecified object of the referenced type whose lifetime and that of all subobjects includes the entire constant evaluation and whose dynamic type is constexpr-unknown.
[Example 5: template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { return N; } void use_array(int const (&gold_medal_mel)[2]) { constexpr auto gold = array_size(gold_medal_mel); // OK } constexpr auto olympic_mile() { const int ledecky = 1500; return []{ return ledecky; }; } static_assert(olympic_mile()() == 1500); // OK struct Swim { constexpr int phelps() { return 28; } virtual constexpr int lochte() { return 12; } int coughlin = 12; }; constexpr int how_many(Swim& swam) { Swim* p = &swam; return (p + 1 - 1)->phelps(); } void splash(Swim& swam) { static_assert(swam.phelps() == 28); // OK static_assert((&swam)->phelps() == 28); // OK Swim* pswam = &swam; static_assert(pswam->phelps() == 28); // error: lvalue-to-rvalue conversion on a pointer // not usable in constant expressions static_assert(how_many(swam) == 28); // OK static_assert(Swim().lochte() == 12); // OK static_assert(swam.lochte() == 12); // error: invoking virtual function on reference // with constexpr-unknown dynamic type static_assert(swam.coughlin == 12); // error: lvalue-to-rvalue conversion on an object // not usable in constant expressions } extern Swim dc; extern Swim& trident; constexpr auto& sandeno = typeid(dc); // OK, can only be typeid(Swim) constexpr auto& gallagher = typeid(trident); // error: constexpr-unknown dynamic type — end example]
An object a is said to have constant destruction if
  • it is not of class type nor (possibly multidimensional) array thereof, or
  • it is of class type or (possibly multidimensional) array thereof, that class type has a constexpr destructor, and for a hypothetical expression E whose only effect is to destroy a, E would be a core constant expression if the lifetime of a and its non-mutable subobjects (but not its mutable subobjects) were considered to start within E.
An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression.
[Note 7: 
Such expressions can be used as bit-field lengths ([class.bit]), as enumerator initializers if the underlying type is not fixed ([dcl.enum]), and as alignments.
— end note]
If an expression of literal class type is used in a context where an integral constant expression is required, then that expression is contextually implicitly converted ([conv]) to an integral or unscoped enumeration type and the selected conversion function shall be constexpr.
[Example 6: struct A { constexpr A(int i) : val(i) { } constexpr operator int() const { return val; } constexpr operator long() const { return 42; } private: int val; }; constexpr A a = alignof(int); alignas(a) int n; // error: ambiguous conversion struct B { int n : a; }; // error: ambiguous conversion — end example]
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only and where the reference binding (if any) binds directly.
[Note 8: 
Such expressions can be used in new expressions ([expr.new]), as case expressions ([stmt.switch]), as enumerator initializers if the underlying type is fixed, as array bounds, and as non-type template arguments.
— end note]
A contextually converted constant expression of type bool is an expression, contextually converted to bool ([conv]), where the converted expression is a constant expression and the conversion sequence contains only the conversions above.
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:
  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
  • if the value is an object of scalar type, it does not have an indeterminate or erroneous value ([basic.indet]),
  • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a non-immediate function, or a null pointer value,
  • if the value is of pointer-to-member-function type, it does not designate an immediate function, and
  • if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.
[Note 9: 
A glvalue core constant expression that either refers to or points to an unspecified object is not a constant expression.
— end note]
[Example 7: consteval int f() { return 42; } consteval auto g() { return f; } consteval int h(int (*p)() = g()) { return p(); } constexpr int r = h(); // OK constexpr auto e = g(); // error: a pointer to an immediate function is // not a permitted result of a constant expression struct S { int x; constexpr S() {} }; int i() { constexpr S s; // error: s.x has erroneous value } — end example]
Recommended practice: Implementations should provide consistent results of floating-point evaluations, irrespective of whether the evaluation is performed during translation or during program execution.
[Note 10: 
Since this document imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution.
[Example 8: bool f() { char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // May be evaluated at runtime return sizeof(array) == size; }
It is unspecified whether the value of f() will be true or false.
— end example]
— end note]
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
  • its innermost enclosing non-block scope is a function parameter scope of an immediate function,
  • it is a subexpression of a manifestly constant-evaluated expression or conversion, or
  • its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An invocation is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context.
An aggregate initialization is an immediate invocation if it evaluates a default member initializer that has a subexpression that is an immediate-escalating expression.
An expression or conversion is immediate-escalating if it is not initially in an immediate function context and it is either
  • a potentially-evaluated id-expression that denotes an immediate function that is not a subexpression of an immediate invocation, or
  • an immediate invocation that is not a constant expression and is not a subexpression of an immediate invocation.
An immediate-escalating function is
  • the call operator of a lambda that is not declared with the consteval specifier,
  • a defaulted special member function that is not declared with the consteval specifier, or
  • a function that results from the instantiation of a templated entity defined with the constexpr specifier.
An immediate-escalating expression shall appear only in an immediate-escalating function.
An immediate function is a function or constructor that is
  • declared with the consteval specifier, or
  • an immediate-escalating function F whose function body contains an immediate-escalating expression E such that E's innermost enclosing non-block scope is F's function parameter scope.
    [Note 11: 
    Default member initializers used to initialize a base or member subobject ([class.base.init]) are considered to be part of the function body ([dcl.fct.def.general]).
    — end note]
[Example 9: consteval int id(int i) { return i; } constexpr char id(char c) { return c; } template<class T> constexpr int f(T t) { return t + id(t); } auto a = &f<char>; // OK, f<char> is not an immediate function auto b = &f<int>; // error: f<int> is an immediate function static_assert(f(3) == 6); // OK template<class T> constexpr int g(T t) { // g<int> is not an immediate function return t + id(42); // because id(42) is already a constant } template<class T, class F> constexpr bool is_not(T t, F f) { return not f(t); } consteval bool is_even(int i) { return i % 2 == 0; } static_assert(is_not(5, is_even)); // OK int x = 0; template<class T> constexpr T h(T t = id(x)) { // h<int> is not an immediate function // id(x) is not evaluated when parsing the default argument ([dcl.fct.default], [temp.inst]) return t; } template<class T> constexpr T hh() { // hh<int> is an immediate function because of the invocation return h<T>(); // of the immediate function id in the default argument of h<int> } int i = hh<int>(); // error: hh<int>() is an immediate-escalating expression // outside of an immediate-escalating function struct A { int x; int y = id(x); }; template<class T> constexpr int k(int) { // k<int> is not an immediate function because A(42) is a return A(42).y; // constant expression and thus not immediate-escalating } — end example]
An expression or conversion is manifestly constant-evaluated if it is:
  • a constant-expression, or
  • the condition of a constexpr if statement ([stmt.if]), or
  • an immediate invocation, or
  • the result of substitution into an atomic constraint expression to determine whether it is satisfied ([temp.constr.atomic]), or
  • the initializer of a variable that is usable in constant expressions or has constant initialization ([basic.start.static]).70
    [Example 10: template<bool> struct X {}; X<std::is_constant_evaluated()> x; // type X<true> int y; const int a = std::is_constant_evaluated() ? y : 1; // dynamic initialization to 1 double z[a]; // error: a is not usable // in constant expressions const int b = std::is_constant_evaluated() ? 2 : y; // static initialization to 2 int c = y + (std::is_constant_evaluated() ? 2 : y); // dynamic initialization to y+y constexpr int f() { const int n = std::is_constant_evaluated() ? 13 : 17; // n is 13 int m = std::is_constant_evaluated() ? 13 : 17; // m can be 13 or 17 (see below) char arr[n] = {}; // char[13] return m + sizeof(arr); } int p = f(); // m is 13; initialized to 26 int q = p + f(); // m is 17 for this call; initialized to 56 — end example]
[Note 12: 
Except for a static_assert-message, a manifestly constant-evaluated expression is evaluated even in an unevaluated operand ([expr.context]).
— end note]
An expression or conversion is potentially constant evaluated if it is:
A function or variable is needed for constant evaluation if it is:
  • a constexpr function that is named by an expression that is potentially constant evaluated, or
  • a potentially-constant variable named by a potentially constant evaluated expression.
68)68)
Overload resolution ([over.match]) is applied as usual.
69)69)
This includes, for example, signed integer overflow ([expr.pre]), certain pointer arithmetic ([expr.add]), division by zero ([expr.mul]), or certain shift operations ([expr.shift]).
70)70)
Testing this condition can involve a trial evaluation of its initializer as described above.
71)71)
In some cases, constant evaluation is needed to determine whether a narrowing conversion is performed ([dcl.init.list]).
72)72)
In some cases, constant evaluation is needed to determine whether such an expression is value-dependent ([temp.dep.constexpr]).

9.2.6 The constexpr and consteval specifiers [dcl.constexpr]

The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template.
The consteval specifier shall be applied only to the declaration of a function or function template.
A function or static data member declared with the constexpr or consteval specifier on its first declaration is implicitly an inline function or variable ([dcl.inline]).
If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
[Note 1: 
An explicit specialization can differ from the template declaration with respect to the constexpr or consteval specifier.
— end note]
[Note 2: 
Function parameters cannot be declared constexpr.
— end note]
[Example 1: constexpr void square(int &x); // OK, declaration constexpr int bufsz = 1024; // OK, definition constexpr struct pixel { // error: pixel is a type int x; int y; constexpr pixel(int); // OK, declaration }; constexpr pixel::pixel(int a) : x(a), y(x) // OK, definition { square(x); } constexpr pixel small(2); // error: square not defined, so small(2) // not constant ([expr.const]) so constexpr not satisfied constexpr void square(int &x) { // OK, definition x *= x; } constexpr pixel large(4); // OK, square defined int next(constexpr int x) { // error: not for parameters return x + 1; } extern constexpr int memsz; // error: not a definition — end example]
A constexpr or consteval specifier used in the declaration of a function declares that function to be a constexpr function.
[Note 3: 
A function or constructor declared with the consteval specifier is an immediate function ([expr.const]).
— end note]
A destructor, an allocation function, or a deallocation function shall not be declared with the consteval specifier.
A function is not constexpr-suitable if
Except for instantiated constexpr functions, non-templated constexpr functions shall be constexpr-suitable.
[Example 2: constexpr int square(int x) { return x * x; } // OK constexpr long long_max() { return 2147483647; } // OK constexpr int abs(int x) { if (x < 0) x = -x; return x; // OK } constexpr int constant_non_42(int n) { // OK if (n == 42) { static int value = n; return value; } return n; } constexpr int uninit() { struct { int a; } s; return s.a; // error: uninitialized read of s.a } constexpr int prev(int x) { return --x; } // OK constexpr int g(int x, int n) { // OK int r = 1; while (--n > 0) r *= x; return r; } — end example]
An invocation of a constexpr function in a given context produces the same result as an invocation of an equivalent non-constexpr function in the same context in all respects except that
[Note 4: 
Declaring a function constexpr can change whether an expression is a constant expression.
This can indirectly cause calls to std​::​is_constant_evaluated within an invocation of the function to produce a different value.
— end note]
[Note 5: 
It is possible to write a constexpr function for which no invocation satisfies the requirements of a core constant expression.
— end note]
The constexpr and consteval specifiers have no effect on the type of a constexpr function.
[Example 3: constexpr int bar(int x, int y) // OK { return x + y + x*y; } // ... int bar(int x, int y) // error: redefinition of bar { return x * 2 + 3 * y; } — end example]
A constexpr specifier used in an object declaration declares the object as const.
Such an object shall have literal type and shall be initialized.
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression ([expr.const]).
A constexpr variable that is an object, as well as any temporary to which a constexpr reference is bound, shall have constant destruction.
[Example 4: struct pixel { int x, y; }; constexpr pixel ur = { 1294, 1024 }; // OK constexpr pixel origin; // error: initializer missing — end example]

17.12 Coroutines [support.coroutine]

17.12.1 General [support.coroutine.general]

The header <coroutine> defines several types providing compile and run-time support for coroutines in a C++ program.

17.12.2 Header <coroutine> synopsis [coroutine.syn]

// all freestanding #include <compare> // see [compare.syn] namespace std { // [coroutine.traits], coroutine traits template<class R, class... ArgTypes> struct coroutine_traits; // [coroutine.handle], coroutine handle template<class Promise = void> struct coroutine_handle; // [coroutine.handle.compare], comparison operators constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept; constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept; // [coroutine.handle.hash], hash support template<class T> struct hash; template<class P> struct hash<coroutine_handle<P>>; // [coroutine.noop], no-op coroutines struct noop_coroutine_promise; template<> struct coroutine_handle<noop_coroutine_promise>; using noop_coroutine_handle = coroutine_handle<noop_coroutine_promise>; constexpr noop_coroutine_handle noop_coroutine() noexcept; // [coroutine.trivial.awaitables], trivial awaitables struct suspend_never; struct suspend_always; }

17.12.3 Coroutine traits [coroutine.traits]

17.12.3.1 General [coroutine.traits.general]

Subclause [coroutine.traits] defines requirements on classes representing coroutine traits, and defines the class template coroutine_traits that meets those requirements.

17.12.3.2 Class template coroutine_traits [coroutine.traits.primary]

The header <coroutine> defines the primary template coroutine_traits such that if ArgTypes is a parameter pack of types and if the qualified-id R​::​promise_type is valid and denotes a type ([temp.deduct]), then coroutine_traits<R, ArgTypes...> has the following publicly accessible member: using promise_type = typename R::promise_type;
Otherwise, coroutine_traits<R, ArgTypes...> has no members.
Program-defined specializations of this template shall define a publicly accessible nested type named promise_type.

17.12.4 Class template coroutine_handle [coroutine.handle]

17.12.4.1 General [coroutine.handle.general]

namespace std { template<> struct coroutine_handle<void> { // [coroutine.handle.con], construct/reset constexpr coroutine_handle() noexcept; constexpr coroutine_handle(nullptr_t) noexcept; constexpr coroutine_handle& operator=(nullptr_t) noexcept; // [coroutine.handle.export.import], export/import constexpr void* address() const noexcept; static constexpr coroutine_handle from_address(void* addr); // [coroutine.handle.observers], observers constexpr explicit operator bool() const noexcept; constexpr bool done() const; // [coroutine.handle.resumption], resumption constexpr void operator()() const; constexpr void resume() const; constexpr void destroy() const; private: void* ptr; // exposition only }; template<class Promise> struct coroutine_handle { // [coroutine.handle.con], construct/reset constexpr coroutine_handle() noexcept; constexpr coroutine_handle(nullptr_t) noexcept; static constexpr coroutine_handle from_promise(Promise&); constexpr coroutine_handle& operator=(nullptr_t) noexcept; // [coroutine.handle.export.import], export/import constexpr void* address() const noexcept; static constexpr coroutine_handle from_address(void* addr); // [coroutine.handle.conv], conversion constexpr operator coroutine_handle<>() const noexcept; // [coroutine.handle.observers], observers constexpr explicit operator bool() const noexcept; constexpr bool done() const; // [coroutine.handle.resumption], resumption constexpr void operator()() const; constexpr void resume() const; constexpr void destroy() const; // [coroutine.handle.promise], promise access constexpr Promise& promise() const; private: void* ptr; // exposition only }; }
An object of type coroutine_handle<T> is called a coroutine handle and can be used to refer to a suspended or executing coroutine.
A coroutine_handle object whose member address() returns a null pointer value does not refer to any coroutine.
Two coroutine_handle objects refer to the same coroutine if and only if their member address() returns the same non-null value.
If a program declares an explicit or partial specialization of coroutine_handle, the behavior is undefined.

17.12.4.2 Construct/reset [coroutine.handle.con]

constexpr coroutine_handle() noexcept; constexpr coroutine_handle(nullptr_t) noexcept;
Postconditions: address() == nullptr.
static constexpr coroutine_handle from_promise(Promise& p);
Preconditions: p is a reference to a promise object of a coroutine.
Postconditions: addressof(h.promise()) == addressof(p).
Returns: A coroutine handle h referring to the coroutine.
constexpr coroutine_handle& operator=(nullptr_t) noexcept;
Postconditions: address() == nullptr.
Returns: *this.

17.12.4.3 Conversion [coroutine.handle.conv]

constexpr operator coroutine_handle<>() const noexcept;
Effects: Equivalent to: return coroutine_handle<>​::​from_address(address());

17.12.4.4 Export/import [coroutine.handle.export.import]

constexpr void* address() const noexcept;
Returns: ptr.
static constexpr coroutine_handle<> coroutine_handle<>::from_address(void* addr);
Preconditions: addr was obtained via a prior call to address on an object whose type is a specialization of coroutine_handle.
Postconditions: from_address(address()) == *this.
static constexpr coroutine_handle<Promise> coroutine_handle<Promise>::from_address(void* addr);
Preconditions: addr was obtained via a prior call to address on an object of type cv coroutine_handle<Promise>.
Postconditions: from_address(address()) == *this.

17.12.4.5 Observers [coroutine.handle.observers]

constexpr explicit operator bool() const noexcept;
Returns: address() != nullptr.
constexpr bool done() const;
Preconditions: *this refers to a suspended coroutine.
Returns: true if the coroutine is suspended at its final suspend point, otherwise false.

17.12.4.6 Resumption [coroutine.handle.resumption]

Resuming a coroutine via resume, operator(), or destroy on an execution agent other than the one on which it was suspended has implementation-defined behavior unless each execution agent either is an instance of std​::​thread or std​::​jthread, or is the thread that executes main.
[Note 1: 
A coroutine that is resumed on a different execution agent should avoid relying on consistent thread identity throughout, such as holding a mutex object across a suspend point.
— end note]
[Note 2: 
A concurrent resumption of the coroutine can result in a data race.
— end note]
constexpr void operator()() const; constexpr void resume() const;
Preconditions: *this refers to a suspended coroutine.
The coroutine is not suspended at its final suspend point.
Effects: Resumes the execution of the coroutine.
constexpr void destroy() const;
Preconditions: *this refers to a suspended coroutine.
Effects: Destroys the coroutine ([dcl.fct.def.coroutine]).

17.12.4.7 Promise access [coroutine.handle.promise]

constexpr Promise& promise() const;
Preconditions: *this refers to a coroutine.
Returns: A reference to the promise of the coroutine.

17.12.4.8 Comparison operators [coroutine.handle.compare]

constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept;
Returns: x.address() == y.address().
constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept;
Returns: compare_three_way()(x.address(), y.address()).

17.12.4.9 Hash support [coroutine.handle.hash]

template<class P> struct hash<coroutine_handle<P>>;
The specialization is enabled ([unord.hash]).

17.12.5 No-op coroutines [coroutine.noop]

17.12.5.1 Class noop_coroutine_promise [coroutine.promise.noop]

struct noop_coroutine_promise {};
The class noop_coroutine_promise defines the promise type for the coroutine referred to by noop_coroutine_handle ([coroutine.syn]).

17.12.5.2 Class coroutine_handle<noop_coroutine_promise> [coroutine.handle.noop]

17.12.5.2.1 General [coroutine.handle.noop.general]

namespace std { template<> struct coroutine_handle<noop_coroutine_promise> { // [coroutine.handle.noop.conv], conversion constexpr operator coroutine_handle<>() const noexcept; // [coroutine.handle.noop.observers], observers constexpr explicit operator bool() const noexcept; constexpr bool done() const noexcept; // [coroutine.handle.noop.resumption], resumption constexpr void operator()() const noexcept; constexpr void resume() const noexcept; constexpr void destroy() const noexcept; // [coroutine.handle.noop.promise], promise access constexpr noop_coroutine_promise& promise() const noexcept; // [coroutine.handle.noop.address], address constexpr void* address() const noexcept; private: constexpr coroutine_handle(unspecified); void* ptr; // exposition only }; }

17.12.5.2.2 Conversion [coroutine.handle.noop.conv]

constexpr operator coroutine_handle<>() const noexcept;
Effects: Equivalent to: return coroutine_handle<>​::​from_address(address());

17.12.5.2.3 Observers [coroutine.handle.noop.observers]

constexpr explicit operator bool() const noexcept;
Returns: true.
constexpr bool done() const noexcept;
Returns: false.

17.12.5.2.4 Resumption [coroutine.handle.noop.resumption]

constexpr void operator()() const noexcept; constexpr void resume() const noexcept; constexpr void destroy() const noexcept;
Effects: None.
Remarks: If noop_coroutine_handle is converted to coroutine_handle<>, calls to operator(), resume and destroy on that handle will also have no observable effects.

17.12.5.2.5 Promise access [coroutine.handle.noop.promise]

constexpr noop_coroutine_promise& promise() const noexcept;
Returns: A reference to the promise object associated with this coroutine handle.

17.12.5.2.6 Address [coroutine.handle.noop.address]

constexpr void* address() const noexcept;
Returns: ptr.
Remarks: A noop_coroutine_handle's ptr is always a non-null pointer value.

17.12.5.3 Function noop_coroutine [coroutine.noop.coroutine]

constexpr noop_coroutine_handle noop_coroutine() noexcept;
Returns: A handle to a coroutine that has no observable effects when resumed or destroyed.
Remarks: A handle returned from noop_coroutine may or may not compare equal to a handle returned from another invocation of noop_coroutine.

17.12.6 Trivial awaitables [coroutine.trivial.awaitables]

namespace std { struct suspend_never { constexpr bool await_ready() const noexcept { return true; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} }; struct suspend_always { constexpr bool await_ready() const noexcept { return false; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} }; }
[Note 1: 
The types suspend_never and suspend_always can be used to indicate that an await-expression either never suspends or always suspends, and in either case does not produce a value.
— end note]

Feature test macros

15.11 Predefined macro names [cpp.predefined]

__cpp_constexpr_coroutines 2024??L

17.3.2 Header <version> synopsis [version.syn]

#define __cpp_lib_constexpr_coroutines 2024??L