Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 17 – Modules and coroutines

C++ coroutines: co_await, co_yield and co_return

What turns a C++ function into a coroutine, what promise_type and coroutine_handle do, and why coroutine frames cause lifetime bugs.

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: A coroutine is a state machine the compiler writes for you · Three keywords make a coroutine; its return type decides the rest · Every coroutine body runs inside the same skeleton · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Explain what turns a function into a coroutine
  • Describe the roles of promise_type and coroutine_handle
  • Trace the order in which a coroutine's promise hooks run
  • Avoid lifetime bugs in coroutine frames

Before you start

A function that hands out the squares 1, 4, 9, 16 … one per call has to remember where it stopped between calls. A coroutine: A function that can suspend in the middle and be resumed later, with its local variables kept in a coroutine frame. In C++ a function is a coroutine when its body uses co_await, co_yield or co_return; nothing in its declaration says so. does it as a plain loop, because it can stop in the middle and continue later, with its local variables kept in a The storage of one coroutine call: its promise, copies of its parameters and the locals that live across a suspension. It is allocated when the coroutine is called, usually on the heap, and lives until the coroutine is destroyed.. A function becomes a coroutine as soon as its body uses co_await, co_yield or co_return: co_await x suspends until x is ready, co_yield v hands v to whoever drives the coroutine and suspends, and co_return v finishes it.

Each keyword’s meaning comes from the coroutine’s return type, through its nested promise_type, and a std::coroutine_handle resumes the paused coroutine or destroys its frame. Below, a small coroutine prints every hook as it runs on two compilers, and you step through that run.

A coroutine is a state machine the compiler writes for you

Some functions naturally pause. A function that produces a long sequence would like to hand over one value and wait for the next request; a function that sends a request over the network would like to wait for the answer without blocking a thread. Without coroutines, both become state machines written by hand: the local variables move into a struct, and a switch remembers where to continue. A coroutine lets the compiler write that state machine. Here is the source of the first n squares both ways:

The first n squares Written by hand As a coroutine
The code you write A class with a next() member A loop that runs co_yield i * i
Where i and n live Members of the object Locals, kept in the frame
Where the next call continues Wherever a member records Just after the last co_yield

In the coroutine, the compiler keeps i and n alive in the frame between one value and the next. C++20 added the language mechanism and almost no library types: until C++23 brought std::generator, the standard library offered only building blocks such as std::coroutine_handle, std::suspend_always and std::suspend_never, so most code used types from libraries or wrote its own.

Checkpoint

Check: A coroutine keeps its place

  1. Question 1 of 2 In the coroutine version of the squares source, where do i and n live between one value and the next?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: In the coroutine frame, which the compiler allocates

    A coroutine keeps the locals that live across a suspension in its frame. The compiler writes the state machine that the hand-written class spells out: the members become variables of the frame, and the point where the body resumes replaces the member that records the state.

  2. Question 2 of 2 Without coroutines, a function must hand out one record of a large file per request and continue where it stopped at the next request. What do you write?

    Choose one answer.

    A hint

    Where would the position in the file live between two requests?

    Show the answer: Question 2 of 2

    Answer: A class that keeps the file and the position as members, with a next() that steps them

    A function that pauses becomes a state machine when written by hand: its local variables move into a class or a struct, and a member, often read by a switch, records where to continue. A coroutine lets the compiler build that machine from a plain loop.

Three keywords make a coroutine; its return type decides the rest

Nothing in a declaration marks a coroutine: Traced answer(int extra) below is one only because its body uses co_await and co_return. What each keyword does is up to the coroutine’s types:

In the body What happens Decided by
co_await expr Suspends unless expr is ready, until something resumes the coroutine The awaiter
co_yield value Passes value to the promise and suspends The promise
co_return value Stores a result in the promise and ends the coroutine The promise’s return_value

