<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang xml:lang>
<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="2023-10-14" />
  <title>Sender Algorithm Customization</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">Sender Algorithm
Customization</h1>
<table style="border:none;float:right">
  <tr>
    <td>Document #:</td>
    <td>P2999R0</td>
  </tr>
  <tr>
    <td>Date:</td>
    <td>2023-10-14</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>
      LEWG Library Evolution<br>
    </td>
  </tr>
  <tr>
    <td style="vertical-align:top">Reply-to:</td>
    <td>
      Eric Niebler<br>&lt;<a href="mailto:eric.niebler@gmail.com" class="email">eric.niebler@gmail.com</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="#introduction" id="toc-introduction"><span class="toc-section-number">1</span> Introduction<span></span></a></li>
<li><a href="#the-issue" id="toc-the-issue"><span class="toc-section-number">2</span> The Issue<span></span></a></li>
<li><a href="#proposed-design" id="toc-proposed-design"><span class="toc-section-number">3</span> Proposed Design<span></span></a>
<ul>
<li><a href="#features-and-rationale" id="toc-features-and-rationale"><span class="toc-section-number">3.1</span> Features and
rationale<span></span></a>
<ul>
<li><a href="#dispatching-via-domain-tags" id="toc-dispatching-via-domain-tags"><span class="toc-section-number">3.1.1</span> Dispatching via domain
tags<span></span></a></li>
<li><a href="#late-senderreceiver-connection-time-customization" id="toc-late-senderreceiver-connection-time-customization"><span class="toc-section-number">3.1.2</span> Late (sender/receiver
connection-time) customization<span></span></a></li>
<li><a href="#early-sender-construction-time-customization" id="toc-early-sender-construction-time-customization"><span class="toc-section-number">3.1.3</span> Early (sender construction-time)
customization<span></span></a></li>
<li><a href="#decomposable-senders" id="toc-decomposable-senders"><span class="toc-section-number">3.1.4</span> Decomposable
senders<span></span></a></li>
</ul></li>
<li><a href="#summary-of-proposed-changes" id="toc-summary-of-proposed-changes"><span class="toc-section-number">3.2</span> Summary of proposed
changes<span></span></a></li>
</ul></li>
<li><a href="#implementation-experience" id="toc-implementation-experience"><span class="toc-section-number">4</span> Implementation
Experience<span></span></a></li>
<li><a href="#proposed-wording" id="toc-proposed-wording"><span class="toc-section-number">5</span> Proposed
Wording<span></span></a></li>
<li><a href="#bibliography" id="toc-bibliography"><span class="toc-section-number">6</span> References<span></span></a></li>
</ul>
</div>
<h1 data-number="1" id="introduction"><span class="header-section-number">1</span> Introduction<a href="#introduction" class="self-link"></a></h1>
<p>This paper proposes some design changes to P2300 to address some
shortcomings in how algorithm customizations are found.</p>
<h1 data-number="2" id="the-issue"><span class="header-section-number">2</span> The Issue<a href="#the-issue" class="self-link"></a></h1>
<dl>
<dt>The essence of the issue is this:</dt>
<dd>
<p><em>Many senders do not know on what execution context they will
complete, so using solely that information to find customizations (as
P2300R7 does) is unsatisfactory.</em></p>
</dd>
</dl>
<p>In <span class="citation" data-cites="P2300R7">[<a href="#ref-P2300R7" role="doc-biblioref">P2300R7</a>]</span>, the sender
algorithms (<code class="sourceCode default">then</code>,
<code class="sourceCode default">let_value</code>, etc) are
customization point objects that internally dispatch via
<code class="sourceCode default">tag_invoke</code> to the correct
algorithm implementation. Each algorithm has a default implementation
that is used if no custom implementation is found.</p>
<p>Custom implementations of sender algorithms are found by asking the
predecessor sender for its completion scheduler and using the scheduler
as a tag for the purpose of tag dispatching. A <em>completion
scheduler</em> is a scheduler that refers to the execution context on
which that sender will complete.</p>
<p>A typical sender algorithm like
<code class="sourceCode default">then</code> might be implemented as
follows:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">/// </span><span class="an">@brief</span><span class="co"> A helper concept for testing whether an algorithm customization</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co">///   exists</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">template</span> <span class="op">&lt;</span><span class="kw">class</span> AlgoTag, <span class="kw">class</span> SetTag, <span class="kw">class</span> Sender, <span class="kw">class</span><span class="op">...</span> Args<span class="op">&gt;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">concept</span> <em>has-customization</em> <span class="op">=</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>  <span class="kw">requires</span> <span class="op">(</span>Sender sndr, Args<span class="op">...</span> args<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    tag_invoke<span class="op">(</span>AlgoTag<span class="op">()</span>,</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>               get_completion_scheduler<span class="op">&lt;</span>SetTag<span class="op">&gt;(</span>get_env<span class="op">(</span>sndr<span class="op">))</span>,</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>               std<span class="op">::</span>forward<span class="op">&lt;</span>Sender<span class="op">&gt;(</span>sndr<span class="op">)</span>,</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>               std<span class="op">::</span>forward<span class="op">&lt;</span>Args<span class="op">&gt;(</span>args<span class="op">)...)</span>;</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span>;</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co">/// </span><span class="an">@brief</span><span class="co"> The tag type and the customization point object type for the</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="co">///   `then` sender algorithm</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> then_t <span class="op">{</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>  <span class="kw">template</span> <span class="op">&lt;</span>sender Sender, <span class="kw">class</span> Fun<span class="op">&gt;</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    <span class="kw">requires</span> <span class="co">/* requirements here */</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>  <span class="kw">auto</span> <span class="kw">operator</span><span class="op">()(</span>Sender<span class="op">&amp;&amp;</span> sndr, Fun fun<span class="op">)</span> <span class="kw">const</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>  <span class="op">{</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>    <span class="co">// If the predecessor sender has a completion scheduler, and if we can use</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>    <span class="co">// the completion scheduler to find a custom implementation for the `then`</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>    <span class="co">// algorithm, dispatch to that. Otherwise, dispatch to the default `then`</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>    <span class="co">// implementation.</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="kw">constexpr</span> <span class="op">(</span><em>has-customization</em><span class="op">&lt;</span>then_t, set_value_t, Sender, Fun<span class="op">&gt;)</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>      <span class="kw">auto</span><span class="op">&amp;&amp;</span> env <span class="op">=</span> get_env<span class="op">(</span>sndr<span class="op">)</span>;</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>      <span class="cf">return</span> tag_invoke<span class="op">(*</span><span class="kw">this</span>,</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a>                        get_completion_scheduler<span class="op">&lt;</span>set_value_t<span class="op">&gt;(</span>env<span class="op">)</span>,</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a>                        std<span class="op">::</span>forward<span class="op">&lt;</span>Sender<span class="op">&gt;(</span>sndr<span class="op">)</span>,</span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a>                        std<span class="op">::</span>move<span class="op">(</span>fun<span class="op">))</span>;</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a>    <span class="cf">else</span></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>      <span class="cf">return</span> <em>then-sender</em><span class="op">&lt;</span>Sender, Fun<span class="op">&gt;(</span>std<span class="op">::</span>forward<span class="op">&lt;</span>Sender<span class="op">&gt;(</span>sndr<span class="op">)</span>, std<span class="op">::</span>move<span class="op">(</span>fun<span class="op">))</span>;</span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a><span class="op">}</span>;</span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a><span class="kw">inline</span> <span class="kw">constexpr</span> then_t then <span class="op">{}</span>;</span></code></pre></div>
<p>This scheme has a number of shortcomings:</p>
<ol type="1">
<li><p>A simple sender like
<code class="sourceCode default">just(42)</code> does not know its
completion scheduler. It completes on the execution context on which it
is started. That is not known at the time the sender is constructed,
which is when we are looking for customizations.</p></li>
<li><p>For a sender like
<code class="sourceCode default">on( sched, then(just(), fun) )</code>,
the nested <code class="sourceCode default">then</code> sender is
constructed before we have specified the scheduler, but we need the
scheduler to dispatch to the correct customization of
<code class="sourceCode default">then</code>. How?</p></li>
<li><p>A composite sender like
<code class="sourceCode default">when_all(sndr1, sndr2)</code> cannot
know its completion scheduler in the general case. Even if
<code class="sourceCode default">sndr1</code> and
<code class="sourceCode default">sndr2</code> both know their completion
schedulers – say, <code class="sourceCode default">sched1</code> and
<code class="sourceCode default">sched2</code> respectively – the
<code class="sourceCode default">when_all</code> sender can complete on
<em>either</em> <code class="sourceCode default">sched1</code>
<em>or</em> <code class="sourceCode default">sched2</code> depending on
which of <code class="sourceCode default">sndr1</code> and
<code class="sourceCode default">sndr2</code> completes last. That is a
dynamic property of the program’s execution, not suitable for finding an
algorithm customization.</p></li>
</ol>
<p>In cases (1) and (2), the issue is that the information necessary to
find the correct algorithm implementation is not available at the time
we look for customizations. In case (3), the issue is that the algorithm
semantics make it impossible to know statically to what algorithm
customization scheme to dispatch.</p>
<p>The issue described in (2) above is particularly pernicious. Consider
these two programs (where <code class="sourceCode default">ex::</code>
is a namespace alias for
<code class="sourceCode default">std::execution</code>):</p>
<table>
<thead>
<tr class="header">
<th><div style="text-align:center">
<strong>Good</strong>
</div></th>
<th><div style="text-align:center">
<strong>Bad</strong>
</div></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><div>

