<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="mpark/wg21" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <meta name="dcterms.date" content="2025-07-15" />
  <title>Coroutine Task Issues</title>
  <style>
      code{white-space: pre-wrap;}
      span.smallcaps{font-variant: small-caps;}
      span.underline{text-decoration: underline;}
      div.column{display: inline-block; vertical-align: top; width: 50%;}
      div.csl-block{margin-left: 1.5em;}
      ul.task-list{list-style: none;}
      pre > code.sourceCode { white-space: pre; position: relative; }
      pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
      pre > code.sourceCode > span:empty { height: 1.2em; }
      .sourceCode { overflow: visible; }
      code.sourceCode > span { color: inherit; text-decoration: inherit; }
      div.sourceCode { margin: 1em 0; }
      pre.sourceCode { margin: 0; }
      @media screen {
      div.sourceCode { overflow: auto; }
      }
      @media print {
      pre > code.sourceCode { white-space: pre-wrap; }
      pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
      }
      pre.numberSource code
        { counter-reset: source-line 0; }
      pre.numberSource code > span
        { position: relative; left: -4em; counter-increment: source-line; }
      pre.numberSource code > span > a:first-child::before
        { content: counter(source-line);
          position: relative; left: -1em; text-align: right; vertical-align: baseline;
          border: none; display: inline-block;
          -webkit-touch-callout: none; -webkit-user-select: none;
          -khtml-user-select: none; -moz-user-select: none;
          -ms-user-select: none; user-select: none;
          padding: 0 4px; width: 4em;
          color: #aaaaaa;
        }
      pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
      div.sourceCode
        {  background-color: #f6f8fa; }
      @media screen {
      pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
      }
      code span { } /* Normal */
      code span.al { color: #ff0000; } /* Alert */
      code span.an { } /* Annotation */
      code span.at { } /* Attribute */
      code span.bn { color: #9f6807; } /* BaseN */
      code span.bu { color: #9f6807; } /* BuiltIn */
      code span.cf { color: #00607c; } /* ControlFlow */
      code span.ch { color: #9f6807; } /* Char */
      code span.cn { } /* Constant */
      code span.co { color: #008000; font-style: italic; } /* Comment */
      code span.cv { color: #008000; font-style: italic; } /* CommentVar */
      code span.do { color: #008000; } /* Documentation */
      code span.dt { color: #00607c; } /* DataType */
      code span.dv { color: #9f6807; } /* DecVal */
      code span.er { color: #ff0000; font-weight: bold; } /* Error */
      code span.ex { } /* Extension */
      code span.fl { color: #9f6807; } /* Float */
      code span.fu { } /* Function */
      code span.im { } /* Import */
      code span.in { color: #008000; } /* Information */
      code span.kw { color: #00607c; } /* Keyword */
      code span.op { color: #af1915; } /* Operator */
      code span.ot { } /* Other */
      code span.pp { color: #6f4e37; } /* Preprocessor */
      code span.re { } /* RegionMarker */
      code span.sc { color: #9f6807; } /* SpecialChar */
      code span.ss { color: #9f6807; } /* SpecialString */
      code span.st { color: #9f6807; } /* String */
      code span.va { } /* Variable */
      code span.vs { color: #9f6807; } /* VerbatimString */
      code span.wa { color: #008000; font-weight: bold; } /* Warning */
      code.diff {color: #898887}
      code.diff span.va {color: #006e28}
      code.diff span.st {color: #bf0303}
  </style>
  <style type="text/css">
body {
margin: 5em;
font-family: serif;

hyphens: auto;
line-height: 1.35;
text-align: justify;
}
@media screen and (max-width: 30em) {
body {
margin: 1.5em;
}
}
div.wrapper {
max-width: 60em;
margin: auto;
}
ul {
list-style-type: none;
padding-left: 2em;
margin-top: -0.2em;
margin-bottom: -0.2em;
}
a {
text-decoration: none;
color: #4183C4;
}
a.hidden_link {
text-decoration: none;
color: inherit;
}
li {
margin-top: 0.6em;
margin-bottom: 0.6em;
}
h1, h2, h3, h4 {
position: relative;
line-height: 1;
}
a.self-link {
position: absolute;
top: 0;
left: calc(-1 * (3.5rem - 26px));
width: calc(3.5rem - 26px);
height: 2em;
text-align: center;
border: none;
transition: opacity .2s;
opacity: .5;
font-family: sans-serif;
font-weight: normal;
font-size: 83%;
}
a.self-link:hover { opacity: 1; }
a.self-link::before { content: "§"; }
ul > li:before {
content: "\2014";
position: absolute;
margin-left: -1.5em;
}
:target { background-color: #C9FBC9; }
:target .codeblock { background-color: #C9FBC9; }
:target ul { background-color: #C9FBC9; }
.abbr_ref { float: right; }
.folded_abbr_ref { float: right; }
:target .folded_abbr_ref { display: none; }
:target .unfolded_abbr_ref { float: right; display: inherit; }
.unfolded_abbr_ref { display: none; }
.secnum { display: inline-block; min-width: 35pt; }
.header-section-number { display: inline-block; min-width: 35pt; }
.annexnum { display: block; }
div.sourceLinkParent {
float: right;
}
a.sourceLink {
position: absolute;
opacity: 0;
margin-left: 10pt;
}
a.sourceLink:hover {
opacity: 1;
}
a.itemDeclLink {
position: absolute;
font-size: 75%;
text-align: right;
width: 5em;
opacity: 0;
}
a.itemDeclLink:hover { opacity: 1; }
span.marginalizedparent {
position: relative;
left: -5em;
}
li span.marginalizedparent { left: -7em; }
li ul > li span.marginalizedparent { left: -9em; }
li ul > li ul > li span.marginalizedparent { left: -11em; }
li ul > li ul > li ul > li span.marginalizedparent { left: -13em; }
div.footnoteNumberParent {
position: relative;
left: -4.7em;
}
a.marginalized {
position: absolute;
font-size: 75%;
text-align: right;
width: 5em;
}
a.enumerated_item_num {
position: relative;
left: -3.5em;
display: inline-block;
margin-right: -3em;
text-align: right;
width: 3em;
}
div.para { margin-bottom: 0.6em; margin-top: 0.6em; text-align: justify; }
div.section { text-align: justify; }
div.sentence { display: inline; }
span.indexparent {
display: inline;
position: relative;
float: right;
right: -1em;
}
a.index {
position: absolute;
display: none;
}
a.index:before { content: "⟵"; }

a.index:target {
display: inline;
}
.indexitems {
margin-left: 2em;
text-indent: -2em;
}
div.itemdescr {
margin-left: 3em;
}
.bnf {
font-family: serif;
margin-left: 40pt;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.ncbnf {
font-family: serif;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: 40pt;
}
.ncsimplebnf {
font-family: serif;
font-style: italic;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: 40pt;
background: inherit; 
}
span.textnormal {
font-style: normal;
font-family: serif;
white-space: normal;
display: inline-block;
}
span.rlap {
display: inline-block;
width: 0px;
}
span.descr { font-style: normal; font-family: serif; }
span.grammarterm { font-style: italic; }
span.term { font-style: italic; }
span.terminal { font-family: monospace; font-style: normal; }
span.nonterminal { font-style: italic; }
span.tcode { font-family: monospace; font-style: normal; }
span.textbf { font-weight: bold; }
span.textsc { font-variant: small-caps; }
a.nontermdef { font-style: italic; font-family: serif; }
span.emph { font-style: italic; }
span.techterm { font-style: italic; }
span.mathit { font-style: italic; }
span.mathsf { font-family: sans-serif; }
span.mathrm { font-family: serif; font-style: normal; }
span.textrm { font-family: serif; }
span.textsl { font-style: italic; }
span.mathtt { font-family: monospace; font-style: normal; }
span.mbox { font-family: serif; font-style: normal; }
span.ungap { display: inline-block; width: 2pt; }
span.textit { font-style: italic; }
span.texttt { font-family: monospace; }
span.tcode_in_codeblock { font-family: monospace; font-style: normal; }
span.phantom { color: white; }

span.math { font-style: normal; }
span.mathblock {
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 1.2em;
margin-bottom: 1.2em;
text-align: center;
}
span.mathalpha {
font-style: italic;
}
span.synopsis {
font-weight: bold;
margin-top: 0.5em;
display: block;
}
span.definition {
font-weight: bold;
display: block;
}
.codeblock {
margin-left: 1.2em;
line-height: 127%;
}
.outputblock {
margin-left: 1.2em;
line-height: 127%;
}
div.itemdecl {
margin-top: 2ex;
}
code.itemdeclcode {
white-space: pre;
display: block;
}
span.textsuperscript {
vertical-align: super;
font-size: smaller;
line-height: 0;
}
.footnotenum { vertical-align: super; font-size: smaller; line-height: 0; }
.footnote {
font-size: small;
margin-left: 2em;
margin-right: 2em;
margin-top: 0.6em;
margin-bottom: 0.6em;
}
div.minipage {
display: inline-block;
margin-right: 3em;
}
div.numberedTable {
text-align: center;
margin: 2em;
}
div.figure {
text-align: center;
margin: 2em;
}
table {
border: 1px solid black;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
margin-top: 0.8em;
text-align: left;
hyphens: none; 
}
td, th {
padding-left: 1em;
padding-right: 1em;
vertical-align: top;
}
td.empty {
padding: 0px;
padding-left: 1px;
}
td.left {
text-align: left;
}
td.right {
text-align: right;
}
td.center {
text-align: center;
}
td.justify {
text-align: justify;
}
td.border {
border-left: 1px solid black;
}
tr.rowsep, td.cline {
border-top: 1px solid black;
}
tr.even, tr.odd {
border-bottom: 1px solid black;
}
tr.capsep {
border-top: 3px solid black;
border-top-style: double;
}
tr.header {
border-bottom: 3px solid black;
border-bottom-style: double;
}
th {
border-bottom: 1px solid black;
}
span.centry {
font-weight: bold;
}
div.table {
display: block;
margin-left: auto;
margin-right: auto;
text-align: center;
width: 90%;
}
span.indented {
display: block;
margin-left: 2em;
margin-bottom: 1em;
margin-top: 1em;
}
ol.enumeratea { list-style-type: none; background: inherit; }
ol.enumerate { list-style-type: none; background: inherit; }

code.sourceCode > span { display: inline; }
</style>
  <link href="data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AIJEAACCRAAAgkQAAIJEAACCRAAAgkQAVoJEAN6CRADegkQAWIJEAACCRAAAgkQAAIJEAACCRAAA////AP///wCCRAAAgkQAAIJEAACCRAAsgkQAvoJEAP+CRAD/gkQA/4JEAP+CRADAgkQALoJEAACCRAAAgkQAAP///wD///8AgkQAAIJEABSCRACSgkQA/IJEAP99PQD/dzMA/3czAP99PQD/gkQA/4JEAPyCRACUgkQAFIJEAAD///8A////AHw+AFiBQwDqgkQA/4BBAP9/PxP/uZd6/9rJtf/bybX/upd7/39AFP+AQQD/gkQA/4FDAOqAQgBc////AP///wDKklv4jlEa/3o7AP+PWC//8+3o///////////////////////z7un/kFox/35AAP+GRwD/mVYA+v///wD///8A0Zpk+NmibP+0d0T/8evj///////+/fv/1sKz/9bCs//9/fr//////+/m2/+NRwL/nloA/5xYAPj///8A////ANKaZPjRmGH/5cKh////////////k149/3UwAP91MQD/lmQ//86rhv+USg3/m1YA/5hSAP+bVgD4////AP///wDSmmT4zpJY/+/bx///////8+TV/8mLT/+TVx//gkIA/5lVAP+VTAD/x6B//7aEVv/JpH7/s39J+P///wD///8A0ppk+M6SWP/u2sf///////Pj1f/Nj1T/2KFs/8mOUv+eWhD/lEsA/8aee/+0glT/x6F7/7J8Rvj///8A////ANKaZPjRmGH/48Cf///////+/v7/2qt//82PVP/OkFX/37KJ/86siv+USg7/mVQA/5hRAP+bVgD4////AP///wDSmmT40ppk/9CVXP/69O////////7+/v/x4M//8d/P//7+/f//////9u7n/6tnJf+XUgD/nFgA+P///wD///8A0ppk+NKaZP/RmWL/1qNy//r07///////////////////////+vXw/9akdP/Wnmn/y5FY/6JfFvj///8A////ANKaZFTSmmTo0ppk/9GYYv/Ql1//5cWm//Hg0P/x4ND/5cWm/9GXYP/RmGH/0ppk/9KaZOjVnmpY////AP///wDSmmQA0ppkEtKaZI7SmmT60ppk/9CWX//OkVb/zpFW/9CWX//SmmT/0ppk/NKaZJDSmmQS0ppkAP///wD///8A0ppkANKaZADSmmQA0ppkKtKaZLrSmmT/0ppk/9KaZP/SmmT/0ppkvNKaZCrSmmQA0ppkANKaZAD///8A////ANKaZADSmmQA0ppkANKaZADSmmQA0ppkUtKaZNzSmmTc0ppkVNKaZADSmmQA0ppkANKaZADSmmQA////AP5/AAD4HwAA4AcAAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAMADAADgBwAA+B8AAP5/AAAoAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP///wCCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAAyCRACMgkQA6oJEAOqCRACQgkQAEIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAA////AP///wD///8A////AIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRABigkQA5oJEAP+CRAD/gkQA/4JEAP+CRADqgkQAZoJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAAD///8A////AP///wD///8AgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAA4gkQAwoJEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQAxIJEADyCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAAgkQAAP///wD///8A////AP///wCCRAAAgkQAAIJEAACCRAAAgkQAAIJEAACCRAAWgkQAmIJEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAJyCRAAYgkQAAIJEAACCRAAAgkQAAIJEAACCRAAA////AP///wD///8A////AIJEAACCRAAAgkQAAIJEAACCRAAAgkQAdIJEAPCCRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAP+CRAD/gkQA/4JEAPSCRAB4gkQAAIJEAACCRAAAgkQAAIJEAAD///8A////AP///wD///8AgkQAAIJEAACCRAAAgkQASoJEANKCRAD/gkQA/4JEAP+CRAD/g0YA/39AAP9zLgD/bSQA/2shAP9rIQD/bSQA/3MuAP9/PwD/g0YA/4JEAP+CRAD/gkQA/4JEAP+CRADUgkQAToJEAACCRAAAgkQAAP///wD///8A////AP///wB+PwAAgkUAIoJEAKiCRAD/gkQA/4JEAP+CRAD/hEcA/4BBAP9sIwD/dTAA/5RfKv+viF7/vp56/76ee/+wiF7/lWAr/3YxAP9sIwD/f0AA/4RHAP+CRAD/gkQA/4JEAP+CRAD/gkQArIJEACaBQwAA////AP///wD///8A////AIBCAEBzNAD6f0EA/4NFAP+CRAD/gkQA/4VIAP92MwD/bSUA/6N1Tv/ezsL/////////////////////////////////38/D/6V3Uv9uJgD/dTEA/4VJAP+CRAD/gkQA/4JEAP+BQwD/fUAA/4FDAEj///8A////AP///wD///8AzJRd5qBlKf91NgD/dDUA/4JEAP+FSQD/cy4A/3YyAP/PuKP//////////////////////////////////////////////////////9K7qP94NQD/ciwA/4VJAP+CRAD/fkEA/35BAP+LSwD/mlYA6v///wD///8A////AP///wDdpnL/4qx3/8KJUv+PUhf/cTMA/3AsAP90LgD/4dK+/////////////////////////////////////////////////////////////////+TYxf91MAD/dTIA/31CAP+GRwD/llQA/6FcAP+gWwD8////AP///wD///8A////ANGZY/LSm2X/4ap3/92mcP+wdT3/byQA/8mwj////////////////////////////////////////////////////////////////////////////+LYxv9zLgP/jUoA/59bAP+hXAD/nFgA/5xYAPL///8A////AP///wD///8A0ppk8tKaZP/RmWL/1p9q/9ubXv/XqXj////////////////////////////7+fD/vZyG/6BxS/+gcUr/vJuE//r37f//////////////////////3MOr/5dQBf+dVQD/nVkA/5xYAP+cWAD/nFgA8v///wD///8A////AP///wDSmmTy0ppk/9KaZP/SmWP/yohJ//jo2P//////////////////////4NTG/4JDFf9lGAD/bSQA/20kAP9kGAD/fz8S/+Xb0f//////5NG9/6txN/+LOgD/m1QA/51aAP+cWAD/m1cA/5xYAP+cWADy////AP///wD///8A////ANKaZPLSmmT/0ppk/8+TWf/Unmv//v37//////////////////////+TWRr/VwsA/35AAP+ERgD/g0UA/4JGAP9lHgD/kFga/8KXX/+TRwD/jT4A/49CAP+VTQD/n10A/5xYAP+OQQD/lk4A/55cAPL///8A////AP///wD///8A0ppk8tKaZP/SmmT/y4tO/92yiP//////////////////////8NnE/8eCQP+rcTT/ez0A/3IyAP98PgD/gEMA/5FSAP+USwD/jj8A/5lUAP+JNwD/yqV2/694Mf+HNQD/jkAA/82rf/+laBj/jT4A8v///wD///8A////AP///wDSmmTy0ppk/9KaZP/LiUr/4byY///////////////////////gupX/0I5P/+Wuev/Lklz/l1sj/308AP+QSwD/ol0A/59aAP+aVQD/k0oA/8yoh///////+fXv/6pwO//Lp3v///////Pr4f+oay7y////AP///wD///8A////ANKaZPLSmmT/0ppk/8uJSv/hvJj//////////////////////+G7l//Jhkb/0ppk/96nc//fqXX/x4xO/6dkFP+QSQD/llEA/5xXAP+USgD/yaOA///////38uv/qG05/8ijdv//////8efb/6ZpLPL///8A////AP///wD///8A0ppk8tKaZP/SmmT/zIxO/9yxh///////////////////////7dbA/8iEQf/Sm2X/0Zlj/9ScZv/eqHf/2KJv/7yAQf+XTgD/iToA/5lSAP+JNgD/yKFv/611LP+HNQD/jT8A/8qmeP+kZRT/jT4A8v///wD///8A////AP///wDSmmTy0ppk/9KaZP/Pk1n/1J5q//78+//////////////////+/fv/1aFv/8iEQv/Tm2b/0ppl/9GZY//Wn2z/1pZc/9eldf/Bl2b/kUcA/4w9AP+OQAD/lUwA/59eAP+cWQD/jT8A/5ZOAP+eXADy////AP///wD///8A////ANKaZPLSmmT/0ppk/9KZY//KiEn/8d/P///////////////////////47+f/05tm/8iCP//KiEj/yohJ/8eCP//RmGH//vfy///////n1sP/rXQ7/4k4AP+TTAD/nVoA/5xYAP+cVwD/nFgA/5xYAPL///8A////AP///wD///8A0ppk8tKaZP/SmmT/0ptl/8uLTf/aq37////////////////////////////+/fz/6c2y/961jv/etY7/6Myx//78+v//////////////////////3MWv/5xXD/+ORAD/mFQA/51ZAP+cWAD/nFgA8v///wD///8A////AP///wDSmmTy0ppk/9KaZP/SmmT/0ppk/8mFRP/s1b//////////////////////////////////////////////////////////////////////////////+PD/0JFU/7NzMv+WUQD/kUsA/5tXAP+dWQDy////AP///wD///8A////ANKaZP/SmmT/0ppk/9KaZP/Sm2X/z5NZ/8yMT//z5NX/////////////////////////////////////////////////////////////////9Ofa/8yNUP/UmGH/36p5/8yTWv+qaSD/kksA/5ROAPz///8A////AP///wD///8A0ppk5NKaZP/SmmT/0ppk/9KaZP/TnGf/zY9T/82OUv/t1sD//////////////////////////////////////////////////////+7Yw//OkFX/zI5R/9OcZ//SmmP/26V0/9ymdf/BhUf/ol8R6P///wD///8A////AP///wDSmmQ80ppk9tKaZP/SmmT/0ppk/9KaZP/TnGj/zpFW/8qJSv/dson/8uHS//////////////////////////////////Lj0//etIv/y4lL/86QVf/TnGj/0ppk/9KaZP/RmWP/05xn/9ymdfjUnWdC////AP///wD///8A////ANKaZADSmmQc0ppkotKaZP/SmmT/0ppk/9KaZP/Tm2b/0Zli/8qJSf/NjlH/16Z3/+G8mP/myKr/5siq/+G8mP/Xp3f/zY5S/8qISf/RmGH/05tm/9KaZP/SmmT/0ppk/9KaZP/SmmSm0pljINWdaQD///8A////AP///wD///8A0ppkANKaZADSmmQA0ppkQtKaZMrSmmT/0ppk/9KaZP/SmmT/0ptl/9GYYf/Nj1P/y4lL/8qISP/KiEj/y4lK/82PU//RmGH/0ptl/9KaZP/SmmT/0ppk/9KaZP/SmmTO0ppkRtKaZADSmmQA0ppkAP///wD///8A////AP///wDSmmQA0ppkANKaZADSmmQA0ppkANKaZGzSmmTu0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmTw0ppkcNKaZADSmmQA0ppkANKaZADSmmQA////AP///wD///8A////ANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZBLSmmSQ0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppklNKaZBTSmmQA0ppkANKaZADSmmQA0ppkANKaZAD///8A////AP///wD///8A0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQy0ppkutKaZP/SmmT/0ppk/9KaZP/SmmT/0ppk/9KaZP/SmmT/0ppkvtKaZDbSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkAP///wD///8A////AP///wDSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkXNKaZODSmmT/0ppk/9KaZP/SmmT/0ppk5NKaZGDSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA////AP///wD///8A////ANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkBtKaZIbSmmTo0ppk6tKaZIrSmmQK0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZADSmmQA0ppkANKaZAD///8A////AP/8P///+B///+AH//+AAf//AAD//AAAP/AAAA/gAAAHwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA+AAAAfwAAAP/AAAP/8AAP//gAH//+AH///4H////D//" rel="icon" />
  
  <!--[if lt IE 9]>
    <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
  <![endif]-->
</head>
<body>
<div class="wrapper">
<header id="title-block-header">
<h1 class="title" style="text-align:center">Coroutine Task Issues</h1>
<table style="border:none;float:right">
  <tr>
    <td>Document #:</td>
    <td>
      P3796R0
      [<a href="https://wg21.link/P3796">Latest</a>]
      [<a href="https://wg21.link/P3796/status">Status</a>]
    </td>
  </tr>
  <tr>
    <td>Date:</td>
    <td>2025-07-15</td>
  </tr>
  <tr>
    <td style="vertical-align:top">Project:</td>
    <td>Programming Language C++</td>
  </tr>
  <tr>
    <td style="vertical-align:top">Audience:</td>
    <td>
      Concurrency Working Group (SG1)<br>
      Library Evolution Working Group (LEWG)<br>
      Library Working Group (LWG)<br>
    </td>
  </tr>
  <tr>
    <td style="vertical-align:top">Reply-to:</td>
    <td>
      Dietmar Kühl (Bloomberg)<br>&lt;<a href="mailto:dkuhl@bloomberg.net" class="email">dkuhl@bloomberg.net</a>&gt;<br>
    </td>
  </tr>
</table>
</header>
<div style="clear:both">
<div id="TOC" role="doc-toc">
<h1 id="toctitle">Contents</h1>
<ul>
<li><a href="#change-history" id="toc-change-history"><span class="toc-section-number">1</span> Change History<span></span></a>
<ul>
<li><a href="#r0-initial-revision" id="toc-r0-initial-revision"><span class="toc-section-number">1.1</span> R0 Initial
Revision<span></span></a></li>
</ul></li>
<li><a href="#general" id="toc-general"><span class="toc-section-number">2</span> General<span></span></a></li>
<li><a href="#the-concerns" id="toc-the-concerns"><span class="toc-section-number">3</span> The Concerns<span></span></a>
<ul>
<li><a href="#affine_on-concerns" id="toc-affine_on-concerns"><span class="toc-section-number">3.1</span>
<code class="sourceCode cpp">affine_on</code> Concerns<span></span></a>
<ul>
<li><a href="#affine_on-default-implementation-lacks-a-specification" id="toc-affine_on-default-implementation-lacks-a-specification"><span class="toc-section-number">3.1.1</span>
<code class="sourceCode cpp">affine_on</code> Default Implementation
Lacks a Specification<span></span></a></li>
<li><a href="#affine_on-semantics-are-not-clear" id="toc-affine_on-semantics-are-not-clear"><span class="toc-section-number">3.1.2</span>
<code class="sourceCode cpp">affine_on</code> Semantics Are Not
Clear<span></span></a></li>
<li><a href="#affine_ons-shape-may-not-be-correct" id="toc-affine_ons-shape-may-not-be-correct"><span class="toc-section-number">3.1.3</span>
<code class="sourceCode cpp">affine_on</code>’s Shape May Not Be
Correct<span></span></a></li>
<li><a href="#affine_on-shouldnt-forward-stop-requests-to-scheduling-operations" id="toc-affine_on-shouldnt-forward-stop-requests-to-scheduling-operations"><span class="toc-section-number">3.1.4</span>
<code class="sourceCode cpp">affine_on</code> Shouldn’t Forward Stop
Requests To Scheduling Operations<span></span></a></li>
<li><a href="#affine_on-customisation-for-other-senders" id="toc-affine_on-customisation-for-other-senders"><span class="toc-section-number">3.1.5</span>
<code class="sourceCode cpp">affine_on</code> Customisation For Other
Senders<span></span></a></li>
</ul></li>
<li><a href="#task-operation" id="toc-task-operation"><span class="toc-section-number">3.2</span> Task Operation<span></span></a>
<ul>
<li><a href="#starting-a-task-should-not-unconditionally-reschedule" id="toc-starting-a-task-should-not-unconditionally-reschedule"><span class="toc-section-number">3.2.1</span> Starting A
<code class="sourceCode cpp">task</code> Should Not Unconditionally
Reschedule<span></span></a></li>
<li><a href="#resuming-after-a-task-should-not-reschedule" id="toc-resuming-after-a-task-should-not-reschedule"><span class="toc-section-number">3.2.2</span> Resuming After A
<code class="sourceCode cpp">task</code> Should Not
Reschedule<span></span></a></li>
<li><a href="#no-support-for-symmetric-transfer" id="toc-no-support-for-symmetric-transfer"><span class="toc-section-number">3.2.3</span> No Support For Symmetric
Transfer<span></span></a></li>
</ul></li>
<li><a href="#allocation" id="toc-allocation"><span class="toc-section-number">3.3</span> Allocation<span></span></a>
<ul>
<li><a href="#unusual-allocator-customisation" id="toc-unusual-allocator-customisation"><span class="toc-section-number">3.3.1</span> Unusual Allocator
Customisation<span></span></a></li>
<li><a href="#issue-flexible-allocator-position" id="toc-issue-flexible-allocator-position"><span class="toc-section-number">3.3.2</span> Issue: Flexible Allocator
Position<span></span></a></li>
<li><a href="#shadowing-the-environment-allocator-is-questionable" id="toc-shadowing-the-environment-allocator-is-questionable"><span class="toc-section-number">3.3.3</span> Shadowing The Environment
Allocator Is Questionable<span></span></a></li>
</ul></li>
<li><a href="#stop-token-management" id="toc-stop-token-management"><span class="toc-section-number">3.4</span> Stop Token
Management<span></span></a>
<ul>
<li><a href="#a-stop-source-always-needs-to-be-created" id="toc-a-stop-source-always-needs-to-be-created"><span class="toc-section-number">3.4.1</span> A Stop Source Always Needs To Be
Created<span></span></a></li>
<li><a href="#the-wording-implies-the-stop-token-is-default-constructible" id="toc-the-wording-implies-the-stop-token-is-default-constructible"><span class="toc-section-number">3.4.2</span> The Wording Implies The Stop
Token Is Default Constructible<span></span></a></li>
</ul></li>
<li><a href="#miscellaneous-concerns" id="toc-miscellaneous-concerns"><span class="toc-section-number">3.5</span> Miscellaneous
Concerns<span></span></a>
<ul>
<li><a href="#task-is-not-actually-lazily-started" id="toc-task-is-not-actually-lazily-started"><span class="toc-section-number">3.5.1</span> Task Is Not Actually Lazily
Started<span></span></a></li>
<li><a href="#the-coroutine-frame-is-destroyed-too-late" id="toc-the-coroutine-frame-is-destroyed-too-late"><span class="toc-section-number">3.5.2</span> The Coroutine Frame Is Destroyed
Too Late<span></span></a></li>
<li><a href="#taskt-e-has-no-default-arguments" id="toc-taskt-e-has-no-default-arguments"><span class="toc-section-number">3.5.3</span> <code class="sourceCode cpp">task<span class="op">&lt;</span>T, E<span class="op">&gt;</span></code>
Has No Default Arguments<span></span></a></li>
<li><a href="#unhandled_stopped-isnt-noexcept" id="toc-unhandled_stopped-isnt-noexcept"><span class="toc-section-number">3.5.4</span> <code class="sourceCode cpp">unhandled_stopped<span class="op">()</span></code>
Isn’t
<code class="sourceCode cpp"><span class="kw">noexcept</span></code><span></span></a></li>
<li><a href="#the-environment-design-may-be-inefficient" id="toc-the-environment-design-may-be-inefficient"><span class="toc-section-number">3.5.5</span> The Environment Design May Be
Inefficient<span></span></a></li>
<li><a href="#no-completion-scheduler" id="toc-no-completion-scheduler"><span class="toc-section-number">3.5.6</span> No Completion
Scheduler<span></span></a></li>
<li><a href="#awaitable-non-senders-are-not-supported" id="toc-awaitable-non-senders-are-not-supported"><span class="toc-section-number">3.5.7</span> Awaitable
non-<code class="sourceCode cpp">sender</code>s Are Not
Supported<span></span></a></li>
<li><a href="#should-taskpromise_type-use-with_awaitable_senders" id="toc-should-taskpromise_type-use-with_awaitable_senders"><span class="toc-section-number">3.5.8</span> Should <code class="sourceCode cpp">task<span class="op">::</span>promise_type</code>
Use
<code class="sourceCode cpp">with_awaitable_senders</code>?<span></span></a></li>
<li><a href="#a-future-coroutine-feature-could-avoid-co_yield-for-errors" id="toc-a-future-coroutine-feature-could-avoid-co_yield-for-errors"><span class="toc-section-number">3.5.9</span> A Future Coroutine Feature Could
Avoid
<code class="sourceCode cpp"><span class="kw">co_yield</span></code> For
Errors<span></span></a></li>
<li><a href="#there-is-no-hook-to-capturerestore-tls" id="toc-there-is-no-hook-to-capturerestore-tls"><span class="toc-section-number">3.5.10</span> There Is No Hook To
Capture/Restore TLS<span></span></a></li>
</ul></li>
</ul></li>
<li><a href="#conclusion" id="toc-conclusion"><span class="toc-section-number">4</span> Conclusion<span></span></a></li>
<li><a href="#potential-polls" id="toc-potential-polls"><span class="toc-section-number">5</span> Potential
Polls<span></span></a></li>
<li><a href="#acknowledgement" id="toc-acknowledgement"><span class="toc-section-number">6</span>
Acknowledgement<span></span></a></li>
</ul>
</div>
<p>After the <a href="https://wg21.link/p3552">task proposal</a> was
voted to be forwarded to plenary for inclusion into C++26 a number of
issues were brought up. The issues were reported using various means.
This paper describes the issues and proposes potential changes to
address them.</p>
<h1 data-number="1" id="change-history"><span class="header-section-number">1</span> Change History<a href="#change-history" class="self-link"></a></h1>
<h2 data-number="1.1" id="r0-initial-revision"><span class="header-section-number">1.1</span> R0 Initial Revision<a href="#r0-initial-revision" class="self-link"></a></h2>
<h1 data-number="2" id="general"><span class="header-section-number">2</span> General<a href="#general" class="self-link"></a></h1>
<p>After LWG voted to forward the <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html">task
proposal</a> to be forwarded to plenary for inclusion into C++26 some
issues were brought up. The concerns range from accidental omissions
(e.g., <code class="sourceCode cpp">unhandled_stopped</code> lacking
<code class="sourceCode cpp"><span class="kw">noexcept</span></code>)
via wording issues and performance concerns to some design questions. In
particular the behaviour of the algorithm
<code class="sourceCode cpp">affine_on</code> used to implement
scheduler affinity for <code class="sourceCode cpp">task</code> received
some questions which may lead to some design changes and/or
clarifications. This document discusses the various issues raised and
proposes ways to address some of them.</p>
<p>One statement from the plenary was that
<code class="sourceCode cpp">task</code> is the obvious name for a
coroutine task and we should get it right. There are certainly
improvements which can be applied. Although I’m not aware of anything
concrete beyond the raised issues there is probably potential for
improvements. If the primary concern is that there may be better task
interfaces in the future and a better task should get the name
<code class="sourceCode cpp">task</code>, it seems reasonable to rename
the component. The original name used for the component was
<code class="sourceCode cpp">lazy</code> and feedback from Hagenberg was
to rename it <code class="sourceCode cpp">task</code>. In principle, any
name could work, e.g., <code class="sourceCode cpp">affine_task</code>.
It seems reasonable to keep <code class="sourceCode cpp">task</code> in
the name somehow if <code class="sourceCode cpp">task</code> is to be
renamed.</p>
<p>Specifically for <code class="sourceCode cpp">task</code> it is worth
pointing out that multiple coroutine task components can coexist. Due to
the design of coroutines and sender/receiver different coroutine tasks
can interoperate.</p>
<h1 data-number="3" id="the-concerns"><span class="header-section-number">3</span> The Concerns<a href="#the-concerns" class="self-link"></a></h1>
<p>Some concerns were posted to the LWG chat on Mattermost (they were
originally raised on a Discord chat where multiple of the authors of
sender/receiver components coordinate the work). Other concerns were
raised separately (and the phrasing isn’t exactly that of the
originally, e.g., because it was stated in a different language). I
became aware of all but one of these concerns after the poll by LWG to
forward the proposal for inclusion into C++26. The rest of this section
discusses these concerns:</p>
<h2 data-number="3.1" id="affine_on-concerns"><span class="header-section-number">3.1</span>
<code class="sourceCode cpp">affine_on</code> Concerns<a href="#affine_on-concerns" class="self-link"></a></h2>
<p>This section covers different concerns around the specification, or
rather lack thereof, of <code class="sourceCode cpp">affine_on</code>.
The algorithm <code class="sourceCode cpp">affine_on</code> is at the
core of the scheduler affinity implementation: this algorithm is used to
establish the invariant that a <code class="sourceCode cpp">task</code>
executes on the currently installed scheduler. Originally, the
<code class="sourceCode cpp">task</code> proposal used
<code class="sourceCode cpp">continue_on</code> in the specification but
during the SG1 discussion it was suggested that a differently named
algorithm is used. The original idea was that <code class="sourceCode cpp">affine_on<span class="op">(</span><em>sndr</em>, <em>sch</em><span class="op">)</span></code>
behaves like <code class="sourceCode cpp">continues_on<span class="op">(</span><em>sndr</em>, <em>sch</em><span class="op">)</span></code>
but it is customised to avoid actual scheduling when it knows that
<code class="sourceCode cpp"><em>sndr</em></code> completes on
<code class="sourceCode cpp"><em>sch</em></code>. When further exploring
the direction of using a different algorithm than
<code class="sourceCode cpp">continues_on</code> some additional
potential for changed semantics emerged (see below).</p>
<p>The name <code class="sourceCode cpp">affine_on</code> was
<em>not</em> discussed by SG1. The direction was “come up with a name”.
The current name just concentrates on the primary objective of
implementing scheduler affinity for
<code class="sourceCode cpp">task</code>. It can certainly use a
different name. For example the algorithm could be called
<code class="sourceCode cpp">continues_inline_or_on</code> or
<code class="sourceCode cpp">affine_continues_on</code>.</p>
<p>To some extend <code class="sourceCode cpp">affine_on</code>’s
specification is deliberately vague because currently the
<code class="sourceCode cpp">execution</code> specification is lacking
some tools which would allow omission of scheduling, e.g., for
<code class="sourceCode cpp">just</code> which completes immediately
with a result. While users can’t necessarily tap into the
customisations, yet, implementation could use tools like those proposed
by <a href="https://wg21.link/p3206">P3206</a> “A sender query for
completion behaviour” using a suitable hidden name while the proposal
isn’t adopted.</p>
<h3 data-number="3.1.1" id="affine_on-default-implementation-lacks-a-specification"><span class="header-section-number">3.1.1</span>
<code class="sourceCode cpp">affine_on</code> Default Implementation
Lacks a Specification<a href="#affine_on-default-implementation-lacks-a-specification" class="self-link"></a></h3>
<p>The wording of <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#executionaffine_on-exec.affine.on"><code class="sourceCode cpp">affine_on</code></a>
doesn’t have a specification for the default implementation. For other
algorithms the default implementation is specified. To resolve this
concern add a new paragraph in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#executionaffine_on-exec.affine.on"><code class="sourceCode cpp">affine_on</code></a>
to provide a specification for the default implementation:</p>
<div class="add" style="color: #006e28">

<blockquote>
<p><span class="marginalizedparent"><a class="marginalized">5</a></span>
Let <code class="sourceCode default">sndr</code> and
<code class="sourceCode default">env</code> be subexpressions such that
<code class="sourceCode default">Sndr</code> is
<code class="sourceCode default">decltype((sndr))</code>. If <code class="sourceCode default"><em>sender-for</em>&lt;Sndr, affine_on_t&gt;</code>
is <code class="sourceCode default">false</code>, then the expression
<code class="sourceCode default">affine_on.transform_sender(sndr, env)</code>
is ill-formed; otherwise, it is equivalent to:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode default default"><code class="sourceCode default"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>auto [_, sch, child] = sndr;</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>return transform_sender(</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <em>query-with-default</em>(get_domain, sch, default_domain()),</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  continues_on(std::move(child), std::move(sch)));</span></code></pre></div>
<p>except that <code class="sourceCode default">sch</code> is only
evaluated once.</p>
</blockquote>

</div>
<p>The intention for <code class="sourceCode cpp">affine_on</code> was
to all optimisation/customisation in a way reducing the necessary
scheduling: if the implementation can determine if a sender completed
already on the correct execution agent it should be allowed to avoid
scheduling. That may be achieved by using <code class="sourceCode cpp">get_completion_scheduler<span class="op">&lt;</span>set_value_t<span class="op">&gt;</span></code>
of the sender, using (for now implementation specific) queries like
those proposed by <a href="https://wg21.link/P3206">P3206 “A sender
query for completion behaviour”</a>, or some other means. Unfortunately,
the specification proposed above seems to disallow implementation
specific techniques to avoid scheduling. Future revisions of the
standard could require some of the techniques to avoid scheduling
assuming the necessary infrastructure gets standardised.</p>
<h3 data-number="3.1.2" id="affine_on-semantics-are-not-clear"><span class="header-section-number">3.1.2</span>
<code class="sourceCode cpp">affine_on</code> Semantics Are Not Clear<a href="#affine_on-semantics-are-not-clear" class="self-link"></a></h3>
<p>The wording in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#executionaffine_on-exec.affine.on"><code class="sourceCode cpp">affine_on</code></a>
p5 says:</p>
<blockquote>
<p>… Calling <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
will start <code class="sourceCode cpp">sndr</code> on the current
execution agent and execution completion operations on
<code class="sourceCode cpp">out_rcvr</code> on an execution agent of
the execution resource associated with
<code class="sourceCode cpp">sch</code>. If the current execution
resource is the same as the execution resource associated with
<code class="sourceCode cpp">sch</code>, the completion operation on
<code class="sourceCode cpp">out_rcvr</code> may be invoked on the same
thread of execution before <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
completes. If scheduling onto <code class="sourceCode cpp">sch</code>
fails, an error completion on
<code class="sourceCode cpp">out_rcvr</code> shall be executed on an
unspecified execution agent.</p>
</blockquote>
<p>The sentence If the current execution resource is the same as the
execution resource associated with
<code class="sourceCode cpp">sch</code> is not clear to which execution
resource is the “current execution resource”. It could be the “current
execution agent” that was used to call <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>,
or it could be the execution agent that the predecessor completes
on.</p>
<p>The intended meaning for “current execution resource” was to refer to
the execution resource of the execution agent that was used to call
<code class="sourceCode cpp">stop<span class="op">(</span>op<span class="op">)</span></code>.
The specification could be clarified by changing the wording to
become:</p>
<blockquote>
<p>… Calling <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
will start <code class="sourceCode cpp">sndr</code> on the current
execution agent and execution completion operations on
<code class="sourceCode cpp">out_rcvr</code> on an execution agent of
the execution resource associated with
<code class="sourceCode cpp">sch</code>. If the <span class="rm" style="color: #bf0303"><del>current</del></span> execution resource
<span class="add" style="color: #006e28"><ins>of the execution agent
that was used to invoke
<span><code class="sourceCode default">start(op)</code></span></ins></span>
is the same as the execution resource associated with
<code class="sourceCode cpp">sch</code>, the completion operation on
<code class="sourceCode cpp">out_rcvr</code> may be called before <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
completes. If scheduling onto <code class="sourceCode cpp">sch</code>
fails, an error completion on
<code class="sourceCode cpp">out_rcvr</code> shall be executed on an
unspecified execution agent.</p>
</blockquote>
<p>It is also not clear what the actual difference in semantics between
<code class="sourceCode cpp">continues_on</code> and
<code class="sourceCode cpp">affine_on</code> is. The
<code class="sourceCode cpp">continues_on</code> semantics already
requires that the resulting sender completes on the specified schedulers
execution agent. It does not specify that it must evaluate a
<code class="sourceCode cpp">schedule<span class="op">()</span></code>
(although that is what the default Implementation does), and so in
theory it already permits an implementation/customisation to skip the
schedule (e.g. if the child senders completion scheduler was equal to
the target scheduler).</p>
<p>The key semantic that we want here is to specify one of two possible
semantics that differ from
<code class="sourceCode cpp">continues_on</code>:</p>
<ol type="1">
<li>That the completion of an
<code class="sourceCode cpp">affine_on</code> sender will occur on the
same scheduler that the operation started on. This is a slightly
stronger requirement than that of
<code class="sourceCode cpp">continues_on</code>, in that it puts a
requirement on the caller of
<code class="sourceCode cpp">affine_on</code> to ensure that the
operation is started on the scheduler passed to
<code class="sourceCode cpp">affine_on</code>, but then also grants
permission for the operation to complete inline if it completes
synchronously.</li>
<li>That the completion of an
<code class="sourceCode cpp">affine_on</code> sender will either
complete inline on the execution agent that it was started on, or it
will complete asynchronously on an execution agent associated with the
provided scheduler. This is a slightly more permissive than option 1. in
that it permits the caller to start on any context, but also is no
longer able to definitively advertise a completion context, since it
might now complete on one of two possible contexts (even if in many
cases those two contexts might be the same). This weaker semantic can be
used in conjunction with knowledge by the caller that they will start
the operation on a context associated with the same scheduler passed to
<code class="sourceCode cpp">affine_on</code> to ensure that the
operation will complete on the given scheduler.</li>
</ol>
<p>The description in the paper at the Hagenberg meeting assumed that
<code class="sourceCode cpp">task</code> uses
<code class="sourceCode cpp">continues_on</code> directly to achieve
scheduler affinity. During the SG1 discussion it was requested that the
approach to scheduler affinity doesn’t use
<code class="sourceCode cpp">continues_on</code> directly but rather
uses a different algorithm which can be customised separately. This is
the algorithm now named <code class="sourceCode cpp">affine_on</code>.
The intention was that <code class="sourceCode cpp">affine_on</code> can
avoid scheduling in more cases than
<code class="sourceCode cpp">continues_on</code>.</p>
<p>It is worth noting that an implementation can’t determine the
execution resource of the execution agent which invoked <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
directly. If <code class="sourceCode cpp">rcvr</code> is the receiver
used to create <code class="sourceCode cpp">op</code> it can be queried
for <code class="sourceCode cpp">get_scheduler<span class="op">(</span>get_env<span class="op">(</span>rcvr<span class="op">))</span></code>
which is intended to yield this execution resource. However, there is no
guarantee that this is the case [yet?].</p>
<p>The intended direction here is to pursue the second possibility
mentioned above. Compared to
<code class="sourceCode cpp">continues_on</code> that would effectively
relax the requirement that <code class="sourceCode cpp">affine_on</code>
completes on <code class="sourceCode cpp">sch</code> if
<code class="sourceCode cpp">sender</code> doesn’t actually change
schedulers and completes inline: if <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
gets invoked on an execution agents which matches
<code class="sourceCode cpp">sch</code>’s execution resources the
guarantee holds but <code class="sourceCode cpp">affine_on</code> would
be allowed to complete on the execution agent <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
was invoked. It is upon the user to invoke <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
on the correct execution agent.</p>
<p>Another difference to
<code class="sourceCode cpp">continues_on</code> is that
<code class="sourceCode cpp">affine_on</code> can be separately
customised.</p>
<h3 data-number="3.1.3" id="affine_ons-shape-may-not-be-correct"><span class="header-section-number">3.1.3</span>
<code class="sourceCode cpp">affine_on</code>’s Shape May Not Be
Correct<a href="#affine_ons-shape-may-not-be-correct" class="self-link"></a></h3>
<p>The <code class="sourceCode cpp">affine_on</code> algorithm defined
in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#executionaffine_on-exec.affine.on"><code class="sourceCode cpp">affine_on</code></a>
takes two arguments; a <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
and a <a href="https://eel.is/c++draft/exec.sched"><code class="sourceCode cpp">scheduler</code></a>.</p>
<p>As mentioned above, the semantic that we really want for the purpose
in coroutines is that the operation completes on the same execution
context that it started on. This way, we can ensure, by induction, that
the coroutine which starts on the right context, and resumes on the same
context after each suspend-point, will itself complete on the same
context.</p>
<p>This then also begs the question: Could we just take the scheduler
that the operation will be started on from the <a href="https://eel.is/c++draft/exec.get.scheduler"><code class="sourceCode cpp">get_scheduler</code></a>
query on the receivers environment and avoid having to explicitly pass
the scheduler as an argument?</p>
<p>To this end, we should consider potentially simplifying the
<code class="sourceCode cpp">affine_on</code> algorithm to just take an
input sender and to pick up the scheduler that it will be started on
from the receivers environment and promise to complete on that
context.</p>
<p>For example, the <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html?validation_key=7c6057b778f7a41d2ba63fd94676bf5e#class-taskpromise_type-task.promise"><code class="sourceCode cpp">await_transform</code></a>
function could potentially be changed to return: <code class="sourceCode cpp">as_awaitable<span class="op">(</span>affine_on<span class="op">(</span>std<span class="op">::</span>forward<span class="op">&lt;</span>Sndr<span class="op">&gt;(</span>sndr<span class="op">)))</span></code>.
Then we could define the <code class="sourceCode cpp">affine_on<span class="op">.</span>transform_sender<span class="op">(</span>sndr, env<span class="op">)</span></code>
expression (which provides the default implementation) to be equivalent
to <code class="sourceCode cpp">continues_on<span class="op">(</span>sndr, get_scheduler<span class="op">(</span>env<span class="op">))</span></code>.</p>
<p>Such an approach would also a require change to the semantic
requirements of <code class="sourceCode cpp">get_scheduler<span class="op">(</span>get_env<span class="op">(</span>rcvr<span class="op">))</span></code>
to require that <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
is called on that context.</p>
<p>This change isn’t strictly necessary, though. The interface of
<code class="sourceCode cpp">affine_on</code> as is can be used. Making
this change does improve the design. Nothing outside of
<code class="sourceCode cpp">task</code> is currently using
<code class="sourceCode cpp">affine_on</code> and as it is an algorithm
tailored for <code class="sourceCode cpp">task</code>’s needs it seems
reasonable to make it fit this use case exactly. This change would also
align with the direction that the execution agent used to invoke <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>
matches the execution resource of <code class="sourceCode cpp">get_scheduler<span class="op">(</span>get_env<span class="op">(</span>rcvr<span class="op">))</span></code>.</p>
<h3 data-number="3.1.4" id="affine_on-shouldnt-forward-stop-requests-to-scheduling-operations"><span class="header-section-number">3.1.4</span>
<code class="sourceCode cpp">affine_on</code> Shouldn’t Forward Stop
Requests To Scheduling Operations<a href="#affine_on-shouldnt-forward-stop-requests-to-scheduling-operations" class="self-link"></a></h3>
<p>The <code class="sourceCode cpp">affine_on</code> algorithm is used
by the <code class="sourceCode cpp">task</code> coroutine to ensure that
the coroutine always resumes back on its associated scheduler by
applying the <code class="sourceCode cpp">affine_on</code> algorithm to
each awaited value in a
<code class="sourceCode cpp"><span class="kw">co_await</span></code>
expression.</p>
<p>In cases where the awaited operation completes asynchronously,
resumption of the coroutine will be scheduled using the coroutines
associated scheduler via a <a href="https://eel.is/c++draft/exec.schedule"><code class="sourceCode cpp">schedule</code></a>
operation.</p>
<p>If that schedule operation completes with
<code class="sourceCode cpp">set_value</code> then the coroutine
successfully resumes on its associated execution context. However, if it
completes with <code class="sourceCode cpp">set_error</code> or
<code class="sourceCode cpp">set_stopped</code> then resuming the
coroutine on the execution context of the completion is not going to
preserve the invariant that the coroutine is always going to resume on
its associated context.</p>
<p>For some cases this may not be an issue, but for other cases,
resuming on the right execution context may be important for
correctness, even during exception unwind or due to cancellation. For
example, destructors may require running in a UI thread in order to
release UI resources. Or the associated scheduler may be a strand (which
runs all tasks scheduled to it sequentially) in order to synchronise
access to shared resources used by destructors.</p>
<p>Thus, if a stop-request has been sent to the coroutine, that
stop-request should be propagated to child operations so that the child
operation it is waiting on can be cancelled if necessary, but should
probably not be propagated to any schedule operation created by the
implicit <code class="sourceCode cpp">affine_on</code> algorithm as this
is needed to complete successfully in order to ensure the coroutine
resumes on its associated context.</p>
<p>One option to work around this with the status-quo would be to define
a scheduler adapter that adapted the underlying
<code class="sourceCode cpp">schedule<span class="op">()</span></code>
operation to prevent passing through stop-requests from the parent
environment (e.g. applying the <a href="https://wg21.link/p3284"><code class="sourceCode cpp">unstoppable</code></a>
adapter). If failing to reschedule onto the associated context was a
fatal error, you could also apply a
<code class="sourceCode cpp">terminate_on_error</code> adaptor as
well.</p>
<p>Then the user could apply this adapter to the scheduler before
passing it to the <code class="sourceCode cpp">task</code>.</p>
<p>For example:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>template&lt;std::execution::scheduler S&gt;</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>struct infallible_scheduler {</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  using scheduler_concept = std::execution::scheduler_t;</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  S scheduler;</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  auto schedule() {</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    return unstoppable(terminate_on_error(std::execution::schedule(scheduler)));</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  }</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  bool operator==(const infallible_scheduler&amp;) const noexcept = default;</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>};</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>template&lt;std::execution::scheduler S&gt;</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>infallible_scheduler(S) -&gt; infallible_scheduler&lt;S&gt;;</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>std::execution::task&lt;void, std::execution::env&lt;&gt;&gt; example() {</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>  co_await some_cancellable_op();</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>std::execution::task&lt;void, std::execution::env&lt;&gt;&gt; caller() {</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>  std::execution::scheduler auto sched = co_await std::execution::read_env(get_scheduler);</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>  co_await std::execution::on(infallible_scheduler{sched}, example());</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>However, this approach has the downside that this scheduler behaviour
now also applies to all other uses of the scheduler - not just the uses
required to ensure the coroutines invariant of always resuming on the
associated context.</p>
<p>Other ways this could be tackled include:</p>
<ul>
<li>Making it the default behaviour of
<code class="sourceCode cpp">affine_on</code> to suppress stop requests
for the scheduling operations. That would mean that
<code class="sourceCode cpp">affine_on</code> won’t delegate to <a href><code class="sourceCode cpp">continues_on</code></a>.</li>
<li>Somehow making the behaviour a policy decision specified via the
<code class="sourceCode cpp">Environment</code> template parameter of
the <code class="sourceCode cpp">task</code>.</li>
<li>Somehow using domain-based customisation to allow the coroutine to
customise the behaviour of
<code class="sourceCode cpp">affine_on</code>.</li>
<li>Making the <code class="sourceCode cpp">task<span class="op">::</span>promise_type<span class="op">::</span>await_transform</code>
apply this adapter to the scheduler passed to
<code class="sourceCode cpp">affine_on</code>. i.e. it calls <code class="sourceCode cpp">affine_on<span class="op">(</span>std<span class="op">::</span>forward<span class="op">&lt;</span>Sndr<span class="op">&gt;(</span>sndr<span class="op">)</span>, infallible_scheduler<span class="op">{</span>SCHED<span class="op">(*</span>sched<span class="op">)})</span></code>.
Taking this route would mean that the shape of
<code class="sourceCode cpp">affine_on</code> should not be
changed.</li>
</ul>
<h3 data-number="3.1.5" id="affine_on-customisation-for-other-senders"><span class="header-section-number">3.1.5</span>
<code class="sourceCode cpp">affine_on</code> Customisation For Other
Senders<a href="#affine_on-customisation-for-other-senders" class="self-link"></a></h3>
<p>Assuming the the <code class="sourceCode cpp">affine_on</code>
algorithm semantics are changed to just require that it completes either
inline or on the context of the receiver environments
<code class="sourceCode cpp">get_scheduler</code> query, then there are
probably some other algorithms that could either make use of this, or
provide customisations for it that short-circuit the need to schedule
unnecessarily.</p>
<p>For example:</p>
<ul>
<li><code class="sourceCode cpp">affine_on<span class="op">(</span>just<span class="op">(</span>args<span class="op">...))</span></code>
could be simplified to <code class="sourceCode cpp">just<span class="op">(</span>args<span class="op">...)</span></code></li>
<li><code class="sourceCode cpp">affine_on<span class="op">(</span>on<span class="op">(</span>sch, sndr<span class="op">))</span></code>
can be simplified to <code class="sourceCode cpp">on<span class="op">(</span>sch, sndr<span class="op">)</span></code>
as on already provides
<code class="sourceCode cpp">affine_on</code>-like semantics</li>
<li>The <code class="sourceCode cpp">counting_scope<span class="op">::</span>join</code>
sender currently already provides
<code class="sourceCode cpp">affine_on</code>-like semantics.
<ul>
<li>We could potentially simplify this sender to just complete inline
unless the join-sender is wrapped in
<code class="sourceCode cpp">affine_on</code>, in which case the
resulting <code class="sourceCode cpp">affine_on<span class="op">(</span>scope<span class="op">.</span>join<span class="op">())</span></code>
sender would have the semantics that <code class="sourceCode cpp">scope<span class="op">.</span>join<span class="op">()</span></code>
has today.</li>
<li>Alternatively, we could just customise <code class="sourceCode cpp">affine_on<span class="op">(</span>scope<span class="op">.</span>join<span class="op">())</span></code>
to be equivalent to <code class="sourceCode cpp">scope<span class="op">.</span>join<span class="op">()</span></code>.</li>
</ul></li>
<li>Other similar senders like those returned from <code class="sourceCode cpp">bounded_queue<span class="op">::</span>async_push</code>
and <code class="sourceCode cpp">bounded_queue<span class="op">::</span>async_pop</code>
which are defined to return a sender that will resume on the original
scheduler.</li>
</ul>
<p>The intended use of <code class="sourceCode cpp">affine_on</code> is
to avoid scheduling where the algorithm already resumes on a suitable
execution agent. However, as the proposal was late it didn’t require
potential optimisations. The intend was to leave the specification of
the default implementation vague enough to let implementations avoid
scheduling where they know it isn’t needed. Making these a requirement
is intended for future revisions of the standard.</p>
<p>It is also a bit unclear how algorithm customisation is actually
implemented in practice. Algorithms can advertise a domain via the
<code class="sourceCode cpp">get_domain</code> query which can then be
used to transform algorithms: <code class="sourceCode cpp">transform_sender<span class="op">(</span>dom, sender, env<span class="op">...)</span></code>
<a href="https://eel.is/c++draft/exec#snd.transform">[exec.snd.transform]</a>
uses <code class="sourceCode cpp">dom<span class="op">.</span>transform_sender<span class="op">(</span>sender, env<span class="op">...)</span></code>
to transform the sender if this expression is valid (otherwise the
transformation from <code class="sourceCode cpp">default_domain</code>
is used which doesn’t transform the sender). One way to allow special
transformations for <code class="sourceCode cpp">affine_on</code> is to
defined the <code class="sourceCode cpp">get_domain</code> query for
<code class="sourceCode cpp">affine_on</code> (well, the environment
obtained by <code class="sourceCode cpp">get_env<span class="op">(</span>a<span class="op">)</span></code>
from an <code class="sourceCode cpp">affine_on</code> sender
<code class="sourceCode cpp">a</code>) to yield a custom domain
<code class="sourceCode cpp">affine_on_domain</code> which delegates
transformations of <code class="sourceCode cpp">affine_on<span class="op">(</span>sndr, sch<span class="op">)</span></code>
the sender <code class="sourceCode cpp">sndr</code> or the scheduler
<code class="sourceCode cpp">sch</code>:</p>
<p>Let <code class="sourceCode cpp">affine_on_domain<span class="op">.</span>transform_sender<span class="op">(</span>affsndr, env<span class="op">...)</span></code>
(where <code class="sourceCode cpp">affsndr</code> is the result of
<code class="sourceCode cpp">affine_on<span class="op">(</span>sch, sndr<span class="op">)</span></code>)
be</p>
<ul>
<li>the result of the expression <code class="sourceCode cpp">sndr<span class="op">.</span>affine_on<span class="op">(</span>sch, env<span class="op">...)</span></code>
if it is valid, otherwise</li>
<li>the result of the expression <code class="sourceCode cpp">sch<span class="op">.</span>affine_on<span class="op">(</span>sndr, env<span class="op">...)</span></code>
if it is valid, otherwise</li>
<li>not defined.</li>
</ul>
<p>A similar approach would be used for other algorithms which can be
customised. Currently, no algorithm defines the exact ways it can be
customised in an open form and the intended design for customisations
may be different. The above outlines one possible way.</p>
<h2 data-number="3.2" id="task-operation"><span class="header-section-number">3.2</span> Task Operation<a href="#task-operation" class="self-link"></a></h2>
<p>This section groups three concerns relating to the way
<code class="sourceCode cpp">task</code> gets started and stopped:</p>
<ol type="1">
<li><code class="sourceCode cpp">task</code> shouldn’t reschedule upon
<code class="sourceCode cpp">start<span class="op">()</span></code>.</li>
<li><code class="sourceCode cpp">task</code>s
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ing
<code class="sourceCode cpp">task</code>s shouldn’t reschedule.</li>
<li><code class="sourceCode cpp">task</code> doesn’t support symmetric
Transfer.</li>
</ol>
<h3 data-number="3.2.1" id="starting-a-task-should-not-unconditionally-reschedule"><span class="header-section-number">3.2.1</span> Starting A
<code class="sourceCode cpp">task</code> Should Not Unconditionally
Reschedule<a href="#starting-a-task-should-not-unconditionally-reschedule" class="self-link"></a></h3>
<p>In <a href="(https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise)">[task.promise]</a>
p6, the wording for <code class="sourceCode cpp">initial_suspend</code>
says that it unconditionally suspends and reschedules onto the
associated scheduler. The intent of this wording seems to be that the
coroutine ensures execution initially starts on the associated scheduler
by executing a schedule operation and then resuming the coroutine from
the initial suspend point inside the set_value completion handler of the
schedule operation. The effect of this would be that every call to a
coroutine would have to round-trip through the scheduler, which,
depending on the behaviour of the schedule operation, might relegate it
to the back of the schedulers queue, greatly increasing latency of the
call.</p>
<p>The specification of <code class="sourceCode cpp">initial_suspend<span class="op">()</span></code>
(assuming it is rephrased to not resume the coroutine immediately)
doesn’t really say it unconditionally reschedules. It merely states that
an awaiter is returned which arranges for the coroutine to get resumed
on the correct scheduler. In general, i.e., when the coroutine gets
resumed via <code class="sourceCode cpp">start<span class="op">(</span>os<span class="op">)</span></code>
on an operation state <code class="sourceCode cpp">os</code> obtained
from <code class="sourceCode cpp"><span class="fu">connect</span><span class="op">(</span>tsk, rcvr<span class="op">)</span></code>
it will need to reschedule. However, an implementation can pass
additional context information, e.g., via implementation specific
properties on the receiver’s environment: while the
<code class="sourceCode cpp">get_scheduler</code> query may not provide
the necessary guarantee that the returned scheduler is the scheduler the
operation was started on a different, non-forwardable query could
provide this guarantee.</p>
<p>A possible alternative is to require that <code class="sourceCode cpp">start<span class="op">(</span>op<span class="op">)</span></code>,
where <code class="sourceCode cpp">op</code> is the result of <code class="sourceCode cpp"><span class="fu">connect</span><span class="op">(</span>sndr, rcvr<span class="op">)</span></code>,
is invoked on an execution agent associated with <code class="sourceCode cpp">get_scheduler<span class="op">(</span>get_env<span class="op">(</span>rcvr<span class="op">))</span></code>,
at least when <code class="sourceCode cpp">sndr</code> is a <code class="sourceCode cpp">task<span class="op">&lt;...&gt;</span></code>.
Such a requirement could be desirable more general but it would also be
part of the general sender/receiver contract. The current specification
in [<a href="https://eel.is/c++draft/exec.async.ops">exec.async.ops</a>]
doesn’t have a corresponding constraint. <code class="sourceCode cpp">task<span class="op">&lt;...&gt;</span></code> is
a sender and where it can be used with standard library algorithms this
constraint would hold.</p>
<p>A different way to approach the issue is to use a
<code class="sourceCode cpp">task</code>-specific awaitable when a
<code class="sourceCode cpp">task</code> is
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
by another <code class="sourceCode cpp">task</code>. This use of an
awaiter shouldn’t be observable but it may be better to explicitly spell
out that <code class="sourceCode cpp">task</code> has a domain with
custom transformation of the
<code class="sourceCode cpp">affine_on</code> algorithm and
<code class="sourceCode cpp">as_awaitable</code> operation.</p>
<h3 data-number="3.2.2" id="resuming-after-a-task-should-not-reschedule"><span class="header-section-number">3.2.2</span> Resuming After A
<code class="sourceCode cpp">task</code> Should Not Reschedule<a href="#resuming-after-a-task-should-not-reschedule" class="self-link"></a></h3>
<p>Similar to the previous issue, a
<code class="sourceCode cpp">task</code> needs to resume on the expected
scheduler. The definition of
<code class="sourceCode cpp">await_transform</code> which is used for a
<code class="sourceCode cpp">task</code> is defined in <a href="(https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise)">[task.promise]</a>
paragraph 10 (the
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
<code class="sourceCode cpp">task</code> would be the
<code class="sourceCode cpp">sndr</code> parameter):</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>as_awaitable(affine_on(std::forward&lt;Sender&gt;(sndr), SCHED(*this)), *this)</span></code></pre></div>
<p>As defined there is no additional information about the scheduler on
which the <code class="sourceCode cpp">sndr</code> completes. Thus, it
is likely that a scheduling operation is needed after a
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
<code class="sourceCode cpp">task</code> completes when the outer
<code class="sourceCode cpp">task</code> resumes, even if the
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
<code class="sourceCode cpp">task</code> completed on the same execution
agent.</p>
<p>As <code class="sourceCode cpp">task</code> is scheduler affine, it
is likely that it completes on the same scheduler it was started on,
i.e., there shouldn’t be a need to reschedule (if the scheduler used by
the <code class="sourceCode cpp">task</code> was changed using <code class="sourceCode cpp"><span class="kw">co_await</span> change_coroutine_scheduler<span class="op">(</span>sch<span class="op">)</span></code>
it still needs to be rescheduled). The implementation can provide a
query on the state used to execute the
<code class="sourceCode cpp">task</code> which identifies the scheduler
on which it completed and the
<code class="sourceCode cpp">affine_on</code> implementation can use
this information to decide whether it is necessary to reschedule after
the <code class="sourceCode cpp">task</code>’s completion. It would be
preferable if a corresponding query were specified by the standard
library to have user-defined senders provide similar information but
currently there is no such query.</p>
<p>A different approach is to transform the <code class="sourceCode cpp">affine_on<span class="op">(</span>task, sch<span class="op">)</span></code>
operation into a <code class="sourceCode cpp">task</code>-specific
implementation which arranges to always complete on
<code class="sourceCode cpp">sch</code> using
<code class="sourceCode cpp">task</code> specific information. It may be
necessary to explicit specify or, at least, allow that
<code class="sourceCode cpp">task</code> provides a domain with a custom
transformation for <code class="sourceCode cpp">affine_on</code>.</p>
<h3 data-number="3.2.3" id="no-support-for-symmetric-transfer"><span class="header-section-number">3.2.3</span> No Support For Symmetric
Transfer<a href="#no-support-for-symmetric-transfer" class="self-link"></a></h3>
<p>The specification doesn’t mention any use of symmetric transfer.
Further, the <code class="sourceCode cpp">task</code> gets adapted by
<code class="sourceCode cpp">affine_on</code> in
<code class="sourceCode cpp">await_transform</code> (<a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
paragraph 10) which produces a different sender than
<code class="sourceCode cpp">task</code> which needs special treatment
to use symmetric transfer. With symmetric transfer stack overflow can be
avoided when operation complete immediately, e.g.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">template</span> <span class="op">&lt;</span><span class="kw">typename</span> Env<span class="op">&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>task<span class="op">&lt;</span><span class="dt">void</span>, Env<span class="op">&gt;</span> test<span class="op">()</span> <span class="op">{</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span>std<span class="op">::</span><span class="dt">size_t</span> i<span class="op">{}</span>; i <span class="op">&lt;</span> <span class="dv">1000000</span>; <span class="op">++</span>i<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>        <span class="kw">co_await</span> std<span class="op">::</span>invoke<span class="op">([]()-&gt;</span>task<span class="op">&lt;</span><span class="dt">void</span>, env<span class="op">&lt;&gt;&gt;</span> <span class="op">{</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>            <span class="kw">co_return</span>;</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>        <span class="op">})</span>;</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>When using a scheduler which actually schedules the work (rather than
immediately completing when a corresponding sender gets started) there
isn’t a stack overflow but the scheduling may be slow. With symmetric
transfer the it can be possible to avoid both the expensive scheduling
operation and the stack overflow, at least in some cases. When the inner
<code class="sourceCode cpp">task</code> actually
<code class="sourceCode cpp"><span class="kw">co_await</span></code>s
any work which synchronously completes, e.g., <code class="sourceCode cpp"><span class="kw">co_await</span> just<span class="op">()</span></code>,
the code could still result in a stack overflow despite using symmetric
transfer. There is a general issue that stack size needs to be bounded
when operations complete synchronously. The general idea is to use a
trampoline scheduler which bounds stack size and reschedules when the
stack size or the recursion depth becomes too big.</p>
<p>Except for the presence or absence of stack overflows it shouldn’t be
observable whether an implementation invokes the nested coroutine
directly through an awaiter interface or using the default
implementations of <code class="sourceCode cpp">as_awaitable</code> and
<code class="sourceCode cpp">affine_on</code>. To address the different
scheduling problems (schedule on
<code class="sourceCode cpp">start</code>, schedule on completion, and
symmetric transfer) it may be reasonable to mandate that
<code class="sourceCode cpp">task</code> customises
<code class="sourceCode cpp">affine_on</code> and that the result of
this customisation also customises
<code class="sourceCode cpp">as_awaitable</code>.</p>
<h2 data-number="3.3" id="allocation"><span class="header-section-number">3.3</span> Allocation<a href="#allocation" class="self-link"></a></h2>
<h3 data-number="3.3.1" id="unusual-allocator-customisation"><span class="header-section-number">3.3.1</span> Unusual Allocator
Customisation<a href="#unusual-allocator-customisation" class="self-link"></a></h3>
<p>The allocator customisation mechanism is inconsistent with the design
of <code class="sourceCode cpp">generator</code> allocator
customisation: with <code class="sourceCode cpp">generator</code>, when
you don’t specify an allocator in the template arg, then you can use any
<code class="sourceCode cpp">allocator_arg</code> type. With
<code class="sourceCode cpp">task</code> if no allocator is specified in
the <code class="sourceCode cpp">Environment</code> the allocator type
defaults to <code class="sourceCode cpp">std<span class="op">::</span>allocator<span class="op">&lt;</span>std<span class="op">::</span>byte<span class="op">&gt;</span></code>
and using an allocator with an incompatible type results in an
ill-formed program.</p>
<p>For example:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> default_env <span class="op">{}</span>;</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> allocator_env <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">using</span> allocator_type <span class="op">=</span> std<span class="op">::</span>pmr<span class="op">::</span>polymorphic_allocator<span class="op">&lt;&gt;</span>;</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span>;</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="kw">template</span> <span class="op">&lt;</span><span class="kw">typename</span> Env<span class="op">&gt;</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>ex<span class="op">::</span>task<span class="op">&lt;</span><span class="dt">int</span>, Env<span class="op">&gt;</span> test<span class="op">(</span><span class="dt">int</span> i, <span class="kw">auto</span><span class="op">&amp;&amp;...)</span> <span class="op">{</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">co_return</span> <span class="kw">co_await</span> ex<span class="op">::</span>just<span class="op">(</span>i<span class="op">)</span>;</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Depending on how the coroutine is invoked this may fail to
compile:</p>
<ul>
<li><code class="sourceCode cpp">test<span class="op">&lt;</span>default_env<span class="op">&gt;(</span><span class="dv">17</span><span class="op">)</span></code>:
OK - use default allocator</li>
<li><code class="sourceCode cpp">test<span class="op">&lt;</span>default_env<span class="op">&gt;(</span><span class="dv">17</span>, std<span class="op">::</span>allocator_arg, std<span class="op">::</span>allocator<span class="op">&lt;</span><span class="dt">int</span><span class="op">&gt;())</span></code>:
OK - allocator is convertible</li>
<li><code class="sourceCode cpp">test<span class="op">&lt;</span>default_env<span class="op">&gt;(</span><span class="dv">17</span>, std<span class="op">::</span>allocator_arg, std<span class="op">::</span>pmr<span class="op">::</span>polymorphic_allocator<span class="op">&lt;&gt;())</span></code>:
compile-time error</li>
<li><code class="sourceCode cpp">test<span class="op">&lt;</span>alloctor_env<span class="op">&gt;(</span><span class="dv">17</span>, std<span class="op">::</span>allocator_arg, std<span class="op">::</span>pmr<span class="op">::</span>polymorphic_allocator<span class="op">&lt;&gt;())</span></code>:
OK</li>
</ul>
<p>The main motivator for always having an
<code class="sourceCode cpp">allocator_type</code> is it to support the
<code class="sourceCode cpp">get_allocator</code> query on the
receiver’s environments when
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ing
another sender. The immediate use of the allocator is the allocation of
the coroutine frame and these two needs can be considered separate.</p>
<p>Instead of using the
<code class="sourceCode cpp">allocator_type</code> both for the
environment and the coroutine frame, the coroutine frame could be
allocated with any allocator specified as an argument (i.e., as the
argument following an <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>
argument). The cost of doing so is that the allocator stored with the
coroutine frame would need to be type-erased which is, however, fairly
cheap as only the <code class="sourceCode cpp">deallocate<span class="op">(</span>ptr, size<span class="op">)</span></code>
operation needs to be known and certainly no extra allocation is needed
to do so. If no <code class="sourceCode cpp">allocator_type</code> is
specified in the <code class="sourceCode cpp">Environment</code>
argument to <code class="sourceCode cpp">task</code>, the
<code class="sourceCode cpp">get_allocator</code> query would not be
available from the environment of received used with
<code class="sourceCode cpp"><span class="kw">co_await</span></code>.</p>
<h3 data-number="3.3.2" id="issue-flexible-allocator-position"><span class="header-section-number">3.3.2</span> Issue: Flexible Allocator
Position<a href="#issue-flexible-allocator-position" class="self-link"></a></h3>
<p>For <code class="sourceCode cpp">task<span class="op">&lt;</span>T, E<span class="op">&gt;</span></code>
with position of an <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>
can be anywhere in the argument list. For
<code class="sourceCode cpp">generator</code> the <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>
argument needs to be the first argument. That is consistent with other
uses of <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>.
<code class="sourceCode cpp">task</code> should also require the <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>
argument to be the first argument.</p>
<p>The reason why <code class="sourceCode cpp">task</code> deviates from
the approach normally taken is that the consistent with non-coroutines
is questionable: the primary reason why the <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>
is needed in the first position is that allocation is delegated to <code class="sourceCode cpp">std<span class="op">::</span>uses_allocator</code>
construction. This approach, however, doesn’t apply to coroutines at
all: the coroutine frame is allocated from an <code class="sourceCode cpp"><span class="kw">operator</span> <span class="kw">new</span><span class="op">()</span></code>
overloaded for the <code class="sourceCode cpp">promise_type</code>. In
the context of coroutines the question is rather how to make it easy to
optionally support allocators.</p>
<p>Any coroutine which wants to support allocators for the allocation of
the coroutine frame needs to allow that allocators show up on the
parameter list. As coroutines normally have other parameters, too,
requiring that the optional <code class="sourceCode cpp">std<span class="op">::</span>allocator_arg</code>/allocator
pair of arguments comes first effectively means that a forwarding
function is provided, e.g., for a coroutine taking an
<code class="sourceCode cpp"><span class="dt">int</span></code>
parameter for its work (<code class="sourceCode cpp">Env</code> is
assumed to be a environment type with a suitable definition of
<code class="sourceCode cpp">allocator_type</code>):</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>task&lt;void, Env&gt; work(std::allocator_arg_t, auto, int value) {</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>    co_await just(value);</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>task&lt;void, Env&gt; work(int value) {</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>    return work(std::allocator_arg, std::allocator&lt;std::byte&gt;());</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>If the <code class="sourceCode cpp">allocator_arg</code> argument can
be at an arbitrary location of the parameter list, defining a coroutine
with optional allocator support amounts to adding a suitable trailing
list of parameters, e.g.:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>task&lt;void, Env&gt; work(int value, auto&amp;&amp;...) {</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>    co_await just(value);</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>Constraining <code class="sourceCode cpp">task</code> to match the
use of <code class="sourceCode cpp">generator</code> is entirely
possible. It seems more reasonable to rather consider relaxing the
requirement on <code class="sourceCode cpp">generator</code> in a future
revision of the C++ standard.</p>
<h3 data-number="3.3.3" id="shadowing-the-environment-allocator-is-questionable"><span class="header-section-number">3.3.3</span> Shadowing The Environment
Allocator Is Questionable<a href="#shadowing-the-environment-allocator-is-questionable" class="self-link"></a></h3>
<p>The <code class="sourceCode cpp">get_allocator</code> query on the
environment of receiver passed to
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
senders always returns the allocator determined when the coroutine frame
is created. The allocator provided by the environment of receiver the
<code class="sourceCode cpp">task</code> is
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed to
is hidden.</p>
<p>Creating a coroutine (calling the coroutine function) chooses the
allocator for the coroutine frame, either explicitly specified or
implicitly determined. Either way the coroutine frame is allocated when
the function is called. At this point the coroutine isn’t
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed to
a receiver, yet. The fundamental idea of the allocator model is that
children of an entity use the same allocator as their parent unless an
allocator is explicitly specified for a child. The
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
entities are children of the coroutine and should share the coroutine’s
allocator.</p>
<p>In addition, to forward the allocator from the receiver’s environment
in an environment, its static type needs to be convertible to the
coroutine’s allocator type: the coroutine’s environment types are
determined when the coroutine is created at which point the receiver
isn’t known, yet. Whether the allocator from the receiver’s environment
can be used with the statically determined allocator type can’t be
determined. It may be possible to use a type-erase allocator which could
be created in the operation state when the
<code class="sourceCode cpp">task</code> is
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed.</p>
<p>On the flip side, the receiver’s environment can contain the
configuration from the user of a work-graph which is likely better
informed about the best allocator to use. The allocator from the
receiver’s environment could be forwarded if the
<code class="sourceCode cpp">task</code>’s allocator can be initialised
with it, e.g., because the <code class="sourceCode cpp">task</code> use
<code class="sourceCode cpp">std<span class="op">::</span>pmr<span class="op">::</span>polymorphic_allocator<span class="op">&lt;&gt;</span></code>.
It isn’t clear what should happen if the receiver’s environment has an
allocator which can’t be converted to the allocator based
<code class="sourceCode cpp">task</code>’s environment: at least
ignoring a mismatching allocator or producing a compile time error are
options. It is likely possible to come up with a way to configure the
desired behaviour using the environment.</p>
<h2 data-number="3.4" id="stop-token-management"><span class="header-section-number">3.4</span> Stop Token Management<a href="#stop-token-management" class="self-link"></a></h2>
<h3 data-number="3.4.1" id="a-stop-source-always-needs-to-be-created"><span class="header-section-number">3.4.1</span> A Stop Source Always Needs To
Be Created<a href="#a-stop-source-always-needs-to-be-created" class="self-link"></a></h3>
<p>The specification of the
<code class="sourceCode cpp">promise_type</code> contains
exposition-only members for a stop source and a stop token. It is
expected that in may situations the upstream stop token will be an
<code class="sourceCode cpp">inplace_stop_token</code> and this is also
the token type exposed downstream. In these cases the
<code class="sourceCode cpp">promise_type</code> should store neither a
stop source nor a stop token: instead the stop token should be obtained
from the upstream stream environment. Necessarily storing a stop source
and a stop token would increase the size of the promise type by multiple
pointers. On top of that, to forward the state of an upstream stop token
via a new stop source requires registration/deregistration of a stop
callback which requires dealing with synchronisation.</p>
<p>The expected implementation is, indeed, to get the stop token from
the operation state: when the operation state is created, it is known
whether the upstream stop token is compatible with the statically
determined stop token exposed by
<code class="sourceCode cpp">task</code> to
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
operations via the respective receiver’s environment. It the type is
compatible there is no need to store anything beyond the receiver from
which a stop token can be obtained using the
<code class="sourceCode cpp">get_stop_token</code> query when needed.
Otherwise, the operation state can store an optional stop source which
gets initialised and connected to the upstream stop toke via a stop
callback when the first stop token is requested.</p>
<p>The exposition-only members are not meant to imply that a
corresponding object is actually stored or where they are. Instead, they
are merely meant to talk about the corresponding entities where the
behaviour of the environment is described. If the current wording is
considered to imply that these entities actually exist or it can be
misunderstood to imply that, the wording may need some massaging
possibly using specification macros to refer to the respective entities
in the operation state.</p>
<h3 data-number="3.4.2" id="the-wording-implies-the-stop-token-is-default-constructible"><span class="header-section-number">3.4.2</span> The Wording Implies The Stop
Token Is Default Constructible<a href="#the-wording-implies-the-stop-token-is-default-constructible" class="self-link"></a></h3>
<p>Using an exposition-only member for the stop token implies that the
stop token type is default constructible. The stop token types are
generally not default constructible and are, instead, created via the
stop source and always refer to the corresponding stop source.</p>
<p>The intent here is, indeed, that the stop token type isn’t actually
stored at all: the operation state either stores a stop source which is
used to get the stop token or, probably in the comment case, the stop
token is obtained from the upstream receiver’s environment by querying
<code class="sourceCode cpp">get_stop_token</code>. The rewording for
the previous concern should also address this concern.</p>
<h2 data-number="3.5" id="miscellaneous-concerns"><span class="header-section-number">3.5</span> Miscellaneous Concerns<a href="#miscellaneous-concerns" class="self-link"></a></h2>
<p>The remaining concerns aren’t as coupled to other concerns and
discussed separately.</p>
<h3 data-number="3.5.1" id="task-is-not-actually-lazily-started"><span class="header-section-number">3.5.1</span> Task Is Not Actually Lazily
Started<a href="#task-is-not-actually-lazily-started" class="self-link"></a></h3>
<p>The wording for <code class="sourceCode cpp">task<span class="op">&lt;...&gt;::</span>promise_type<span class="op">::</span>initial_suspend</code>
in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
p6 may imply that a <code class="sourceCode cpp">task</code> is eagerly
started:</p>
<blockquote>
<p><code class="sourceCode cpp"><span class="kw">auto</span> initial_suspend<span class="op">()</span> <span class="kw">noexcept</span>;</code></p>
<p><em>Returns:</em> An awaitable object of unspecified type
([expr.await]) whose member functions arrange for</p>
<ul>
<li>the calling coroutine to be suspended,</li>
<li>the coroutine to be resumed on an execution agent of the execution
resource associated with <code class="sourceCode cpp">SCHED<span class="op">(*</span><span class="kw">this</span><span class="op">)</span></code>.</li>
</ul>
</blockquote>
<p>In particular the second bullet can be interpreted to mean that the
task gets resumed immediately. That wouldn’t actually work because <code class="sourceCode cpp">SCHED<span class="op">(*</span><span class="kw">this</span><span class="op">)</span></code>
only gets initialised when the <code class="sourceCode cpp">task</code>
gets
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed to
a suitable receiver. The intention of the current specification is to
establish the invariant that the coroutine is running on the correct
scheduler when the coroutine is resumed (see discussion of
<code class="sourceCode cpp">affine_on</code> below). The mechanisms
used to achieve that are not detailed to avoid requiring that it gets
scheduled. The formulation should, at least, be improved to clarify that
the coroutine isn’t resumed immediately, possibly changing the text like
this:</p>
<blockquote>
<ul>
<li>the coroutine <span class="rm" style="color: #bf0303"><del>to be
resumed</del></span><span class="add" style="color: #006e28"><ins>resuming</ins></span> on an execution agent
of the execution resource associated with <code class="sourceCode cpp">SCHED<span class="op">(*</span><span class="kw">this</span><span class="op">)</span></code>
<span class="add" style="color: #006e28"><ins>when it gets
resumed</ins></span>.</li>
</ul>
</blockquote>
<p>The proposed fix from the issue is to specify that <code class="sourceCode cpp">initial_suspend<span class="op">()</span></code>
always returns <code class="sourceCode cpp">suspend_always<span class="op">{}</span></code>
and require that
<code class="sourceCode cpp">start<span class="op">(...)</span></code>
calls <code class="sourceCode cpp">handle<span class="op">.</span>resume<span class="op">()</span></code>
to resume the coroutine on the appropriate scheduler after <code class="sourceCode cpp">SCHED<span class="op">(*</span><span class="kw">this</span><span class="op">)</span></code>
has been initialised. The corresponding change could be</p>
<p>Change <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
paragraph 6:</p>
<blockquote>
<p><code class="sourceCode cpp"><span class="kw">auto</span> initial_suspend<span class="op">()</span> <span class="kw">noexcept</span>;</code></p>
<div class="rm" style="color: #bf0303">
<p><em>Returns:</em> An awaitable object of unspecified type
([expr.await]) whose member functions arrange for:</p>
<ul>
<li>the calling coroutine to be suspended,</li>
<li>the coroutine to be resumed on an execution agent of the execution
resource associated with
<code class="sourceCode default">SCHED(*this)</code>.</li>
</ul>

</div>
<div class="add" style="color: #006e28">
<p><em>Returns:</em>
<code class="sourceCode default">suspend_always{}</code>.</p>
</div>
</blockquote>
<p>The suggestion is to ensure that the task gets resumed on the correct
associated context via added requirements on the receiver’s <code class="sourceCode cpp">get_scheduler<span class="op">()</span></code>
(see below).</p>
<p>In separate discussions it was suggested to relax the specification
of <code class="sourceCode cpp">initial_suspend<span class="op">()</span></code>
to allow returning an awaiter which does semantically what
<code class="sourceCode cpp">suspend_always</code> does but isn’t
necessary <code class="sourceCode cpp">suspend_always</code>. The
proposal was to copy the wording of the
<code class="sourceCode cpp">suspend_always</code> specification <a href="https://eel.is/c++draft/coroutine.trivial.awaitables#lib:suspend_always">[coroutine.trivial.awaitables]</a>.
This specification just shows the class completion definition.</p>
<h3 data-number="3.5.2" id="the-coroutine-frame-is-destroyed-too-late"><span class="header-section-number">3.5.2</span> The Coroutine Frame Is
Destroyed Too Late<a href="#the-coroutine-frame-is-destroyed-too-late" class="self-link"></a></h3>
<p>When the <code class="sourceCode cpp">task</code> completes from a
suspended coroutine without ever reaching
<code class="sourceCode cpp">final_suspend</code> the coroutine frame
lingers until the operation state object is destroyed. This happens when
the <code class="sourceCode cpp">task</code> isn’t resumed because a
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
sender completed with <code class="sourceCode cpp">set_stopped<span class="op">()</span></code> or
when the <code class="sourceCode cpp">task</code> completes using <code class="sourceCode cpp"><span class="kw">co_yield</span> when_error<span class="op">(</span>e<span class="op">)</span></code>.
In these cases the order of destruction of object may be unexpected:
objects held by the coroutine frame may be destroyed only long after the
respective completion function was called.</p>
<p>This behaviour is an oversight in the specification and not
intentional at all. Instead, there should probably be a statement that
the coroutine frame is destroyed before the any of the completion
functions is invoked. The implication is that the results can’t be
stored in the coroutine frame but that is fine as the best place store
them is in the operation state.</p>
<h3 data-number="3.5.3" id="taskt-e-has-no-default-arguments"><span class="header-section-number">3.5.3</span> <code class="sourceCode cpp">task<span class="op">&lt;</span>T, E<span class="op">&gt;</span></code>
Has No Default Arguments<a href="#taskt-e-has-no-default-arguments" class="self-link"></a></h3>
<p>The current specification of <code class="sourceCode cpp">task<span class="op">&lt;</span>T, E<span class="op">&gt;</span></code>
doesn’t have any default arguments in its first declaration in <a href="https://eel.is/c++draft/execution.syn">[execution.syn]</a>. The
intent was to default the type <code class="sourceCode cpp">T</code> to
<code class="sourceCode cpp"><span class="dt">void</span></code> and the
environment <code class="sourceCode cpp">E</code> to
<code class="sourceCode cpp">env<span class="op">&lt;&gt;</span></code>.
That is, this change should be applied to <a href="https://eel.is/c++draft/execution.syn">[execution.syn]</a>:</p>
<blockquote>
<div class="sourceCode" id="cb8"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>  ...</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>  // [exec.task]</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>  template &lt;class T<span class="add" style="color: #006e28"><ins>= void</ins></span>, class Environment<span class="add" style="color: #006e28"><ins>= env&lt;&gt;</ins></span>&gt;</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>  class task;</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>  ...</span></code></pre></div>
</blockquote>
<p>It isn’t catastrophic if that change isn’t made but it seems to
improve usability without any drawbacks.</p>
<h3 data-number="3.5.4" id="unhandled_stopped-isnt-noexcept"><span class="header-section-number">3.5.4</span> <code class="sourceCode cpp">unhandled_stopped<span class="op">()</span></code>
Isn’t
<code class="sourceCode cpp"><span class="kw">noexcept</span></code><a href="#unhandled_stopped-isnt-noexcept" class="self-link"></a></h3>
<p>The <code class="sourceCode cpp">unhandled_stopped<span class="op">()</span></code>
member function of <code class="sourceCode cpp">task<span class="op">::</span>promise_type</code>
<a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
is not currently marked as
<code class="sourceCode cpp"><span class="kw">noexcept</span></code>.</p>
<p>As this method is generally called from the
<code class="sourceCode cpp">set_stopped</code> completion-handler of a
receiver (such as in <a href="https://eel.is/c++draft/exec.as.awaitable#4.3">[exec.as.awaitable]
p4.3</a>) and is invoked without handler from a
<code class="sourceCode cpp"><span class="kw">noexcept</span></code>
function, we should probably require that this function is marked
<code class="sourceCode cpp"><span class="kw">noexcept</span></code> as
well.</p>
<p>The equivalent method defined as part of the
<code class="sourceCode cpp">with_awaitable_senders</code> base-class
(<a href="https://eel.is/c++draft/exec.with.awaitable.senders#1">[exec.with.awaitable.senders]</a>
p1) is also marked
<code class="sourceCode cpp"><span class="kw">noexcept</span></code>.</p>
<p>This change should be applied to <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>:</p>
<blockquote>
<div class="sourceCode" id="cb9"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>    ...</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>    void uncaught_exception();</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>    coroutine_handle&lt;&gt; unhandled_stopped()<span class="add" style="color: #006e28"><ins>noexcept</ins></span>;</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>    void return_void(); // present only if is_void_v&lt;T&gt; is true;</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>    ...</span></code></pre></div>
<p>…</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode default"><code class="sourceCode default"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>coroutine_handle&lt;&gt; unhandled_stopped()<span class="add" style="color: #006e28"><ins>noexcept</ins></span>;</span></code></pre></div>
<p><span class="marginalizedparent"><a class="marginalized">13</a></span>
<em>Effects</em>: Completes the asynchronous operation associated with
<code class="sourceCode cpp">STATE<span class="op">(*</span><span class="kw">this</span><span class="op">)</span></code>
by invoking <code class="sourceCode cpp">set_stopped<span class="op">(</span>std<span class="op">::</span>move<span class="op">(</span>RCVR<span class="op">(*</span><span class="kw">this</span><span class="op">)))</span></code>.</p>
</blockquote>
<h3 data-number="3.5.5" id="the-environment-design-may-be-inefficient"><span class="header-section-number">3.5.5</span> The Environment Design May Be
Inefficient<a href="#the-environment-design-may-be-inefficient" class="self-link"></a></h3>
<p>The environment returned from <code class="sourceCode cpp">get_env<span class="op">(</span>rcvr<span class="op">)</span></code>
for the receiver used to
<code class="sourceCode cpp"><span class="fu">connect</span></code> to
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
sender is described in terms of members of the <code class="sourceCode cpp">promise_type<span class="op">::</span><em>state</em></code>
in [<a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">task.promise</a>]
paragraph 15. In particular the
<code class="sourceCode cpp"><em>state</em></code> contains a member
<code class="sourceCode cpp"><em>own-env</em></code> which gets
initialised via the upstream receiver’s environment (if the
corresponding expression is valid). As the environment
<code class="sourceCode cpp"><em>own-env</em></code> is initialised with
a temporary, <code class="sourceCode cpp"><em>own-env</em></code> will
need to create a copy of whatever it is holding.</p>
<p>As the environment exposed to
<code class="sourceCode cpp"><span class="kw">co_await</span></code>ed
senders via the <code class="sourceCode cpp">get_env<span class="op">(</span>rcvr<span class="op">)</span></code>
is statically determined when the
<code class="sourceCode cpp">task</code> is created, not when the
<code class="sourceCode cpp">task</code> is
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed to
an upstream receiver, the environment needs to be type erase. Some of
the queries (<code class="sourceCode cpp">get_scheduler</code>,
<code class="sourceCode cpp">get_stop_token</code>, and
<code class="sourceCode cpp">get_allocator</code>) are known and already
receive treatment by the <code class="sourceCode cpp">task</code>
itself. However, the <code class="sourceCode cpp">task</code> cannot
know about user-defined properties which may need to be type-erased as
well. To all the <code class="sourceCode cpp">Environment</code> to
possibly type-erase properties from the upstream receiver, an <code class="sourceCode cpp">own<span class="op">-</span>env<span class="op">-</span>t</code>
object is created and a reference to the object is passed to the
<code class="sourceCode cpp">Environment</code> constructor (if there
are suitable constructors). Thus, the entire use of <code class="sourceCode cpp">own<span class="op">-</span>env<span class="op">-</span>t</code>
is about enabling storage of whatever is needed to type-erase the result
of user-defined queries. Ideally, this functionality isn’t needed and
the <code class="sourceCode cpp">Environment</code> parameter doesn’t
specify an <code class="sourceCode cpp">env_type</code>. If it is needed
the type-erase will likely need to store some data.</p>
<h3 data-number="3.5.6" id="no-completion-scheduler"><span class="header-section-number">3.5.6</span> No Completion Scheduler<a href="#no-completion-scheduler" class="self-link"></a></h3>
<p>The concern raised is that <code class="sourceCode cpp">task</code>
doesn’t define a <code class="sourceCode cpp">get_env</code> that
returns an environment with a <code class="sourceCode cpp">get_completion_scheduler<span class="op">&lt;</span>Tag<span class="op">&gt;</span></code>
query. As a result, it will be necessary to reschedule in situations
although the <code class="sourceCode cpp">task</code> already completes
on the correct scheduler.</p>
<p>The query <code class="sourceCode cpp">get_completion_scheduler<span class="op">(</span>env<span class="op">)</span></code>
operates on the sender’s environment, i.e., it would operate on the
<code class="sourceCode cpp">task</code>’s environment. However, when
the <code class="sourceCode cpp">task</code> is created it has no
information about the scheduler, yet, it is going to use. The entire
information the <code class="sourceCode cpp">task</code> has is what is
passed to the coroutine [factory] function. The information about any
scheduling gets provided when the
<code class="sourceCode cpp">task</code> is
<code class="sourceCode cpp"><span class="fu">connect</span></code>ed to
a receiver: the receiver provides the queries on where the task is
started (<code class="sourceCode cpp">get_scheduler</code>). The
<code class="sourceCode cpp">task</code> may complete on this scheduler
assuming the scheduler isn’t changed (using <code class="sourceCode cpp"><span class="kw">co_await</span> change_coroutine_scheduler<span class="op">(</span>sch<span class="op">)</span></code>).</p>
<p>The only place where a <code class="sourceCode cpp">task</code>
actually knows its completion scheduler is once it has completed.
However, there is no query or environment defined for completed
operation states. Thus, it seems there is no way where a
<code class="sourceCode cpp">get_completion_scheduler</code> would be
useful. A future evolution of the sender/receiver interface may change
that in which case <code class="sourceCode cpp">task</code> should be
revisited.</p>
<h3 data-number="3.5.7" id="awaitable-non-senders-are-not-supported"><span class="header-section-number">3.5.7</span> Awaitable
non-<code class="sourceCode cpp">sender</code>s Are Not Supported<a href="#awaitable-non-senders-are-not-supported" class="self-link"></a></h3>
<p>The overload of <code class="sourceCode cpp">await_transform</code>
described in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
is constrained to require arguments to satisfy the <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
concept. However, this precludes awaiting types that implement the <a href="https://eel.is/c++draft/exec.as.awaitable"><code class="sourceCode cpp">as_awaitable<span class="op">()</span></code></a>
customisation point but that do not satisfy the <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
concept from being able to be awaited within a
<code class="sourceCode cpp">task</code> coroutine.</p>
<p>This is inconsistent with the behaviour of the <a href><code class="sourceCode cpp">with_awaitable_senders</code></a>
base class defined in <a href="https://eel.is/c++draft/exec.with.awaitable.senders">[exec.with.awaitable.senders]</a>,
which only requires that the awaited value supports the <a href="https://eel.is/c++draft/exec.as.awaitable"><code class="sourceCode cpp">as_awaitable<span class="op">()</span></code></a>
operation.</p>
<p>The rationale for this is that the argument needs to be passed to the
<a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#executionaffine_on-exec.affine.on"><code class="sourceCode cpp">affine_on</code></a>
algorithm which currently requires its argument to model <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>.
This requirement in turn is there because it is unclear how to guarantee
scheduler affinity with an awaitable-only interface without wrapping a
coroutine around the awaitable.</p>
<p>Do we want to consider relaxing this constraint to be consistent with
the constraints on <a href="https://eel.is/c++draft/exec.with.awaitable.senders">[exec.with.awaitable.senders]</a>?</p>
<p>This would require either:</p>
<ul>
<li>relaxing the <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
constraint on the <code class="sourceCode cpp">affine_on</code>
algorithm to also allow an argument that has only an <a href="https://eel.is/c++draft/exec.as.awaitable"><code class="sourceCode cpp">as_awaitable<span class="op">()</span></code></a>
but that did not satisfy the <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
concept.</li>
<li>extending the <a href="https://eel.is/c++draft/exec.snd.concepts"><code class="sourceCode cpp">sender</code></a>
concept to match types that provide the <code class="sourceCode cpp"><span class="op">.</span>as_awaitable<span class="op">()</span></code>
member-function similar to how it supports types with <code class="sourceCode cpp"><span class="kw">operator</span>   <span class="kw">co_await</span><span class="op">()</span></code>
(see <a href="https://eel.is/c++draft/exec.connect">[exec.connect]</a>).</li>
</ul>
<h3 data-number="3.5.8" id="should-taskpromise_type-use-with_awaitable_senders"><span class="header-section-number">3.5.8</span> Should <code class="sourceCode cpp">task<span class="op">::</span>promise_type</code>
Use <code class="sourceCode cpp">with_awaitable_senders</code>?<a href="#should-taskpromise_type-use-with_awaitable_senders" class="self-link"></a></h3>
<p>The existing <a href="https://eel.is/c++draft/exec">[exec]</a>
wording added the <a href="https://eel.is/c++draft/exec#with.awaitable.senders"><code class="sourceCode cpp">with_awaitable_senders</code></a>
helper class with the intention that it be usable as the base-class for
asynchronous coroutine promise-types to provide the ability to await
senders. It does this by providing the necessary
<code class="sourceCode cpp">await_transform</code> overload and also
the <code class="sourceCode cpp">unhandled_stopped</code>
member-function necessary to support the <code class="sourceCode cpp">as_awaitable<span class="op">()</span></code>
adaptor for senders.</p>
<p>However, the current specification of <code class="sourceCode cpp">task<span class="op">::</span>promise_types</code>
does not use this facility for a couple of reasons:</p>
<ul>
<li>It needs to apply the <code class="sourceCode cpp">affine_on</code>
adaptor to awaited senders.</li>
<li>It needs to provide a custom implementation of
<code class="sourceCode cpp">unhandled_stopped</code> that reschedules
onto the original scheduler in the case that it is different from the
current scheduler (e.g. due to <code class="sourceCode cpp"><span class="kw">co_await</span> change_coroutine_scheduler<span class="op">{</span>other<span class="op">}</span></code>).</li>
</ul>
<p>This raises a couple of questions:</p>
<ul>
<li>Should there also be a
<code class="sourceCode cpp">with_affine_awaitable_senders</code> that
implements the scheduler affinity logic?</li>
<li>Is <code class="sourceCode cpp">with_awaitable_senders</code>
actually the right design if the first coroutine type we add to the
standard library doesn’t end up using it?</li>
</ul>
<p>These are valid questions. It seems the
<code class="sourceCode cpp">with_awaitable_senders</code> class was
designed at time when there was no consensus that a
<code class="sourceCode cpp">task</code> coroutine should be scheduler
affine by default. However, these questions don’t really affect the
design of <code class="sourceCode cpp">task</code> and they can be
answered in a future revision of the standard. The
<code class="sourceCode cpp">task</code> specification also uses a few
other tools which could be useful for users who want to implement their
own custom <code class="sourceCode cpp">task</code>-like coroutine. Such
tools should probably be proposed for inclusion into a future revision
of the C++ standard, too.</p>
<p>However, what is relevant to the
<code class="sourceCode cpp">task</code> specification is that the
wording for <code class="sourceCode cpp">unhandled_stopped</code> in <a href="https://wiki.edg.com/pub/Wg21sofia2025/StrawPolls/P3552R3.html#class-taskpromise_type-task.promise">[task.promise]</a>
paragraph 13 doesn’t spell out that the
<code class="sourceCode cpp">set_stopped</code> completion is invoked on
the correct scheduler.</p>
<h3 data-number="3.5.9" id="a-future-coroutine-feature-could-avoid-co_yield-for-errors"><span class="header-section-number">3.5.9</span> A Future Coroutine Feature
Could Avoid
<code class="sourceCode cpp"><span class="kw">co_yield</span></code> For
Errors<a href="#a-future-coroutine-feature-could-avoid-co_yield-for-errors" class="self-link"></a></h3>
<p>The mechanism to complete with an error without using an exception
uses <code class="sourceCode cpp"><span class="kw">co_yield</span> with_error<span class="op">(</span>x<span class="op">)</span></code>.
This use may surprise users as
<code class="sourceCode cpp"><span class="kw">co_yield</span></code>ing
is normally assumed not to be a final operation. A future language
change may provide a better way to achieve the objective of reporting
errors without using exceptions.</p>
<p>Using not, yet, proposed features which may become part of a future
revision of the C++ standard may always provide facilities which result
in a better design for pretty much anything. There is no current
<code class="sourceCode cpp">task</code> implementation and certainly
none which promises ABI stability. Assuming a corresponding language
change gets proposed reasonably early for the next cycle, implementation
can take it into account for possibly improving the interface without
the need to break ABI. The shape of the envisioned language change is
unknown but most likely it is an extension which hopefully be integrated
with the existing design. The functionality to use
<code class="sourceCode cpp"><span class="kw">co_yield</span></code>
would, however, continue to exist until it eventually gets deprecated
and removed (assuming a better alternative emerges).</p>
<h3 data-number="3.5.10" id="there-is-no-hook-to-capturerestore-tls"><span class="header-section-number">3.5.10</span> There Is No Hook To
Capture/Restore TLS<a href="#there-is-no-hook-to-capturerestore-tls" class="self-link"></a></h3>
<p>This concern is the only one I was aware of for a few weeks prior to
the Sofia meeting. I believe the necessary functionality can be
implemented although it is possibly harder to do than with more direct
support. The concern raised is that existing code accesses thread local
storage (TLS) to store context information. When a
<code class="sourceCode cpp"><span class="kw">co_await</span></code>
actually suspends it is possible that a different task running on the
same thread replaces used TLS data or the task gets resumed on a
different thread of a thread pool. In that case it is necessary to
capture the TLS variables prior to suspending and restoring them prior
to resuming the coroutine. There is currently no way to actually do
that.</p>
<p>The sender/receiver approach to propagating context information isn’t
using TLS but rather the environments accessed via the receiver.
However, existing software does use TLS and at least for migration
corresponding support may be necessary. One way to implement this
functionality is using a scheduler adapter with a customised
<code class="sourceCode cpp">affine_on</code> algorithm:</p>
<ul>
<li>When the <code class="sourceCode cpp">affine_on</code> algorithm is
started, it captures the relevant TLS data into the operation state and
then starts <code class="sourceCode cpp">affine_on</code> for the
adapted scheduler with a receiver observing the completions.</li>
<li>When the adapted scheduler invokes any of the completion signals the
TLS data is restored before invoke the algorithms completion
signal.</li>
</ul>
<p>Support for optionally capturing some state before starting a child
operation and restoring the captured started before resuming could be
added to the <code class="sourceCode cpp">task</code>, avoiding the need
to use custom scheduling. Doing so would yield a nicer and easier to use
interface. In environment where TLS is currently used and developers
move to an asynchronous model, failure to capture and restore data in
TLS is a likely source of errors. However, it will be necessary to
specify what data needs to be stored, i.e., the problems can’t be
automatically avoided.</p>
<h1 data-number="4" id="conclusion"><span class="header-section-number">4</span> Conclusion<a href="#conclusion" class="self-link"></a></h1>
<p>There are some parts of the specification which can be misunderstood.
These should be clarified correspondingly. In most of these cases the
change only affects the wording and doesn’t affect the design.</p>
<p>For the issues around <code class="sourceCode cpp">affine_on</code>
there are some potential design changes. In particular with respect to
the exact semantics of <code class="sourceCode cpp">affine_on</code> the
design was possibly unclear. Likewise, explicitly specifying that
<code class="sourceCode cpp">task</code> customises
<code class="sourceCode cpp">affine_on</code> and provides an
<code class="sourceCode cpp">as_awaitable</code> function is somewhat in
design space. The intention was that doing so would be possible with the
current specification and it just didn’t spell out how
<code class="sourceCode cpp"><span class="kw">co_await</span></code> a
<code class="sourceCode cpp">task</code> from a
<code class="sourceCode cpp">task</code> actually works.</p>
<p>In any case, some fixed of the specification are needed.</p>
<h1 data-number="5" id="potential-polls"><span class="header-section-number">5</span> Potential Polls<a href="#potential-polls" class="self-link"></a></h1>
<ol type="1">
<li>Should <code class="sourceCode cpp">task</code> be renamed to
something else?</li>
<li>Is the naming scheme
<code class="sourceCode cpp">task<em>year</em></code> an approach to be
used?</li>
</ol>
<h1 data-number="6" id="acknowledgement"><span class="header-section-number">6</span> Acknowledgement<a href="#acknowledgement" class="self-link"></a></h1>
<p>The issue descriptions are largely based on <a href="https://github.com/lewissbaker/papers/blob/master/isocpp/task-issues.org">this
draft</a> written by Lewis Baker. Lewis Baker and Tomasz Kamiski
contributed to the discussions towards addressing the issues.</p>
</div>
</div>
</body>
</html>