Those types:

  • promise_type is a class nested in the return type, and the compiler calls its member functions at fixed points.
  • std::coroutine_handle<P> is a pointer-sized handle to one frame, with resume(), done(), destroy() and promise(); it copies like a pointer and owns nothing.
  • std::suspend_always and std::suspend_never are ready-made The object that co_await works with: await_ready says whether the result is already there, await_suspend runs once the coroutine is suspended, and await_resume gives the value of the co_await expression.: the first always suspends, the second never does.

Checkpoint

Check: Keywords, promise type and handle

  1. Question 1 of 2 Which of these four functions is a coroutine? Assume that Task has a suitable promise_type.

    Read the code, then choose one answer.

    Task a() { return Task{}; }
    Task b() { co_return; }
    Task c() { Task t = b(); return t; }
    int d() { return 0; }
    Show the answer: Question 1 of 2

    Answer: b

    A function is a coroutine exactly when its body contains co_await, co_yield or co_return. The return type is where the compiler then finds the promise type, but returning a Task changes nothing by itself.

  2. Question 2 of 2 What does co_await std::suspend_never{}; do in a coroutine body?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing visible; the coroutine goes on to the next line without suspending

    std::suspend_always and std::suspend_never are the two ready-made awaiters of the standard library: the first always suspends, the second never does. A promise hook returns one of them to say whether the coroutine should stop at that point.

Every coroutine body runs inside the same skeleton

The standard turns every coroutine body into the same skeleton: allocate a frame, create the promise, ask it for the return object, await initial_suspend(), run the body, and await final_suspend() at the end. The promise’s member functions are called hooks below, because the compiler hooks them into that skeleton. Follow the diagram from the call down to destroy():

A coroutine call allocates a frame and promise, waits at initial_suspend, runs on resume, returns, waits at the end, then is destroyed.The caller calls answer(2)Allocate the frame (operator new)copy or move the parameters into itconstruct the promisepromise.get_return_object()the object the caller receivesco_await promise.initial_suspend()suspend_always: wait for resume()The body runs on handle.resume()co_await exprawait_ready, await_suspend: back to the resumerawait_resume on the next resume()co_return valuepromise.return_value(value)co_await promise.final_suspend()suspend_always: the frame stays, done() is truehandle.destroy()promise, then parameters destroyedframe freed (operator delete)resume()resume()

The life of a coroutine and the promise hooks on the way

Text description of the diagram

The diagram follows the coroutine answer(2) of the traced example from top to bottom.

  1. The caller calls answer(2). The compiler allocates the coroutine frame with operator new, copies or moves the parameters into it and constructs the promise object.
  2. promise.get_return_object() makes the object the caller will receive, here a Traced that holds the coroutine handle.
  3. The coroutine awaits promise.initial_suspend(). With suspend_always it stops before running any of its body, and the caller gets the object.
  4. The body runs when someone calls handle.resume().
  5. At co_await expr the awaiter is asked await_ready; when it answers no, await_suspend runs and control goes back to whoever resumed the coroutine. The next resume() continues with await_resume, whose result is the value of the co_await expression.
  6. co_return value calls promise.return_value(value).
  7. The coroutine awaits promise.final_suspend(). With suspend_always the frame stays alive and handle.done() is true, so the caller can read the result.
  8. handle.destroy() destroys the promise, then the parameter copies, and frees the frame with operator delete.

Both ends are A place where a coroutine can stop and hand control back to whoever called or resumed it: each co_await and co_yield, and the initial and final suspension points that the compiler puts around the body.. At the start, suspend_always makes the body wait for the first resume(). At the end, it keeps the frame after the body so the result can still be read; with suspend_never there, the frame is freed as soon as the body ends. Because the frame usually outlives the call that created it, everything it refers to must live at least as long; reference parameters and lambda captures are the two classic ways to break that rule.

Key idea

Every coroutine body runs inside the same skeleton, and its two ends decide its life: initial_suspend says whether the body waits for the first resume(), and final_suspend whether the frame outlives the body.

Checkpoint

