1. Problem It Solves
The promise customizes coroutine behavior, the handle controls a suspended frame, and the frame stores state needed across suspensions. It makes an important assumption visible and checkable.
2. Prerequisites
The coroutine state-machine model from Day 44.
You should be able to compile a short program and read its output.
3. Core Idea
Promise is the control panel inside the frame; the handle is a small remote control that can resume, inspect, or destroy that frame. Read std::coroutine_handle as a precise promise; runtime preconditions still belong to the programmer.
4. Minimal Syntax
using Handle = std::coroutine_handle<promise_type>;5. How It Works
The program introduces the smallest relevant form of
std::coroutine_handle.It applies the feature to fixed data while required owners remain in scope.
It prints one result that can be checked against the source.
6. Common Mistakes
A raw handle does not automatically own cleanup, so double destruction, leaks, or resuming a completed/dangling frame causes undefined behavior.
Also check the required header, C++20 library support, lifetime, and deduced types.
7. When to Use It
Use it when building a coroutine abstraction whose lifecycle and result protocol must be explicit.
Avoid it when an existing task or generator abstraction already provides safe ownership.
8. Simple Example
A small move-only task obtains a typed handle from its promise, resumes once, reads a result, and destroys the frame in its destructor. The companion .cpp uses no input, so its result is easy to reproduce.
Complete sample code
Source file
cpp20/45_promise_handle_coroutine_frame/main.cpp
// Day 45: Promise Type, Coroutine Handle, and Coroutine Frame
#include <coroutine>
#include <iostream>
#include <utility>
struct Task {
struct promise_type;
using Handle = std::coroutine_handle<promise_type>;
struct promise_type {
int result{};
Task get_return_object() { return Task{Handle::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int value) noexcept { result = value; }
void unhandled_exception() { std::terminate(); }
};
Handle handle{};
explicit Task(Handle value) : handle{value} {}
Task(Task&& other) noexcept : handle{std::exchange(other.handle, {})} {}
Task(const Task&) = delete;
~Task() { if (handle) handle.destroy(); }
int run() { handle.resume(); return handle.promise().result; }
};
Task answer() {
co_return 42;
}
int main() {
Task task = answer();
std::cout << "result = " << task.run() << '\n';
}
9. Key Takeaways
std::coroutine_handleexpresses the central C++20 idea of this day.The example isolates one behavior with fixed data.
Compiler checks do not replace lifetime and runtime reasoning.
Prefer the smallest interface that states the real requirement.
10. Self-Check Questions
Easy — What is the main job of
std::coroutine_handlein the minimal example?Medium — Which promise member creates the return object containing the handle?
Hard — Why must the wrapper disable copying when its destructor calls
handle.destroy()?