1. Problem It Solves
Production coroutine types must coordinate cooperative cancellation, store exceptions, own frame lifetime, and understand where frame allocation occurs. It makes an important assumption visible and checkable.
2. Prerequisites
Task promises, stop tokens, RAII, exceptions, and frame ownership.
You should be able to compile a short program and read its output.
3. Core Idea
The frame is a managed job record: a stop token marks cancellation, the promise stores failure, and an owning wrapper destroys the allocated record once. Read unhandled_exception as a precise promise; runtime preconditions still belong to the programmer.
4. Minimal Syntax
void unhandled_exception() { error = std::current_exception(); }5. How It Works
The program introduces the smallest relevant form of
unhandled_exception.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
Destroying a running frame, swallowing stored exceptions, capturing dead references, or assuming cancellation frees the frame immediately can all break correctness.
Also check the required header, C++20 library support, lifetime, and deduced types.
7. When to Use It
Use it when you are implementing or auditing the ownership protocol of a coroutine abstraction.
Avoid it when a tested library task already provides the required semantics.
8. Simple Example
The task counts promise allocation, stores an exception with unhandled_exception, observes a requested stop, and destroys its frame by RAII. The companion .cpp uses no input, so its result is easy to reproduce.
Complete sample code
Source file
cpp20/49_coroutine_cancellation_exception_lifetime/main.cpp
// Day 49: Coroutine Cancellation, Exceptions, Lifetime, and Allocation
#include <coroutine>
#include <cstddef>
#include <exception>
#include <iostream>
#include <new>
#include <stdexcept>
#include <stop_token>
#include <utility>
int live_frames = 0;
struct Task {
struct promise_type;
using Handle = std::coroutine_handle<promise_type>;
struct promise_type {
int result{};
std::exception_ptr error{};
static void* operator new(std::size_t size) {
++live_frames;
return ::operator new(size);
}
static void operator delete(void* pointer, std::size_t size) noexcept {
--live_frames;
::operator delete(pointer, size);
}
Task get_return_object() { return Task{Handle::from_promise(*this)}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int value) noexcept { result = value; }
void unhandled_exception() { error = std::current_exception(); }
};
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 value() const {
if (handle.promise().error) std::rethrow_exception(handle.promise().error);
return handle.promise().result;
}
};
Task work(std::stop_token token, bool fail) {
if (token.stop_requested()) co_return -1;
if (fail) throw std::runtime_error("task failed");
co_return 1;
}
int main() {
{
std::stop_source source;
source.request_stop();
Task cancelled = work(source.get_token(), false);
Task failed = work({}, true);
std::cout << "cancelled result = " << cancelled.value() << '\n';
try { (void)failed.value(); }
catch (const std::exception& error) { std::cout << error.what() << '\n'; }
std::cout << "live frames in scope = " << live_frames << '\n';
}
std::cout << "live frames after scope = " << live_frames << '\n';
}
9. Key Takeaways
unhandled_exceptionexpresses 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
unhandled_exceptionin the minimal example?Medium — Where is the thrown exception stored before the caller rethrows it?
Hard — Why does requesting cancellation not remove the need for exactly one later
destroy()of the coroutine frame?