Check: The skeleton around every body

  1. Question 1 of 2 In trace.cpp, what has already happened by the time initial_suspend runs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The frame was allocated, the promise constructed and get_return_object called

    The skeleton runs in a fixed order: allocate the frame, construct the promise, call get_return_object, await initial_suspend, then the body. trace.cpp printed exactly that order.

  2. Question 2 of 2 A coroutine type's final_suspend returns std::suspend_never instead of std::suspend_always. What changes when the body reaches its end?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The frame is freed at once, so reading promise() afterwards is a use after free

    final_suspend decides whether the frame survives the end of the body. With suspend_always the coroutine waits at its final suspension point, done() is true and the owner calls destroy(); with suspend_never the frame and the promise are freed at once, and any later use of the handle is a use after free.

A traced run shows each hook as it happens

This coroutine type does nothing useful, and every one of its hooks prints when it runs:

trace.cpp C++ · trace.cpp
// A coroutine whose promise reports every step: who calls what, and when.
#include <coroutine>
#include <cstdio>
#include <cstdlib>
#include <new>

struct Traced {
    struct promise_type {
        int result = 0;

        static void* operator new(std::size_t size) {
            std::printf("  operator new: a frame of %zu bytes\n", size);
            void* frame = std::malloc(size);
            if (!frame) throw std::bad_alloc{};
            return frame;
        }
        static void operator delete(void* frame) {
            std::puts("  operator delete: the frame is freed");
            std::free(frame);
        }

        promise_type() { std::puts("  promise constructed"); }
        ~promise_type() { std::puts("  promise destroyed"); }

