mirror of
https://github.com/boostorg/boostlook.git
synced 2026-02-25 16:22:12 +00:00
1000 lines
36 KiB
HTML
1000 lines
36 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
|
<style>html.fonts-loading{visibility:hidden;opacity:0}</style>
|
|
<script>document.documentElement.classList.add('fonts-loading');</script>
|
|
|
|
<link rel="preload" href="../../_/font/NotoSansDisplay.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/NotoSansDisplay-Italic.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/MonaspaceNeon-Var.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/MonaspaceXenon-Var.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
var revealed = false;
|
|
|
|
var reveal = function() {
|
|
if (revealed) return;
|
|
revealed = true;
|
|
document.documentElement.classList.remove('fonts-loading');
|
|
};
|
|
|
|
setTimeout(reveal, 3000);
|
|
|
|
if (!('FontFace' in window) || !('fonts' in document)) {
|
|
setTimeout(reveal, 100);
|
|
return;
|
|
}
|
|
|
|
var uiRoot = '../../_';
|
|
var fonts = [
|
|
{
|
|
family: 'Noto Sans',
|
|
url: uiRoot + '/font/NotoSansDisplay.woff2',
|
|
descriptors: { style: 'normal', weight: '100 900', stretch: '62.5% 100%' }
|
|
},
|
|
{
|
|
family: 'Noto Sans',
|
|
url: uiRoot + '/font/NotoSansDisplay-Italic.woff2',
|
|
descriptors: { style: 'italic', weight: '100 900', stretch: '62.5% 100%' }
|
|
},
|
|
{
|
|
family: 'Monaspace Neon',
|
|
url: uiRoot + '/font/MonaspaceNeon-Var.woff2',
|
|
descriptors: { style: 'normal', weight: '400' }
|
|
},
|
|
{
|
|
family: 'Monaspace Xenon',
|
|
url: uiRoot + '/font/MonaspaceXenon-Var.woff2',
|
|
descriptors: { style: 'italic', weight: '400' }
|
|
}
|
|
];
|
|
|
|
var loadPromises = fonts.map(function(f) {
|
|
try {
|
|
var face = new FontFace(f.family, 'url("' + f.url + '")', f.descriptors);
|
|
return face.load().then(function(loaded) {
|
|
document.fonts.add(loaded);
|
|
return loaded;
|
|
}).catch(function() {
|
|
return null;
|
|
});
|
|
} catch (e) {
|
|
return Promise.resolve(null);
|
|
}
|
|
});
|
|
|
|
Promise.all(loadPromises)
|
|
.then(function() {
|
|
return document.fonts.ready;
|
|
})
|
|
.then(reveal)
|
|
.catch(reveal);
|
|
})();
|
|
</script> <title>Part IV: Advanced Topics :: Boost Libraries Documentation</title>
|
|
<link rel="canonical" href="https://antora.cppalliance.org/develop/lib/doc/capy/2.cpp20-coroutines/2d.advanced.html">
|
|
<link rel="prev" href="2c.machinery.html">
|
|
<link rel="next" href="../3.concurrency/3.intro.html">
|
|
<meta name="generator" content="Antora 3.1.14">
|
|
<link rel="stylesheet" href="../../_/css/boostlook.css">
|
|
<link rel="stylesheet" href="../../_/css/site.css">
|
|
<link rel="stylesheet" href="../../_/css/vendor/tabs.css">
|
|
<script>
|
|
(function() {
|
|
if (window.self !== window.top) return;
|
|
var theme = localStorage.getItem('antora-theme');
|
|
if (!theme && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
theme = 'dark';
|
|
}
|
|
if (theme === 'dark') document.documentElement.classList.add('dark');
|
|
})();
|
|
</script>
|
|
<script>var uiRootPath = '../../_'</script>
|
|
<link rel="icon" href="../../_/img/favicons/favicon.ico" type="image/x-icon">
|
|
<!-- Favicon configuration -->
|
|
<link rel="apple-touch-icon" sizes="180x180" href="../../_/img/favicons/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="../../_/img/favicons/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="../../_/img/favicons/favicon-16x16.png">
|
|
<link rel="manifest" href="../../_/img/favicons/site.webmanifest">
|
|
<link rel="shortcut icon" href="../../_/img/favicons/favicon.ico">
|
|
</head>
|
|
<body class="article toc2 toc-left">
|
|
<div class="boostlook">
|
|
<script type="module">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; mermaid.initialize({"startOnLoad":true});</script> <div id="header">
|
|
<div id="toc" class="nav-container toc2" data-component="capy" data-version="">
|
|
<aside class="nav">
|
|
<button class="nav-close"></button>
|
|
<div class="panels">
|
|
<div class="nav-panel-menu is-active" data-panel="menu">
|
|
<nav class="nav-menu">
|
|
<div class="title-row">
|
|
<h3 class="title"><a href="../index.html">Boost.Capy</a></h3>
|
|
<button class="theme-toggle" aria-label="Toggle dark mode" title="Toggle theme" style="display:none">
|
|
<i class="fas fa-sun theme-icon-light"></i>
|
|
<i class="fas fa-moon theme-icon-dark"></i>
|
|
</button> </div>
|
|
<ul class="nav-list">
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../index.html">Introduction</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../why-capy.html">Why Capy?</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../quick-start.html">Quick Start</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="2.intro.html">Introduction To C++20 Coroutines</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="2a.foundations.html">Part I: Foundations</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="2b.syntax.html">Part II: C++20 Syntax</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="2c.machinery.html">Part III: Coroutine Machinery</a>
|
|
</li>
|
|
<li class=" is-current-page" data-depth="2">
|
|
<a class="nav-link" href="2d.advanced.html">Part IV: Advanced Topics</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../3.concurrency/3.intro.html">Introduction to Concurrency</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3a.foundations.html">Part I: Foundations</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3b.synchronization.html">Part II: Synchronization</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3c.advanced.html">Part III: Advanced Primitives</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3d.patterns.html">Part IV: Communication & Patterns</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../4.coroutines/4.intro.html">Coroutines in Capy</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4a.tasks.html">The task Type</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4b.launching.html">Launching Coroutines</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4c.executors.html">Executors and Execution Contexts</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4d.io-awaitable.html">The IoAwaitable Protocol</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4e.cancellation.html">Stop Tokens and Cancellation</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4f.composition.html">Concurrent Composition</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4g.allocators.html">Frame Allocators</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../5.buffers/5.intro.html">Buffer Sequences</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5a.overview.html">Why Concepts, Not Spans</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5b.types.html">Buffer Types</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5c.sequences.html">Buffer Sequences</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5d.system-io.html">System I/O Integration</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5e.algorithms.html">Buffer Algorithms</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5f.dynamic.html">Dynamic Buffers</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../6.streams/6.intro.html">Stream Concepts</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6a.overview.html">Overview</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6b.streams.html">Streams (Partial I/O)</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6c.sources-sinks.html">Sources and Sinks (Complete I/O)</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6d.buffer-concepts.html">Buffer Sources and Sinks</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6e.algorithms.html">Transfer Algorithms</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6f.isolation.html">Physical Isolation</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../7.examples/7.intro.html">Example Programs</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7a.hello-task.html">Hello Task</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7b.producer-consumer.html">Producer-Consumer</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7c.buffer-composition.html">Buffer Composition</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7d.mock-stream-testing.html">Mock Stream Testing</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7e.type-erased-echo.html">Type-Erased Echo</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7f.timeout-cancellation.html">Timeout with Cancellation</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7g.parallel-fetch.html">Parallel Fetch</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7h.custom-dynamic-buffer.html">Custom Dynamic Buffer</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7i.echo-server-corosio.html">Echo Server with Corosio</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7j.stream-pipeline.html">Stream Pipeline</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../8.design/8.intro.html">Design</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8a.CapyLayering.html">Layered Abstractions</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8b.Separation.html">Why Capy Is Separate</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8c.ReadStream.html">ReadStream</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8d.ReadSource.html">ReadSource</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8e.BufferSource.html">BufferSource</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8f.WriteStream.html">WriteStream</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8g.WriteSink.html">WriteSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8h.BufferSink.html">BufferSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8i.TypeEraseAwaitable.html">Type-Erasing Awaitables</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8j.any_buffer_sink.html">AnyBufferSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8k.Executor.html">Executor</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8l.RunApi.html">Run API</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8m.WhyNotCobalt.html">Why Not Cobalt?</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8n.WhyNotCobaltConcepts.html">Why Not Cobalt Concepts?</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../8.design/8o.WhyNotTMC.html">Why Not TooManyCooks?</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../reference/boost/capy.html">Reference</a>
|
|
</li>
|
|
</ul>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</div> <div id="content">
|
|
<article class="doc max-width-reset">
|
|
<div class="toolbar" role="navigation">
|
|
<button class="nav-toggle"></button>
|
|
<nav class="breadcrumbs" aria-label="breadcrumbs">
|
|
<ul>
|
|
<li>
|
|
<a href="../index.html" aria-label="Home: Boost.Capy">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 -960 960 960" fill="#000000" aria-hidden="true"><path d="M160-120v-480l320-240 320 240v480H560v-280H400v280H160Z"/></svg>
|
|
</a>
|
|
</li>
|
|
<li><a href="2.intro.html">Introduction To C++20 Coroutines</a></li>
|
|
<li><a href="2d.advanced.html">Part IV: Advanced Topics</a></li>
|
|
</ul>
|
|
</nav>
|
|
<div class="spirit-nav">
|
|
<a accesskey="p" href="2c.machinery.html">
|
|
<span class="material-symbols-outlined" title="Previous: Part III: Coroutine Machinery">arrow_back</span>
|
|
</a>
|
|
<a accesskey="u" href="2.intro.html">
|
|
<span class="material-symbols-outlined" title="Up: Introduction To C++20 Coroutines">arrow_upward</span>
|
|
</a>
|
|
<a accesskey="n" href="../3.concurrency/3.intro.html">
|
|
<span class="material-symbols-outlined" title="Next: Introduction to Concurrency">arrow_forward</span>
|
|
</a>
|
|
</div></div>
|
|
<h1 class="page">Part IV: Advanced Topics</h1>
|
|
<div id="preamble">
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>This section covers advanced coroutine topics: symmetric transfer for efficient resumption, coroutine allocation strategies, and exception handling. These concepts are essential for building production-quality coroutine types.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_prerequisites"><a class="anchor" href="#_prerequisites"></a>Prerequisites</h2>
|
|
<div class="sectionbody">
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Completed <a href="2c.machinery.html" class="xref page">Part III: Coroutine Machinery</a></p>
|
|
</li>
|
|
<li>
|
|
<p>Understanding of promise types, coroutine handles, and generators</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_symmetric_transfer"><a class="anchor" href="#_symmetric_transfer"></a>Symmetric Transfer</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>When a coroutine completes or awaits another coroutine, control must transfer somewhere. The naive approach—simply calling <code>handle.resume()</code>—has a problem: each nested coroutine adds a frame to the call stack. With deep nesting, you risk stack overflow.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Symmetric transfer</strong> solves this by returning a coroutine handle from <code>await_suspend</code>. Instead of resuming the target coroutine via a function call, the compiler generates a tail call that transfers control without growing the stack.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_the_problem_stack_accumulation"><a class="anchor" href="#_the_problem_stack_accumulation"></a>The Problem: Stack Accumulation</h3>
|
|
<div class="paragraph">
|
|
<p>Consider a chain of coroutines where each awaits the next:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">task<> a() { co_await b(); }
|
|
task<> b() { co_await c(); }
|
|
task<> c() { co_return; }</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Without symmetric transfer, when <code>a</code> awaits <code>b</code>:</p>
|
|
</div>
|
|
<div class="olist arabic">
|
|
<ol class="arabic">
|
|
<li>
|
|
<p><code>a</code> calls into the awaiter’s <code>await_suspend</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>await_suspend</code> calls <code>b.handle.resume()</code></p>
|
|
</li>
|
|
<li>
|
|
<p><code>b</code> runs, calls into its awaiter’s <code>await_suspend</code></p>
|
|
</li>
|
|
<li>
|
|
<p>That calls <code>c.handle.resume()</code></p>
|
|
</li>
|
|
<li>
|
|
<p>The stack now has frames for `a’s suspension, `b’s suspension, and `c’s execution</p>
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Each suspension adds a stack frame. With thousands of nested coroutines, the stack overflows.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_the_solution_return_the_handle"><a class="anchor" href="#_the_solution_return_the_handle"></a>The Solution: Return the Handle</h3>
|
|
<div class="paragraph">
|
|
<p><code>await_suspend</code> can return a <code>std::coroutine_handle<></code>:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)
|
|
{
|
|
// store continuation for later
|
|
continuation_ = h;
|
|
|
|
// return handle to resume (instead of calling resume())
|
|
return next_coroutine_;
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>When <code>await_suspend</code> returns a handle, the compiler generates code equivalent to:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">auto next = awaiter.await_suspend(current);
|
|
if (next != std::noop_coroutine())
|
|
next.resume(); // tail call, doesn't grow stack</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The key insight: returning a handle enables the compiler to implement the resumption as a tail call. The current stack frame is reused for the next coroutine.</p>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_return_types_for_await_suspend"><a class="anchor" href="#_return_types_for_await_suspend"></a>Return Types for await_suspend</h3>
|
|
<div class="paragraph">
|
|
<p><code>await_suspend</code> can return three types:</p>
|
|
</div>
|
|
<div class="dlist">
|
|
<dl>
|
|
<dt class="hdlist1"><code>void</code></dt>
|
|
<dd>
|
|
<p>Always suspend. The coroutine is suspended and some external mechanism must resume it.</p>
|
|
</dd>
|
|
<dt class="hdlist1"><code>bool</code></dt>
|
|
<dd>
|
|
<p>Conditional suspension. Return <code>true</code> to suspend, <code>false</code> to continue without suspending.</p>
|
|
</dd>
|
|
<dt class="hdlist1"><code>std::coroutine_handle<></code></dt>
|
|
<dd>
|
|
<p>Symmetric transfer. The returned handle is resumed; returning <code>std::noop_coroutine()</code> suspends without resuming anything.</p>
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_using_symmetric_transfer_in_generators"><a class="anchor" href="#_using_symmetric_transfer_in_generators"></a>Using Symmetric Transfer in Generators</h3>
|
|
<div class="paragraph">
|
|
<p>A production generator uses symmetric transfer at <code>final_suspend</code> to return to whoever is iterating:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">auto final_suspend() noexcept
|
|
{
|
|
struct awaiter
|
|
{
|
|
promise_type* p_;
|
|
|
|
bool await_ready() const noexcept { return false; }
|
|
|
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept
|
|
{
|
|
// Return to the consumer that called resume()
|
|
return p_->consumer_handle_;
|
|
}
|
|
|
|
void await_resume() const noexcept {}
|
|
};
|
|
return awaiter{this};
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_coroutine_allocation"><a class="anchor" href="#_coroutine_allocation"></a>Coroutine Allocation</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Every coroutine needs memory for its <strong>coroutine frame</strong>—the heap-allocated structure holding local variables, parameters, and suspension state.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_default_allocation"><a class="anchor" href="#_default_allocation"></a>Default Allocation</h3>
|
|
<div class="paragraph">
|
|
<p>By default, coroutines allocate their frames using <code>operator new</code>. The frame size depends on:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Local variables in the coroutine</p>
|
|
</li>
|
|
<li>
|
|
<p>Parameters (copied into the frame)</p>
|
|
</li>
|
|
<li>
|
|
<p>Promise type members</p>
|
|
</li>
|
|
<li>
|
|
<p>Compiler-generated bookkeeping</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_heap_allocation_elision_optimization_halo"><a class="anchor" href="#_heap_allocation_elision_optimization_halo"></a>Heap Allocation eLision Optimization (HALO)</h3>
|
|
<div class="paragraph">
|
|
<p>Compilers can sometimes eliminate coroutine frame allocation entirely through <strong>HALO</strong> (Heap Allocation eLision Optimization). When the compiler can prove that:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>The coroutine’s lifetime is contained within the caller’s lifetime</p>
|
|
</li>
|
|
<li>
|
|
<p>The frame size is known at compile time</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>…​it may allocate the frame on the caller’s stack instead of the heap.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>HALO is most effective when:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Coroutines are awaited immediately after creation</p>
|
|
</li>
|
|
<li>
|
|
<p>The coroutine type is marked with <code><a id="clang::coro_await_elidable"></a></code> (Clang extension)</p>
|
|
</li>
|
|
<li>
|
|
<p>Optimization is enabled (<code>-O2</code> or higher)</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">// HALO might apply here because the task is awaited immediately
|
|
co_await compute_something();
|
|
|
|
// HALO cannot apply here because the task escapes
|
|
auto task = compute_something();
|
|
store_for_later(std::move(task));</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_custom_allocators"><a class="anchor" href="#_custom_allocators"></a>Custom Allocators</h3>
|
|
<div class="paragraph">
|
|
<p>Promise types can customize allocation by providing <code>operator new</code> and <code>operator delete</code>:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">struct promise_type
|
|
{
|
|
// Custom allocation
|
|
static void* operator new(std::size_t size)
|
|
{
|
|
return my_allocator.allocate(size);
|
|
}
|
|
|
|
static void operator delete(void* ptr, std::size_t size)
|
|
{
|
|
my_allocator.deallocate(ptr, size);
|
|
}
|
|
|
|
// ... rest of promise type
|
|
};</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The promise’s <code>operator new</code> receives only the frame size. To access allocator arguments passed to the coroutine, use the leading allocator convention with <code>std::allocator_arg_t</code> as the first parameter.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_exception_handling"><a class="anchor" href="#_exception_handling"></a>Exception Handling</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Exceptions in coroutines require special handling because a coroutine can suspend and resume across different call stacks.</p>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_the_exception_flow"><a class="anchor" href="#_the_exception_flow"></a>The Exception Flow</h3>
|
|
<div class="paragraph">
|
|
<p>When an exception is thrown inside a coroutine and not caught:</p>
|
|
</div>
|
|
<div class="olist arabic">
|
|
<ol class="arabic">
|
|
<li>
|
|
<p>The exception is caught by an implicit try-catch surrounding the coroutine body</p>
|
|
</li>
|
|
<li>
|
|
<p><code>promise.unhandled_exception()</code> is called while the exception is active</p>
|
|
</li>
|
|
<li>
|
|
<p>After <code>unhandled_exception()</code> returns, <code>co_await promise.final_suspend()</code> executes</p>
|
|
</li>
|
|
<li>
|
|
<p>The coroutine completes (suspended or destroyed, depending on <code>final_suspend</code>)</p>
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_options_in_unhandled_exception"><a class="anchor" href="#_options_in_unhandled_exception"></a>Options in unhandled_exception()</h3>
|
|
<div class="paragraph">
|
|
<p><strong>Terminate the program:</strong></p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">void unhandled_exception()
|
|
{
|
|
std::terminate();
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Store for later retrieval:</strong></p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">void unhandled_exception()
|
|
{
|
|
exception_ = std::current_exception();
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Rethrow immediately:</strong></p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">void unhandled_exception()
|
|
{
|
|
throw; // propagates to whoever resumed the coroutine
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Swallow the exception:</strong></p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">void unhandled_exception()
|
|
{
|
|
// silently ignored - almost always a mistake
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_the_store_and_rethrow_pattern"><a class="anchor" href="#_the_store_and_rethrow_pattern"></a>The Store-and-Rethrow Pattern</h3>
|
|
<div class="paragraph">
|
|
<p>For tasks and generators where callers expect results, store the exception and rethrow it when results are requested:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">struct promise_type
|
|
{
|
|
std::exception_ptr exception_;
|
|
|
|
void unhandled_exception()
|
|
{
|
|
exception_ = std::current_exception();
|
|
}
|
|
};
|
|
|
|
// In the return object's result accessor:
|
|
T get_result()
|
|
{
|
|
if (handle_.promise().exception_)
|
|
std::rethrow_exception(handle_.promise().exception_);
|
|
return std::move(handle_.promise().result_);
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_exception_example"><a class="anchor" href="#_exception_example"></a>Exception Example</h3>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">#include <coroutine>
|
|
#include <exception>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
struct Task
|
|
{
|
|
struct promise_type
|
|
{
|
|
std::exception_ptr exception;
|
|
|
|
Task get_return_object()
|
|
{
|
|
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
|
|
}
|
|
|
|
std::suspend_always initial_suspend() { return {}; }
|
|
std::suspend_always final_suspend() noexcept { return {}; }
|
|
void return_void() {}
|
|
|
|
void unhandled_exception()
|
|
{
|
|
exception = std::current_exception();
|
|
}
|
|
};
|
|
|
|
std::coroutine_handle<promise_type> handle;
|
|
|
|
Task(std::coroutine_handle<promise_type> h) : handle(h) {}
|
|
~Task() { if (handle) handle.destroy(); }
|
|
|
|
void run() { handle.resume(); }
|
|
|
|
void check_exception()
|
|
{
|
|
if (handle.promise().exception)
|
|
std::rethrow_exception(handle.promise().exception);
|
|
}
|
|
};
|
|
|
|
Task risky_operation()
|
|
{
|
|
std::cout << "Starting risky operation" << std::endl;
|
|
throw std::runtime_error("Something went wrong");
|
|
co_return; // never reached
|
|
}
|
|
|
|
int main()
|
|
{
|
|
Task task = risky_operation();
|
|
|
|
try
|
|
{
|
|
task.run();
|
|
task.check_exception();
|
|
std::cout << "Operation completed successfully" << std::endl;
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
std::cout << "Operation failed: " << e.what() << std::endl;
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Output:</strong></p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-asciidoc hljs" data-lang="asciidoc">Starting risky operation
|
|
Operation failed: Something went wrong</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect2">
|
|
<h3 id="_initialization_exceptions"><a class="anchor" href="#_initialization_exceptions"></a>Initialization Exceptions</h3>
|
|
<div class="paragraph">
|
|
<p>Exceptions thrown before the first suspension point (before <code>initial_suspend</code> completes) propagate directly to the caller without going through <code>unhandled_exception()</code>. If <code>initial_suspend()</code> returns <code>suspend_always</code>, the coroutine suspends before any user code runs, avoiding this edge case.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_building_a_production_generator"><a class="anchor" href="#_building_a_production_generator"></a>Building a Production Generator</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>With all these concepts, here is a production-quality generic generator:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">#include <coroutine>
|
|
#include <exception>
|
|
#include <utility>
|
|
|
|
template<typename T>
|
|
class Generator
|
|
{
|
|
public:
|
|
struct promise_type
|
|
{
|
|
T value_;
|
|
std::exception_ptr exception_;
|
|
|
|
Generator get_return_object()
|
|
{
|
|
return Generator{Handle::from_promise(*this)};
|
|
}
|
|
|
|
std::suspend_always initial_suspend() noexcept { return {}; }
|
|
std::suspend_always final_suspend() noexcept { return {}; }
|
|
|
|
std::suspend_always yield_value(T v)
|
|
{
|
|
value_ = std::move(v);
|
|
return {};
|
|
}
|
|
|
|
void return_void() noexcept {}
|
|
|
|
void unhandled_exception()
|
|
{
|
|
exception_ = std::current_exception();
|
|
}
|
|
|
|
// Prevent co_await inside generators
|
|
template<typename U>
|
|
std::suspend_never await_transform(U&&) = delete;
|
|
};
|
|
|
|
using Handle = std::coroutine_handle<promise_type>;
|
|
|
|
class iterator
|
|
{
|
|
Handle handle_;
|
|
|
|
public:
|
|
using iterator_category = std::input_iterator_tag;
|
|
using value_type = T;
|
|
using difference_type = std::ptrdiff_t;
|
|
|
|
iterator() : handle_(nullptr) {}
|
|
explicit iterator(Handle h) : handle_(h) {}
|
|
|
|
iterator& operator++()
|
|
{
|
|
handle_.resume();
|
|
if (handle_.done())
|
|
{
|
|
auto& promise = handle_.promise();
|
|
handle_ = nullptr;
|
|
if (promise.exception_)
|
|
std::rethrow_exception(promise.exception_);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
T& operator*() const { return handle_.promise().value_; }
|
|
bool operator==(iterator const& other) const
|
|
{
|
|
return handle_ == other.handle_;
|
|
}
|
|
};
|
|
|
|
iterator begin()
|
|
{
|
|
if (handle_)
|
|
{
|
|
handle_.resume();
|
|
if (handle_.done())
|
|
{
|
|
auto& promise = handle_.promise();
|
|
if (promise.exception_)
|
|
std::rethrow_exception(promise.exception_);
|
|
return iterator{};
|
|
}
|
|
}
|
|
return iterator{handle_};
|
|
}
|
|
|
|
iterator end() { return iterator{}; }
|
|
|
|
~Generator() { if (handle_) handle_.destroy(); }
|
|
|
|
Generator(Generator const&) = delete;
|
|
Generator& operator=(Generator const&) = delete;
|
|
|
|
Generator(Generator&& other) noexcept
|
|
: handle_(std::exchange(other.handle_, nullptr)) {}
|
|
|
|
Generator& operator=(Generator&& other) noexcept
|
|
{
|
|
if (this != &other)
|
|
{
|
|
if (handle_) handle_.destroy();
|
|
handle_ = std::exchange(other.handle_, nullptr);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
Handle handle_;
|
|
|
|
explicit Generator(Handle h) : handle_(h) {}
|
|
};</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This generator:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p>Provides a standard iterator interface for range-based for loops</p>
|
|
</li>
|
|
<li>
|
|
<p>Stores and rethrows exceptions during iteration</p>
|
|
</li>
|
|
<li>
|
|
<p>Prevents <code>co_await</code> inside generators via deleted <code>await_transform</code></p>
|
|
</li>
|
|
<li>
|
|
<p>Manages coroutine lifetime with RAII</p>
|
|
</li>
|
|
<li>
|
|
<p>Supports move semantics</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_conclusion"><a class="anchor" href="#_conclusion"></a>Conclusion</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>You have now learned the complete mechanics of C++20 coroutines:</p>
|
|
</div>
|
|
<div class="ulist">
|
|
<ul>
|
|
<li>
|
|
<p><strong>Keywords</strong> — <code>co_await</code>, <code>co_yield</code>, and <code>co_return</code> transform functions into coroutines</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Promise types</strong> — Control coroutine behavior at initialization, suspension, completion, and error handling</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Coroutine handles</strong> — Lightweight references for resuming, querying, and destroying coroutines</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Symmetric transfer</strong> — Efficient control flow without stack accumulation</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Allocation</strong> — Custom allocation and HALO optimization</p>
|
|
</li>
|
|
<li>
|
|
<p><strong>Exception handling</strong> — Capturing and propagating exceptions across suspension points</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>These fundamentals prepare you for understanding Capy’s <code>task<T></code> type and the IoAwaitable protocol, which build on standard coroutine machinery with executor affinity and stop token propagation.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="edit-this-page">
|
|
<a href="https://github.com/cppalliance/capy/edit/develop/doc/modules/ROOT/pages/2.cpp20-coroutines/2d.advanced.adoc">Edit this Page</a>
|
|
</div>
|
|
<nav class="pagination">
|
|
<span class="prev"><a href="2c.machinery.html">Part III: Coroutine Machinery</a></span>
|
|
<span class="next"><a href="../3.concurrency/3.intro.html">Introduction to Concurrency</a></span>
|
|
</nav>
|
|
</article>
|
|
</div>
|
|
<div id="footer">
|
|
<script id="site-script" src="../../_/js/site.js" data-ui-root-path="../../_"></script>
|
|
<script async src="../../_/js/vendor/highlight.js"></script>
|
|
<script async src="../../_/js/vendor/tabs.js" data-sync-storage-key="preferred-tab"></script>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|