<div class="sourceCode" id="cb2"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>my<span class="op">::</span>thread_pool_scheduler sch <span class="op">=</span> <span class="co">/*...*/</span>;</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Describe some bulk work on a thread pool</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="kw">auto</span> work <span class="op">=</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  ex<span class="op">::</span>transfer_just<span class="op">(</span>sch, data<span class="op">)</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="op">|</span> ex<span class="op">::</span>bulk<span class="op">(</span>data<span class="op">.</span>size<span class="op">()</span>,</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>           <span class="op">[](</span><span class="dt">int</span> i, <span class="kw">auto</span><span class="op">&amp;</span> data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>             <span class="op">++</span>data<span class="op">[</span>i<span class="op">]</span>;</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>           <span class="op">})</span>;</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="co">// Execute the work</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>std<span class="op">::</span>this_thread<span class="op">::</span>sync_wait<span class="op">(</span>std<span class="op">::</span>move<span class="op">(</span>work<span class="op">))</span>;</span></code></pre></div>

</div></td>
<td><div>

<div class="sourceCode" id="cb3"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>my<span class="op">::</span>thread_pool_scheduler sch <span class="op">=</span> <span class="co">/*...*/</span>;</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co">// Describe some bulk work</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">auto</span> work <span class="op">=</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>  ex<span class="op">::</span>just<span class="op">(</span>data<span class="op">)</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="op">|</span> ex<span class="op">::</span>bulk<span class="op">(</span>data<span class="op">.</span>size<span class="op">()</span>,</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>           <span class="op">[](</span><span class="dt">int</span> i, <span class="kw">auto</span><span class="op">&amp;</span> data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>             <span class="op">++</span>data<span class="op">[</span>i<span class="op">]</span>;</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>           <span class="op">})</span>;</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="co">// Execute the bulk work on a thread pool</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>std<span class="op">::</span>this_thread<span class="op">::</span>sync_wait<span class="op">(</span>ex<span class="op">::</span>on<span class="op">(</span>sch, std<span class="op">::</span>move<span class="op">(</span>work<span class="op">)))</span>;</span></code></pre></div>