        Traced get_return_object() {
            std::puts("  get_return_object");
            return Traced{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() noexcept {
            std::puts("  initial_suspend: wait for the first resume()");
            return {};
        }
        void return_value(int value) {
            std::printf("  return_value(%d)\n", value);
            result = value;
        }
        std::suspend_always final_suspend() noexcept {
            std::puts("  final_suspend: keep the frame for the caller");
            return {};
        }
        void unhandled_exception() { std::abort(); }
    };

    std::coroutine_handle<promise_type> handle;
};

struct Pause {
    bool await_ready() const noexcept {
        std::puts("  await_ready: no, suspend");
        return false;
    }
    void await_suspend(std::coroutine_handle<>) const noexcept { std::puts("  await_suspend: back to the caller"); }
    int await_resume() const noexcept {
        std::puts("  await_resume: continue with 40");
        return 40;
    }
};

Traced answer(int extra) {
    std::puts("  body starts");
    int half = co_await Pause{};
    std::puts("  body resumed");
    co_return half + extra;
}

int main() {
    std::puts("main: call answer(2)");
    Traced t = answer(2);
    std::puts("main: first resume()");
    t.handle.resume();
    std::puts("main: second resume()");
    t.handle.resume();
    std::printf("main: done() = %s, result = %d\n", t.handle.done() ? "true" : "false", t.handle.promise().result);
    std::puts("main: destroy()");
    t.handle.destroy();
    std::puts("main: end");
}
Every hook, in the order it runs Python · trace_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra trace.cpp -o trace && ./trace
# GCC 16 asks for a frame of another size:
g++-16 -std=c++23 -Wall -Wextra trace.cpp -o trace-gcc && ./trace-gcc | grep 'operator new'
# and prints every other line exactly as Apple clang does:
./trace-gcc | grep -v 'operator new' > gcc.txt && ./trace | grep -v 'operator new' > clang.txt && diff clang.txt gcc.txt && echo identical
""")

Output

$ clang++ -std=c++23 -Wall -Wextra trace.cpp -o trace && ./trace
main: call answer(2)
  operator new: a frame of 32 bytes
  promise constructed
  get_return_object
  initial_suspend: wait for the first resume()
main: first resume()
  body starts
  await_ready: no, suspend
  await_suspend: back to the caller
main: second resume()
  await_resume: continue with 40
  body resumed
  return_value(42)
  final_suspend: keep the frame for the caller
main: done() = true, result = 42
main: destroy()
  promise destroyed
  operator delete: the frame is freed
main: end
# GCC 16 asks for a frame of another size:
$ g++-16 -std=c++23 -Wall -Wextra trace.cpp -o trace-gcc && ./trace-gcc | grep 'operator new'
  operator new: a frame of 40 bytes
# and prints every other line exactly as Apple clang does:
$ ./trace-gcc | grep -v 'operator new' > gcc.txt && ./trace | grep -v 'operator new' > clang.txt && diff clang.txt gcc.txt && echo identical
identical

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trace_session.py

Step through the same run, one hand-over of control at a time:

Interactive · message by message The traced run of answer(2), one hand-over at a time Step through the run trace.cpp recorded. Each arrow is a hand-over between main, the coroutine body and its promise; the sub-labels say what each holds.
The traced run of answer(2), one hand-over at a time, step 11 of 11. Messages so far: main to promise: answer(2); promise to main: Traced{handle}; main to answer(2): resume(); answer(2) to main: back to the resumer; main to answer(2): resume(); answer(2) to promise: return_value(42); promise to main: result = 42; main to promise: destroy().maindrives the coroutineanswer(2)frame freedpromisedestroyedanswer(2)Traced{handle}resume()back to the resumerresume()return_value(42)result = 42destroy()

The drawing shows the whole scene; its 11 steps are listed below.

Every step as text
  1. The call builds the frame. main calls answer(2). Before a single line of the body runs, operator new allocates a frame of 32 bytes and the promise is constructed inside it.
  2. get_return_object. The promise makes what the caller will receive: a Traced object that holds the coroutine handle.
  3. initial_suspend: wait. initial_suspend returns suspend_always, so the coroutine stops before its body and the call answer(2) returns to main. Nothing of the body has printed yet.
  4. First resume(). main calls resume(). The body starts, prints "body starts" and runs as far as co_await Pause{}.
  5. await_ready: no. co_await asks the awaiter first. await_ready returns false, so the coroutine suspends, and only then does await_suspend run, with the coroutine already suspended.
  6. Control returns to main. await_suspend returns void, so control goes back to whoever resumed the coroutine: the first resume() returns, and main prints "second resume()".
  7. Second resume(). The second resume() continues inside the co_await. await_resume returns 40, the value of the whole co_await Pause{} expression, so half is 40.
  8. co_return hands over 42. The body reaches co_return half + extra, that is 40 + 2, and the compiler calls return_value(42), which stores 42 in the promise.
  9. final_suspend: keep it. final_suspend also returns suspend_always, so the coroutine stops at its final suspension point instead of freeing its frame. done() is now true.
  10. main reads the result. main reads promise().result through the handle and prints done() = true, result = 42. That works only because the frame, and the promise inside it, are still alive.
  11. destroy() frees the frame. main calls destroy(): the promise is destroyed, then operator delete frees the frame. Any use of the handle after this would be a use after free.

The run shows three things. Calling answer(2) builds the frame and the promise before a single line of the body runs. Each resume() runs the body to its next suspension point: the first stops in co_await Pause{}, the second reaches co_return, which hands 42 to return_value. And because final_suspend also suspends, main can still read 42 through promise() until destroy() frees the frame.

The two compilers agree on every step, as the session’s last command checks; only the frame differs, 32 bytes with Apple clang and 40 with GCC 16, because each compiler lays out frames its own way. The size reaches the promise’s operator new as its argument, which is how a library can count frame allocations, take them from a pool, or refuse them.

Checkpoint

Check: The hooks in the order they run

  1. Question 1 of 2 The first resume() of trace.cpp has just returned to main. Where is the coroutine?

    Choose one answer.

    A hint

    Follow the run in the figure up to the step where control comes back to main.

    Show the answer: Question 1 of 2

    Answer: Suspended inside co_await Pause{}, after await_suspend ran

    Each resume() runs the body as far as its next suspension point. The first starts the body and stops in the co_await, because await_ready returned false; the second continues from await_resume to the end.

  2. Question 2 of 2 In trace.cpp, change await_resume to return 50 instead of 40. What result does main print?

    Type a number.

    A hint

    The value of co_await Pause{} is whatever await_resume returns.

    Show the answer: Question 2 of 2

    Answer: 52

    await_resume's return value is the value of the whole co_await expression, so half becomes 50, and co_return half + extra hands 50 + 2 = 52 to return_value.

Practise: keywords, hooks and handles

Ten questions on keywords, hooks and handles follow, one at a time, starting where your answers have been weakest.

Practice that adapts to you

Practise this lesson

10 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What does a coroutine save you from writing by hand?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: A state machine that keeps the locals and remembers where to continue

    Without coroutines, a function that pauses needs its locals in a struct and a record of where to continue. A coroutine keeps them in its frame, written by the compiler, so the source stays a plain function.

  2. Question 2 of 10 A team on C++20 wants a coroutine that yields the lines of a file. Besides a compiler that supports coroutines, what do they need?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: A return type with a promise_type, from a library or written by themselves

    C++20 brought the keywords and the building blocks, std::coroutine_handle, std::suspend_always and std::suspend_never, but no coroutine type you could return. Until C++23's std::generator, code used a library's types or wrote its own.

  3. Question 3 of 10 What turns Traced answer(int extra) into a coroutine?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: Its body uses co_await, co_yield or co_return.

    A function is a coroutine exactly when its body contains co_await, co_yield or co_return. Nothing in the declaration says so, which is why a caller cannot tell from the signature. The promise_type is then required, and the compiler looks it up through the return type.

  4. Question 4 of 10 A coroutine's body runs co_yield 7;. What happens?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: 7 is passed to the promise and the coroutine suspends until someone resumes it

    co_yield value passes value to the promise and suspends the coroutine. Whoever drives the coroutine reads the value through the promise and resumes the body when it wants the next one.

  5. Question 5 of 10 Which statements about std::coroutine_handle are true?

    Choose every answer that is right.

    Show the answer: Question 5 of 10

    Answer:

    • destroy() frees the frame, and the handle must not be used afterwards.
    • Copying a handle copies the frame's address, not the frame.
    • resume() continues a suspended coroutine from the point where it last suspended.

    A coroutine handle is a thin, copyable pointer to a frame with no ownership at all. Freeing the frame is the job of whoever calls destroy(), usually the object the coroutine returned, and nothing frees it automatically.

  6. Question 6 of 10 Put the steps of the skeleton around every coroutine body in the order they happen.

    Give each item its position, from 1 (first).

    Show the answer: Question 6 of 10

    Answer:

    1. Allocate the frame
    2. Construct the promise
    3. Call get_return_object()
    4. Await initial_suspend()
    5. Run the body
    6. Await final_suspend()

    The frame must exist before the promise can live in it, and the promise before get_return_object can make the caller's object. Then the coroutine awaits initial_suspend, runs its body when resumed, and awaits final_suspend at the end.

  7. Question 7 of 10 In trace.cpp, why can main still read the result through promise() after the body has finished?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: final_suspend returns suspend_always, so the frame, with its promise, stays alive until destroy().

    A coroutine that suspends at its final suspend point keeps its frame, and the promise lives in the frame. Had final_suspend returned suspend_never, the frame would be freed at the end of the body, and reading the promise afterwards would be a use after free.

  8. Question 8 of 10 How many times does main in trace.cpp call resume() before done() becomes true?

    Type a number.

    Show the answer: Question 8 of 10

    Answer: 2 calls

    The first resume() starts the body and stops in the co_await; the second continues from await_resume to co_return and final_suspend, after which done() is true.

  9. Question 9 of 10 Put these lines of the trace.cpp run in the order they were printed.

    Give each item its position, from 1 (first).

    Show the answer: Question 9 of 10

    Answer:

    1. operator new: a frame of 32 bytes
    2. promise constructed
    3. get_return_object
    4. initial_suspend: wait for the first resume()
    5. await_ready: no, suspend
    6. await_resume: continue with 40
    7. return_value(42)
    8. final_suspend: keep the frame for the caller

    The frame is allocated and the promise constructed before anything else, then the promise makes the return object, and initial_suspend decides that the body waits. The first resume() runs the body up to co_await, where await_ready says no; the second continues with await_resume, reaches co_return and ends at final_suspend.

  10. Question 10 of 10 Apple clang reported a frame of 32 bytes and GCC 16 one of 40 bytes for the same coroutine. What does that tell you?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Frame layout is up to each compiler, so code must not depend on a frame's size.

    The standard only says that the frame holds the promise, the parameter copies and the state the body needs across suspensions; how it is laid out is the compiler's business. A custom operator new in the promise sees the size each compiler asks for, as trace.cpp shows.

Every promise hook, what it decides and when it runs Pro: opens with a Pro, Premium or Ultimate pass

The traced example used the hooks that every coroutine needs. This part adds the rest, a copied parameter, co_yield and an exception, then turns the whole set into a table and a puzzle to check yourself against.

In this part:

  • A full trace: a parameter, co_yield and an exception
  • What each hook decides: the trade-offs of each choice
  • Your turn: order eight lines of a puzzle

4 runnable examples (C++, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Lifetime bugs in coroutine frames, reproduced under AddressSanitizer Pro: opens with a Pro, Premium or Ultimate pass

A coroutine’s frame usually outlives the call that created it, and anything the frame refers to must live at least as long. This part reproduces the three classic ways to break that rule, catches each with AddressSanitizer on both compilers, and fixes them.

In this part:

  • A reference parameter that dies before the coroutine runs, reproduced
  • Letting Clang flag a dangling reference parameter at compile time
  • A coroutine lambda whose captures die first, reproduced
  • One frame destroyed twice, reproduced
  • Locks held across a suspension, and why they break
  • Rules for frames that outlive their callers

1 diagram · 8 runnable examples (C++, C++ (dangling.cpp), C++ (dangling_fixed.cpp), C++ (lambda_dangling.cpp), C++ (lambda_fixed.cpp), C++ header, Python) · about 9 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher to mid · Warm-up

    What makes a C++ function a coroutine, and what do promise_type and coroutine_handle do?

    A function is a coroutine when its body contains co_await, co_yield or co_return; its declaration looks like any other function's. The compiler then needs a promise type, found through the return type, normally as a nested promise_type. The promise's member functions are called at fixed points: get_return_object makes what the caller receives, initial_suspend decides whether the body starts at once, yield_value and return_value receive values, unhandled_exception handles an escaping exception and final_suspend decides what happens at the end. The frame, usually on the heap, holds the promise, the parameter copies and the locals that live across suspensions. A std::coroutine_handle is a non-owning pointer to that frame: resume() continues the coroutine, done() says whether it has finished, promise() reaches the promise and destroy() frees the frame.

  2. Mid to senior

    A lazy coroutine task, Task load(const std::string& path), passes every unit test but crashes now and then in production, where it is created with a path built inside the calling function and run later by a scheduler. What is going on, and how do you fix it and keep it fixed?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    In a code review you see: auto task = [this, config]() -> Task { co_await fetch(); apply(config); }(); and the task is stored and run later. What is wrong with it, even though config is captured by value, and how would you rewrite it?

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    Design the return type of a coroutine used for background jobs: who owns the frame, when the body starts, how errors reach the caller and what happens if the job object is dropped. Walk through the promise hooks you would write and why.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A coroutine is a state machine the compiler writes: its locals stay in a frame, and it continues where it stopped.
  • Any of co_await, co_yield or co_return in a body makes a function a coroutine; nothing in its declaration says so.
  • The promise_type decides how a coroutine starts, where values go and how it ends; a std::coroutine_handle resumes and destroys a frame without owning it.
  • Every body runs inside the same skeleton, and final_suspend decides whether the frame outlives the body.
  • Each resume() runs the body to its next suspension point, as the traced run showed hook by hook.

Terms in this lesson

coroutine
A function that can suspend in the middle and be resumed later, with its local variables kept in a coroutine frame. In C++ a function is a coroutine when its body uses co_await, co_yield or co_return; nothing in its declaration says so.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.