</div></td>
</tr>
</tbody>
</table>
<p>These two programs <em>should</em> be equivalent, but they are not.
The author of the
<code class="sourceCode default">thread_pool_scheduler</code> gave it a
custom <code class="sourceCode default">bulk</code> implementation by
defining:</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">namespace</span> my <span class="op">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="co">// customization of the bulk algorithm for the thread_pool_scheduler:</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">template</span> <span class="op">&lt;</span>ex<span class="op">::</span>sender Sender, std<span class="op">::</span>integral Shape, <span class="kw">class</span> Function<span class="op">&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  <span class="kw">auto</span> tag_invoke<span class="op">(</span>ex<span class="op">::</span>bulk_t,</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>                  thread_pool_scheduler sched,</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>                  Sender<span class="op">&amp;&amp;</span> sndr,</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>                  Shape shape,</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>                  Function fun<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>    <span class="co">/*...*/</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>This overload is found only when the
<code class="sourceCode default">bulk</code> sender’s predecessor
completes on a
<code class="sourceCode default">thread_pool_scheduler</code>.</p>
<p>In the code to the right, however, the predecessor of the
<code class="sourceCode default">bulk</code> operation is
<code class="sourceCode default">just(data)</code>, a sender that does
not know where it will complete. As a result, the above customization of
the <code class="sourceCode default">bulk</code> algorithm will not be
found, and the bulk operation will execute serially on a single thread
in the thread pool. That’s almost certainly <em>not</em> what the
programmer intended.</p>
<p>This is clearly broken and badly in need of fixing.</p>
<blockquote>
<p><em>Note:</em> On the need for async algorithms customization</p>
<p>It is worth asking why async algorithms need customization at all.
After all, the classic STL algorithms need no customization; they
dispatch using a fixed concept hierarchy to a closed set of possible
implementations.</p>
<p>The reason is because of the open and continually evolving nature of
execution contexts. There is little hope of capturing every salient
attribute of every interesting execution model – CPUs, GPUs, FPGAs,
etc., past, present, and future – in a fixed ontology around which we
can build named concepts and immutable basis operations. Instead we do
the best we can and then hedge against the future by making the
algorithms customizable.</p>
</blockquote>
<h1 data-number="3" id="proposed-design"><span class="header-section-number">3</span> Proposed Design<a href="#proposed-design" class="self-link"></a></h1>
<h2 data-number="3.1" id="features-and-rationale"><span class="header-section-number">3.1</span> Features and rationale<a href="#features-and-rationale" class="self-link"></a></h2>
<p>This section describes at a high level the salient features of the
proposed design for sender algorithm customization, and their
rationale.</p>
<h3 data-number="3.1.1" id="dispatching-via-domain-tags"><span class="header-section-number">3.1.1</span> Dispatching via domain tags<a href="#dispatching-via-domain-tags" class="self-link"></a></h3>
<p>As described above, the
<code class="sourceCode default">when_all</code> sender doesn’t know its
completion scheduler, so we cannot use the completion scheduler to find
the <code class="sourceCode default">when_all</code> customization.
Instead, we can use an abstract tag type – a so-called <em>domain</em> –
to dispatch to the correct customizations. As long as
<code class="sourceCode default">when_all</code>’s child senders all
share a domain, we can know what set of algorithm customizations to
use.</p>
<p>This paper proposes the addition of a forwarding
<code class="sourceCode default">get_domain</code> query, and that the
domain is used together with the algorithm tag to dispatch to the
correct algorithm implementation.</p>
<p>Additionally, we proposed that the
<code class="sourceCode default">when_all</code> algorithm only accepts
a set of senders when they all share a common domain. Likewise for
<code class="sourceCode default">let_value</code> and
<code class="sourceCode default">let_error</code>, we require that there
is only one possible domain on which their senders may complete.</p>
<h3 data-number="3.1.2" id="late-senderreceiver-connection-time-customization"><span class="header-section-number">3.1.2</span> Late (sender/receiver
connection-time) customization<a href="#late-senderreceiver-connection-time-customization" class="self-link"></a></h3>
<p>As described above, the sender algorithm customization points don’t
have all the information they need to dispatch to the correct algorithm
implementation in all cases. The solution is to look again for a
customization when all the information is available. That happens when
the sender is <code class="sourceCode default">connect</code>-ed to a
receiver.</p>
<p>This paper proposes the addition of a
<code class="sourceCode default">transform_sender</code> customization
point that is called by the
<code class="sourceCode default">connect</code> customization point to
transform a sender prior to connecting it with the receiver. <em>In this
sense, it is precisely analagous to the <a href="https://eel.is/c++draft/expr.await#3.2"><code class="sourceCode default">await_transform</code></a>
customization used by coroutines to transform an expression prior to
<code class="sourceCode default">co_await</code>-ing it.</em></p>
<h3 data-number="3.1.3" id="early-sender-construction-time-customization"><span class="header-section-number">3.1.3</span> Early (sender
construction-time) customization<a href="#early-sender-construction-time-customization" class="self-link"></a></h3>
<p>We can use <code class="sourceCode default">transform_sender</code>
for early customization as well as late. The benefit of doing this is
that only one set of customizations needs be written for each domain,
rather than two (early and late).</p>
<p>This paper proposes that each algorithm constructs a default sender
that implements the default behavior for that algorithm. It then passes
that sender to <code class="sourceCode default">transform_sender</code>
along with the sender’s domain. The result of
<code class="sourceCode default">transform_sender</code> is what the
algorithm returns.</p>
<p>Some algorithms are required to do work eagerly in their default
implementation (<em>e.g.</em>,
<code class="sourceCode default">split</code>,
<code class="sourceCode default">ensure_started</code>). These
algorithms must first create a dummy sender to pass to
<code class="sourceCode default">transform_sender</code>. The “default”
domain, which is used when no other domain has been specified, can
transform these dummy senders and do their eager work in the process.
The same mechanism is also useful to implement customizable sender
algorithms whose default implementation merely lowers to a more
primitive expression (<em>e.g.</em>
<code class="sourceCode default">transfer(s,sch)</code> becomes
<code class="sourceCode default">schedule_from(sch,s)</code>, and
<code class="sourceCode default">transfer_just(sch, ts...)</code>
becomes
<code class="sourceCode default">just(ts...) | transfer(sch)</code>).</p>
<p>To permit third parties to author customizable sender algorithms that
do eager work in their default implementations, the mechanism by which
the default domain finds the default sender transformations shall be
specified.</p>
<h3 data-number="3.1.4" id="decomposable-senders"><span class="header-section-number">3.1.4</span> Decomposable senders<a href="#decomposable-senders" class="self-link"></a></h3>
<p>For the <code class="sourceCode default">transform_sender</code>
customization point to be useful, we need a way to access the
constituent pieces of a sender and re-assemble it from (possibly
transformed) pieces. Senders, like coroutines, generally begin in a
“suspended” state; they merely curry their algorithm’s arguments into a
subsequent call to <code class="sourceCode default">connect</code>.
These “suspended” senders are colloquially known as <em>lazy</em>
senders.</p>
<p>Each lazy sender has an associated algorithm tag, a (possibly empty)
set of auxiliary data and a (possibly empty) set of child senders;
<em>e.g.</em>, the sender returned from
<code class="sourceCode default">then(snd, fun)</code> has
<code class="sourceCode default">then_t</code> as its tag, the set
<code class="sourceCode default">[fun]</code> as its auxiliary data, and
<code class="sourceCode default">[snd]</code> as its set of child
senders, while <code class="sourceCode default">just(42, 3.14)</code>
has <code class="sourceCode default">just_t</code> as its tag,
<code class="sourceCode default">[42, 3.14]</code> as its data set and
<code class="sourceCode default">[]</code> as its child set.</p>
<p>This paper proposes to use structured bindings as the API for
decomposing a lazy sender into its tag, data, and child senders:</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">auto</span><span class="op">&amp;&amp;</span> <span class="op">[</span>tag, data, <span class="op">...</span>children<span class="op">]</span> <span class="op">=</span> sndr;</span></code></pre></div>
<p><span class="citation" data-cites="P1061R5">[<a href="#ref-P1061R5" role="doc-biblioref">P1061R5</a>]</span>, currently in Core wording
review for C++26, permits the declaration of variadic structured
bindings like above, making this syntax very appealing.</p>
<p>Not all senders are required to be decomposable, although all the
“standard” lazy senders shall be. There needs to be a syntactic way to
distinguish between decomposable and non-decomposable senders
(decomposable senders subsuming the
<code class="sourceCode default">sender</code> concept).</p>
<p>There is currently no trait for determining whether a type can be the
initializer of a structured binding. However, EWG has already approved
<span class="citation" data-cites="P2141R1">[<a href="#ref-P2141R1" role="doc-biblioref">P2141R1</a>]</span> for C++26, and with it such a
trait could be built, giving us a simple way to distinguish between
decomposable and non-decomposable senders.</p>
<p>If P2141 is not adopted for C++26, we will need some other syntactic
way to opt-in. One possibility is to require that the sender type’s
nested <code class="sourceCode default">is_sender</code> type shall have
some known, standard tag type as a base class to signify that that
sender type can be decomposed.</p>
<blockquote>
<p><em>Note:</em> After decomposing a sender, it is often desirable to
re-compose it from its modified constituents. No separate API for
reconstituting senders is necessary though. It is enough to construct a
decomposable sender of some arbitrary type and then pass it to
<code class="sourceCode default">transform_sender</code> with the
appropriate domain tag.</p>
</blockquote>
<h2 data-number="3.2" id="summary-of-proposed-changes"><span class="header-section-number">3.2</span> Summary of proposed changes<a href="#summary-of-proposed-changes" class="self-link"></a></h2>
<p>In condensed form, here are the changes this paper is proposing:</p>
<ol>
<li><p>Add a <code class="sourceCode default">default_domain</code> type
for use when no other domain is determinable.</p></li>
<li><p>Add a new <code class="sourceCode default">get_domain(<em>env</em>) -&gt; <em>domain-tag</em></code>
forwarding query.</p></li>
<li><p>Add a new, non-customizable <code class="sourceCode default">transform_sender(<em>domain</em>, <em>sender</em> [, <em>env</em>]) -&gt; <em>sender</em></code>
API. It will be used for both early customization (at sender
construction-time) and late customization (at sender/receiver
connection-time).</p>
<p><em>Early customization:</em></p>
<ul>
<li>called from within each sender algorithm’s customization point
object</li>
<li>replaces the current mechanism of tag-dispatching to a sender
factory function using the completion scheduler as a tag</li>
<li>called without an environment argument</li>
<li><code class="sourceCode default"><em>domain</em></code> is derived
from the sender by trying the following in order:
<ol type="1">
<li><code class="sourceCode default">get_domain(get_env(<em>sender</em>))</code></li>
<li><code class="sourceCode default">get_domain(get_completion_scheduler&lt;<em>completion-tag</em>&gt;(get_env(<em>sender</em>)))</code>,
where <code class="sourceCode default"><em>completion-tag</em></code> is
one of <code class="sourceCode default">set_value_t</code>,
<code class="sourceCode default">set_error_t</code>, or
<code class="sourceCode default">set_stopped_t</code> depending on the
algorithm</li>
<li><code class="sourceCode default">default_domain()</code></li>
</ol></li>
</ul>
<p><em>Late customization:</em></p>
<ul>
<li>called from the <code class="sourceCode default">connect</code>
customization point object before tag-dispatching with
<code class="sourceCode default">connect_t</code> to
<code class="sourceCode default">tag_invoke</code></li>
<li>called with the receiver’s environment</li>
<li><code class="sourceCode default"><em>domain</em></code> is derived
from the receiver by trying the following in order:
<ol type="1">
<li><code class="sourceCode default">get_domain(get_env(<em>receiver</em>))</code></li>
<li><code class="sourceCode default">get_domain(get_scheduler(get_env(<em>receiver</em>)))</code></li>
<li><code class="sourceCode default">default_domain()</code></li>
</ol></li>
</ul>
<p><code class="sourceCode default">transform_sender(<em>domain</em>, <em>sender</em> [, <em>env</em>])</code>
returns the first of these that is well-formed:</p>
<ul>
<li><code class="sourceCode default"><em>domain</em>.transform_sender(<em>sender</em> [, <em>env</em>])</code></li>
<li><code class="sourceCode default">default_domain().transform_sender(<em>sender</em> [, <em>env</em>])</code></li>
<li><code class="sourceCode default"><em>sender</em></code></li>
</ul></li>
<li><p>The standard, “lazy” sender types (i.e., those returned from
sender factory and adaptor functions) return sender types that are
decomposable using structured bindings into its [<em>tag</em>,
<em>data</em>, …<em>children</em>] components.</p></li>
<li><p>A call to the <code class="sourceCode default">when_all</code>
algorithm should be ill-formed unless all of the sender arguments have
the same domain type (as determined for senders above). The resulting
<code class="sourceCode default">when_all</code> sender should publish
that domain via the sender’s environment.</p></li>
<li><p>The <code class="sourceCode default">on(sch, sndr)</code>
algorithm should be specified in terms of
<code class="sourceCode default">transfer</code> so as to pick up any
late customization of the
<code class="sourceCode default">transfer</code> algorithm. (This
amounts to changing
<code class="sourceCode default">schedule(sch)</code> to
<code class="sourceCode default">transfer_just(sch)</code> in
[exec.on]/3.2.2.). Additionally, it should replace the domain in the
receiver’s environment with the domain of
<code class="sourceCode default">sch</code>.</p></li>
<li><p>The sender factories
<code class="sourceCode default">just</code>,
<code class="sourceCode default">just_error</code>, and
<code class="sourceCode default">just_stopped</code> need their tag
types to be specified. Name them
<code class="sourceCode default">just_t</code>,
<code class="sourceCode default">just_error_t</code>, and
<code class="sourceCode default">just_stopped_t</code>.</p></li>
<li><p>In the algorithm
<code class="sourceCode default">let_value(sndr, fun)</code>, if the
predecessor sender <code class="sourceCode default">sndr</code> has a
completion scheduler for
<code class="sourceCode default">set_value</code>, then the receiver
connected to the secondary sender (the one returned from
<code class="sourceCode default">fun</code> when called with
<code class="sourceCode default">sndr</code>’s results) shall expose
that scheduler as the current scheduler of the receiver’s
environment.</p>
<p>In other words, if the predecessor sender
<code class="sourceCode default">sndr</code> completes with values
<code class="sourceCode default">vs...</code>, then the result of
<code class="sourceCode default">fun(vs...)</code> will be connected to
a receiver <code class="sourceCode default">r</code> such that
<code class="sourceCode default">get_scheduler(get_env(r))</code> is
equal to <code class="sourceCode default">get_completion_scheduler&lt;set_value_t&gt;(get_env(sndr))</code>.</p>
<p>The same is true also of the domain query:
<code class="sourceCode default">get_domain(get_env(r))</code> is equal
to the domain of the predecessor sender as computed by the steps in (2)
above.</p>
<p>So for <code class="sourceCode default">let_value</code>, likewise
also for <code class="sourceCode default">let_error</code>, using
<code class="sourceCode default">set_error_t</code> when querying for
the predecessor sender’s completion scheduler.
(<code class="sourceCode default">let_stopped</code> needs no
modification because the nullary function passed to
<code class="sourceCode default">let_stopped</code> can only have a
single return type; hence there is only one secondary sender type and
one domain to consider.)</p></li>
<li><p>The
<code class="sourceCode default">schedule_from(sched, sndr)</code>
algorithm should return a sender
<code class="sourceCode default">s</code> such that
<code class="sourceCode default">get_domain(get_env(s))</code> is equal
to <code class="sourceCode default">get_domain(sched)</code>.</p></li>
<li><p>The following customizable algorithms, whose default
implementations must do work before returning the result sender, will
have their work performed in overloads of
<code class="sourceCode default">default_domain::transform_sender</code>:</p>
<ul>
<li><code class="sourceCode default">split</code></li>
<li><code class="sourceCode default">ensure_started</code></li>
</ul></li>
<li><p>The following customizable algorithms, whose default
implementations are trivially expressed in terms of other more primitive
operations, will be lowered into their primitive forms by overloads of
<code class="sourceCode default">default_domain::transform_sender</code>:</p>
<ul>
<li><code class="sourceCode default">transfer</code></li>
<li><code class="sourceCode default">transfer_just</code></li>
<li><code class="sourceCode default">transfer_when_all</code></li>
<li><code class="sourceCode default">transfer_when_all_with_variant</code></li>
<li><code class="sourceCode default">when_all_with_variant</code></li>
</ul></li>
<li><p>In the algorithm
<code class="sourceCode default">let_value(snd, fun)</code>, all of the
sender types that the input function
<code class="sourceCode default">fun</code> might return must all have
the same domain; otherwise, the call to
<code class="sourceCode default">let_value</code> is ill-formed. The
resulting <code class="sourceCode default">let_value</code> sender will
report that as its domain. Likewise for
<code class="sourceCode default">let_error</code> and
<code class="sourceCode default">let_stopped</code>.</p></li>
<li><p>Sender consuming algorithms
<code class="sourceCode default">start_detached</code> and
<code class="sourceCode default">sync_wait</code> will continue to
dispatch to <code class="sourceCode default">tag_invoke</code>
customizations using the algorithm tag and the input sender’s domain as
tags for the purpose of tag dispatching.</p></li>
</ol>
<h1 data-number="4" id="implementation-experience"><span class="header-section-number">4</span> Implementation Experience<a href="#implementation-experience" class="self-link"></a></h1>
<p><em>Has it been implented?</em> YES. The design changes herein
proposed are implemented in the main branch of <span class="citation" data-cites="stdexecgithub">[<a href="#ref-stdexecgithub" role="doc-biblioref">stdexecgithub</a>]</span>, the reference
implementation. The bulk of the changes including
<code class="sourceCode default">get_domain</code>,
<code class="sourceCode default">transform_sender</code>, and the
changes to <code class="sourceCode default">connect</code> have been
shipping since <a href="https://github.com/NVIDIA/stdexec/commit/0693876c6144479ab5d9bec671751bd32d14e23a">this
commit</a> on August 3, 2023 which changed the
<code class="sourceCode default">static_thread_pool</code> scheduler to
use <code class="sourceCode default">transform_sender</code> to
parallelize the <code class="sourceCode default">bulk</code>
algorithm.</p>
<h1 data-number="5" id="proposed-wording"><span class="header-section-number">5</span> Proposed Wording<a href="#proposed-wording" class="self-link"></a></h1>
<p>TODO</p>
<h1 data-number="6" id="bibliography"><span class="header-section-number">6</span> References<a href="#bibliography" class="self-link"></a></h1>
<div id="refs" class="references csl-bib-body hanging-indent" role="doc-bibliography">
<div id="ref-P1061R5" class="csl-entry" role="doc-biblioentry">
[P1061R5] Barry Revzin, Jonathan Wakely. 2023-05-18. Structured Bindings
can introduce a Pack. <a href="https://wg21.link/p1061r5"><div class="csl-block">https://wg21.link/p1061r5</div></a>
</div>
<div id="ref-P2141R1" class="csl-entry" role="doc-biblioentry">
[P2141R1] Antony Polukhin. 2023-05-03. Aggregates are named tuples. <a href="https://wg21.link/p2141r1"><div class="csl-block">https://wg21.link/p2141r1</div></a>
</div>
<div id="ref-P2300R7" class="csl-entry" role="doc-biblioentry">
[P2300R7] Michał Dominiak, Georgy Evtushenko, Lewis Baker, Lucian Radu
Teodorescu, Lee Howes, Kirk Shoop, Michael Garland, Eric Niebler, and
Bryce Adelstein Lelbach.
<code class="sourceCode default">std::execution</code>. <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html"><div class="csl-block">https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html</div></a>
</div>
<div id="ref-stdexecgithub" class="csl-entry" role="doc-biblioentry">
[stdexecgithub] stdexec. <a href="https://github.com/NVIDIA/stdexec"><div class="csl-block">https://github.com/NVIDIA/stdexec</div></a>
</div>
</div>
</div>
</div>
</body>
</html>
