<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang xml:lang>
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <meta name="author" content="Eric Niebler" />
  <meta name="dcterms.date" content="2024-07-14" />
  <title>Improving diagnostics for sender expressions</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.hanging-indent{margin-left: 1.5em; text-indent: -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; }
    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
      {   }
    @media screen {
    pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
    }
    code span.al { color: #ff0000; font-weight: bold; } /* Alert */
    code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
    code span.at { color: #7d9029; } /* Attribute */
    code span.bn { color: #40a070; } /* BaseN */
    code span.bu { } /* BuiltIn */
    code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
    code span.ch { color: #4070a0; } /* Char */
    code span.cn { color: #880000; } /* Constant */
    code span.co { color: #60a0b0; font-style: italic; } /* Comment */
    code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
    code span.do { color: #ba2121; font-style: italic; } /* Documentation */
    code span.dt { color: #902000; } /* DataType */
    code span.dv { color: #40a070; } /* DecVal */
    code span.er { color: #ff0000; font-weight: bold; } /* Error */
    code span.ex { } /* Extension */
    code span.fl { color: #40a070; } /* Float */
    code span.fu { color: #06287e; } /* Function */
    code span.im { } /* Import */
    code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
    code span.kw { color: #007020; font-weight: bold; } /* Keyword */
    code span.op { color: #666666; } /* Operator */
    code span.ot { color: #007020; } /* Other */
    code span.pp { color: #bc7a00; } /* Preprocessor */
    code span.sc { color: #4070a0; } /* SpecialChar */
    code span.ss { color: #bb6688; } /* SpecialString */
    code span.st { color: #4070a0; } /* String */
    code span.va { color: #19177c; } /* Variable */
    code span.vs { color: #4070a0; } /* VerbatimString */
    code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
  </style>
  <style type="text/css">body {
max-width: 1000px;
text-align: justify;
margin: auto
}
h1.title {
text-align: center
}
p {
text-align: justify
}
li {
text-align: justify
}
blockquote.note {
background-color: #E0E0E0;
padding-left: 15px;
padding-right: 15px;
padding-top: 1px;
padding-bottom: 1px;
}
span.note:before {
content: "[Note: ";
font-style: italic;
}
span.note:after {
content: " -- end note]";
font-style: italic;
}
span.ednote:before {
content: "[Editorial note: ";
font-style: italic;
}
span.ednote:after {
content: " -- end note]";
font-style: italic;
}
span.ednote,
span.ednote * {
color: blue !important;
margin-top: 0em;
margin-bottom: 0em;
}
ins,
ins * {
color: #00A000 !important
}
del,
del * {
color: #A00000 !important
}
div.ins,
div.ins * {
color: #00A000 !important;
text-decoration-line: none;
}
div.del,
div.del * {
color: #A00000 !important;
text-decoration-line: none;
}
dfn {
font-style: italic;
font-weight: bold;
}
code:not(sourceCode) {
white-space: normal;
font-size: 80% !important;
}
ins>code:not(sourceCode) {
white-space: normal;
font-size: 80% !important;
}
div.sourceCode {
margin-left: 20pt !important;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding-left: 10px;
}
td {
text-align: left;
}
th {
background-color: #f2f2f2;
text-align: center;
}</style>
</head>
<body>
<header id="title-block-header">
<h1 class="title">Improving diagnostics for sender expressions</h1>

<div>
<dl>

<dt><strong>Document:</strong></dt>
<dd>
<span class="document">P3164R1</span>
</dd>

<dt><strong>Authors:</strong></dt>
<dd>
<span class="author"><a href="mailto:eric.niebler@gmail.com">Eric Niebler</a></span>
</dd>

<dt><strong>Date:</strong></dt>
<dd>
<span class="date">July 14, 2024</span>
</dd>

<dt><strong>Source:</strong></dt>
<dd>
<span class="source"><a href="https://github.com/ericniebler/wg21/blob/main/P3164/P3164.md">Github</a></span>
</dd>
  
<dt><strong>Issue tracking:</strong></dt>
<dd>
<span class="issues"><a href="https://github.com/ericniebler/wg21/issues">Github</a></span>
</dd>
  
<dt><strong>Project:</strong></dt>
<dd>
<span class="project">ISO/IEC JTC1/SC22/WG21 14882: Programming Language — C++</span>
</dd>
  
<dt><strong>Audience:</strong></dt>
<dd>
<span class="audience">Library Evolution Working Group</span>
</dd>

</dl>
</div>
</header>
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#synopsis">Synopsis</a></li>
<li><a href="#executive-summary">Executive Summary</a></li>
<li><a href="#revision-history">Revision History</a></li>
<li><a href="#improving-early-diagnostics">Improving early diagnostics</a>
<ul>
<li><a href="#problem-description">Problem Description</a></li>
<li><a href="#non-dependent-senders">Non-dependent senders</a></li>
<li><a href="#suggested-solution">Suggested Solution</a></li>
<li><a href="#comparison-table">Comparison Table</a></li>
<li><a href="#design-considerations">Design Considerations</a></li>
</ul></li>
<li><a href="#improving-late-diagnostics">Improving late diagnostics</a>
<ul>
<li><a href="#problem-description-1">Problem description</a></li>
<li><a href="#suggested-solution-1">Suggested solution</a></li>
<li><a href="#another-problem-and-a-solution">Another problem and a solution</a></li>
<li><a href="#comparison-table-1">Comparison Table</a></li>
</ul></li>
<li><a href="#proposed-wording">Proposed Wording</a></li>
<li><a href="#acknowlegments">Acknowlegments</a></li>
</ul>
</nav>
<h2 id="synopsis">Synopsis</h2>
<p>This paper aims to improve the user experience of the sender framework by giving it better diagnostics when used incorrectly.</p>
<p>First, it moves the diagnosis of many invalid sender expression earlier, when the expression is constructed, rather than later when it is connected to a receiver. A trivial change to the sender adaptor algorithms makes it possible for the majority of sender expressions to be type-checked this way, giving the user immediate feedback when they&#39;ve made a mistake.</p>
<p>Second, this paper proposed changes to the <code>transform_completion_signatures</code> alias template to allow it to serve as a meta-exception propagation channel. This helps with late (<code>connect</code>-time) type checking by allowing type computation errors from deeply nested senders to propagate to the API boundary, where they can be reported concisely.</p>
<h2 id="executive-summary">Executive Summary</h2>
<p>Below are the specific changes this paper proposes in order to improve the diagnostics emitted by sender-based codes:</p>
<ol>
<li><p>Define a &quot;non-dependent sender&quot; to be one whose completions are knowable without an environment.</p></li>
<li><p>Extend the awaitable helper concepts to support querying a type whether it is awaitable in an arbitrary coroutine (without knowing the promise type). For example, anything that implements the awaiter interface (<code>await_ready</code>, <code>await_suspend</code>, <code>await_resume</code>) is awaitable in any coroutine, and should function as a non-dependent sender.</p></li>
<li><p>Add support for calling <code>get_completion_signatures</code> without an environment argument.</p></li>
<li><p>Change the definition of the <code>completion_signatures_of_t</code> alias template to support querying a sender&#39;s non-dependent signatures, if such exist.</p></li>
<li><p>Require the sender adaptor algorithms to preserve the &quot;non-dependent sender&quot; property wherever possible.</p></li>
<li><p>Add &quot;Mandates:&quot; paragraphs to the sender adaptor algorithms to require them to hard-error when passed non-dependent senders that fail type-checking.</p></li>
<li><p>Extend the eager type checking of the <code>let_</code> family of algorithms to hard-error if the user passes a lambda that does not return a sender type.</p></li>
<li><p>Change <code>transform_completion_signatures</code> to propagate any intermediate types that are not specializations of the <code>completion_signatures&lt;&gt;</code> class template. For type errors that occur when computing a sender&#39;s completion signatures, sender authors can return a custom type that describes the error and have it automatically propagates through adaptors that use <code>transform_completion_signatures</code>.</p></li>
<li><p>For any algorithm that eagerly <code>connect</code>s a sender (<code>sync_wait</code>, <code>start_detached</code>, <code>ensure_started</code>, <code>split</code>), hard-error (<em>i.e.</em> <code>static_assert</code>) if the sender fails to type-check rather than SFINAE-ing the overload away.</p></li>
</ol>
<h2 id="revision-history">Revision History</h2>
<p><strong>R1</strong>:</p>
<ul>
<li><p>Change the specification of <code>transform_completion_signatures</code> to propagate types that are not specialization of the <code>completion_signatures&lt;&gt;</code> class template. This makes it easier to use an algorithm&#39;s completion signatures to communicate type errors from child senders.</p></li>
<li><p>For the customization points <code>let_value</code>, <code>let_error</code>, and <code>let_stopped</code>, mandate that the callable&#39;s possible return types all satisfy <code>sender</code>.</p></li>
<li><p>Change <em>Requires:</em> to <em>Mandates</em>: for algorithms that eagerly connect senders.</p></li>
</ul>
<p><strong>R0</strong>:</p>
<ul>
<li>Original revision</li>
</ul>
<h2 id="improving-early-diagnostics">Improving early diagnostics</h2>
<h3 id="problem-description">Problem Description</h3>
<p>Type-checking a sender expression involves computing its completion signatures. In the general case, a sender&#39;s completion signatures may depend on the receiver&#39;s execution environment. For example, the sender:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a>read_env(get_stop_token)</span></code></pre></div>
<p>... when connected to a receiver <code>rcvr</code> and started, will fetch the stop token from the receiver&#39;s environment and then pass it back to the receiver, as follows:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="kw">auto</span> st = get_stop_token(get_env(rcvr));</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>set_value(move(rcvr), move(st));</span></code></pre></div>
<p>Without an execution environment, the sender <code>read_env(get_stop_token)</code> doesn&#39;t know how it will complete.</p>
<p>The type of the environment is known rather late, when the sender is connected to a receiver. This is often far from where the sender expression was constructed. If there are type errors in a sender expression, those errors will be diagnosed far from where the error was made, which makes it harder to know the source of the problem.</p>
<p>It would be far preferable to issue diagnostics while <em>constructing</em> the sender rather than waiting until it is connected to a receiver.</p>
<h3 id="non-dependent-senders">Non-dependent senders</h3>
<p>The majority of senders have completions that don&#39;t depend on the receiver&#39;s environment. Consider <code>just(42)</code> -- it will complete with the integer <code>42</code> no matter what receiver it is connected to. If a so-called &quot;non-dependent&quot; sender advertised itself as such, then sender algorithms could eagerly type-check the non-dependent senders they are passed, giving immediate feedback to the developer.</p>
<p>For example, this expression should be immediately rejected:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a>just(<span class="dv">42</span>) | then([](<span class="dt">int</span>* p) { <span class="cf">return</span> *p; })</span></code></pre></div>
<p>The <code>then</code> algorithm can reject <code>just(42)</code> and the above lambda because the arguments don&#39;t match: an integer cannot be passed to a function expecting an <code>int*</code>. The <code>then</code> algorithm can do that type-checking only when it knows the input sender is non-dependent. It couldn&#39;t, for example, do any type-checking if the input sender were <code>read_env(get_stop_token)</code> instead of <code>just(42)</code>.</p>
<p>And in fact, some senders <em>do</em> advertise themselves as non-dependent, although P2300 does not currently do anything with that extra information. A sender can declare its completions signatures with a nested type alias, as follows:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="kw">template</span> &lt;<span class="kw">class</span> T&gt;</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a><span class="kw">struct</span> just_sender {</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a>  T value;</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a>  <span class="kw">using</span> completion_signatures =</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a>    <span class="bu">std::</span>execution<span class="bu">::</span>completion_signatures&lt;</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a>      <span class="bu">std::</span>execution<span class="bu">::</span>set_value_t(T)</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a>    &gt;;</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a>  <span class="co">// ...</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a>};</span></code></pre></div>
<p>Senders whose completions depend on the execution environment cannot declare their completion signatures this way. Instead, they must define a <code>get_completion_signatures</code> customization that takes the environment as an argument.</p>
<p>We can use this extra bit of information to define a <code>non_dependent_sender</code> concept as follows:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="kw">template</span> &lt;<span class="kw">class</span> Sndr&gt;</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="kw">concept</span> non_dependent_sender =</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a>  sender&lt;Sndr&gt; &amp;&amp;</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>  <span class="kw">requires</span> {</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a>    <span class="kw">typename</span> <span class="dt">remove_cvref_t</span>&lt;Sndr&gt;::completion_signatures;</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true"></a>  };</span></code></pre></div>
<p>A sender algorithm can use this concept to conditionally dispatch to code that does eager type-checking.</p>
<h3 id="suggested-solution">Suggested Solution</h3>
<p>The authors suggests that this notion of non-dependent senders be given fuller treatment in P2300. Conditionally defining the nested typedef in generic sender adaptors -- which may adapt either dependent or non-dependent senders -- is awkward and verbose. We suggest instead to support calling <code>get_completion_signatures</code> either with <em>or without</em> an execution environment. This makes it easier for authors of sender adaptors to preserve the &quot;non-dependent&quot; property of the senders it wraps.</p>
<p>We suggest that a similar change be made to the <code>completion_signatures_of_t</code> alias template. When instantiated with only a sender type, it should compute the non-dependent completion signatures, or be ill-formed.</p>
<h3 id="comparison-table">Comparison Table</h3>
<p>Consider the following code, which contains a type error:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="kw">auto</span> work = just(<span class="dv">42</span>)</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a>          | then([](<span class="dt">int</span>* p) { <span class="co">// &lt;&lt;&lt; ERROR here</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a>              <span class="co">//...</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a>            });</span></code></pre></div>
<p>The table below shows the result of compiling this code both before the proposed change and after:</p>
<table style="table-layout: fixed; width: 100%;">
<thead>
<tr>
<th style="width:30%">

<p>Before</p>
</th>
<th>

<p>After</p>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>

<p><em>no error</em></p>
</td>
<td>

<pre>
error: static_assert failed due to requirement &#39;_is_completion_signatures&lt;
ustdex::ERROR&lt;ustdex::WHERE (ustdex::IN_ALGORITHM, ustdex::then_t), ustdex
::WHAT (ustdex::FUNCTION_IS_NOT_CALLABLE), ustdex::WITH_FUNCTION ((lambda 
at hello.cpp:57:18)), ustdex::WITH_ARGUMENTS (int)&gt;&gt;&#39;                     
    static_assert(_is_completion_signatures&lt;_completions&gt;);               
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                 
</pre>

</td>
</tr>
</tbody>
</table>

<p>This error was generated with with <a href="https://github.com/ericniebler/ustdex">µstdex</a> library and Clang-13.</p>
<h3 id="design-considerations">Design Considerations</h3>
<h4 id="why-have-two-ways-for-non-dependent-senders-to-publish-their-completion-signatures">Why have two ways for non-dependent senders to publish their completion signatures?</h4>
<p>The addition of support for a customization of <code>get_completion_signatures</code> that does not take an environment obviates the need to support the use of a nested <code>::completion_signatures</code> alias. In a class, this:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="kw">auto</span> get_completion_signatures() -&gt;</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a>    <span class="bu">std::</span>execution<span class="bu">::</span>completion_signatures&lt;</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true"></a>        <span class="bu">std::</span>execution<span class="bu">::</span>set_value_t(T)</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true"></a>    &gt;;</span></code></pre></div>
<p>... works just as well as this:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="kw">using</span> completion_signatures =</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a>    <span class="bu">std::</span>execution<span class="bu">::</span>completion_signatures&lt;</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a>        <span class="bu">std::</span>execution<span class="bu">::</span>set_value_t(T)</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a>    &gt;;</span></code></pre></div>
<p>Without a doubt, we could simplify the design by dropping support for the latter. This paper suggests retaining it, though. For something like the <code>just_sender</code>, providing type metadata with an alias is more idiomatic and less surprising, in the author&#39;s opinion, than defining a function and putting the metadata in the return type. That is the case for keeping the <code>typename Sndr::completion_signatures</code> form.</p>
<p>The case for adding the <code>sndr.get_completion_signatures()</code> form is that it makes it simpler for sender adaptors such as <code>then_sender</code> to preserve the &quot;non-dependent&quot; property of the senders it adapts. For instance, one could define <code>then_sender</code> like:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true"></a><span class="kw">template</span> &lt;<span class="kw">class</span> Sndr, <span class="kw">class</span> Fun&gt;</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true"></a><span class="kw">struct</span> then_sender {</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true"></a>    Sndr <span class="va">sndr_</span>;</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true"></a>    Fun <span class="va">fun_</span>;</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true"></a></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true"></a>    <span class="kw">template</span> &lt;<span class="kw">class</span>... Env&gt;</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true"></a>    <span class="kw">auto</span> get_completion_signatures(Env&amp;&amp;... env) <span class="at">const</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true"></a>      -&gt; some-computed-type;</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true"></a></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true"></a>    <span class="co">//....</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true"></a>};</span></code></pre></div>
<p>... and with this one member function support both dependent and non-dependent senders while preserving the &quot;non-dependent-ness&quot; of the adapted sender.</p>
<h2 id="improving-late-diagnostics">Improving late diagnostics</h2>
<h3 id="problem-description-1">Problem description</h3>
<p>Experience implementing and using sender-based libraries has taught the author several things:</p>
<ol>
<li><p>Concepts-based constraints on sender algorithms and their inner workings do more harm than good. The diagnostics are generally poor. The constraint failure may happen deep in a sender expression tree, but the diagnostic the user sees is simply: &quot;no overload found&quot;. That gives users exactly zero information about the cause of the error.</p>
<p>Dropping the one-and-only possible overload from the overload set typically doesn&#39;t help users either, most of whom don&#39;t care about SFINAE. What they want are good diagnostics.</p></li>
<li><p>The current specification of the customization points and utilities make type errors SFINAE-able: either a construct type-checks or else it is ill-formed. That makes it very difficult for sender adaptors to propagate type errors from their child senders. In runtime code, we have exceptions to propagate errors to API boundaries. We have no equivalent for type computations, and P2300&#39;s current facilities offer no help.</p></li>
</ol>
<h3 id="suggested-solution-1">Suggested solution</h3>
<p>To address the first issue, the author recommends using <code>static_assert</code>s instead of <code>requires</code> clauses for type errors in sender algorithms.</p>
<p>But what condition should we put in the <code>static_assert</code>? If we use the same predicates that are in the <code>requires</code> clauses, the errors will be little better. Instead of &quot;no overload found&quot;, users will see: &quot;<code>static_assert: sender_to&lt;Sndr, Rcvr&gt; evaluated to false</code>&quot;, followed by a (lengthy and probably truncated) concepts backtrace. Buried in there somewhere <em>may</em> be the cause of the error for those entripid enough to dig for it.</p>
<p>This brings us to the second issue: propagating type errors from deep inside a sender tree evaluation to the API boundary where it can be concisely reported to the user.</p>
<p>The best way the author has found to report &quot;late&quot; (at <code>connect</code> time) type-checking failures is via the sender&#39;s completion signatures. If a type error happens while trying to compute <code>completion_signatures_of_t&lt;Sndr, Env&gt;</code>, instead of making the type ill-formed, it is better for it to name a type that communicates the error to the user.</p>
<p>Algorithms like <code>sync_wait</code> can then <code>static_assert</code> that the result of <code>completion_signatures_of_t&lt;Sndr, Env&gt;</code> is a specialization of the <code>completion_signatures</code> class template. If it instead names a type that is descriptive of the error, the name of that type will appear prominently in the compiler&#39;s (blissfully short) diagnostic.</p>
<p>Consider the following code, which has a type error in it:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a>thread_context ctx; <span class="co">// non-standard extension</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true"></a><span class="kw">auto</span> sch = ctx.get_scheduler();</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true"></a><span class="kw">using</span> <span class="kw">namespace</span> <span class="bu">std::</span>execution;</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true"></a></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true"></a><span class="kw">auto</span> work = read_env(get_delegatee_scheduler)</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true"></a>          | let_value([](<span class="kw">auto</span> sched) {</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true"></a>              <span class="co">// create some work to delegate to the main thread.</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true"></a>              <span class="kw">auto</span> delegated_work =</span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true"></a>                just() | then([] {</span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true"></a>                  <span class="bu">std::</span>puts(<span class="st">&quot;Hello, world!&quot;</span>);</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true"></a>                });</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true"></a></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true"></a>              <span class="co">// launch the work on the delegation scheduler.</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true"></a>              start_on(sched, delegated_work); <span class="co">// &lt;&lt;&lt; ERROR HERE</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true"></a>            });</span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true"></a></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true"></a><span class="kw">auto</span> s = start_on(sch, work);</span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true"></a></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true"></a>sync_wait(s);</span></code></pre></div>
<p>The error in the code above is that the lambda passed to <code>let_value</code> must return a sender. The error cannot be caught early because this is a dependent sender: the type of the delegation scheduler isn&#39;t known until we pass the sender to <code>sync_wait</code>.</p>
<p>Compiling this with the <a href="https://github.com/ericniebler/ustdex">µstdex</a> library, which uses the suggested technique of propagating descriptive type errors via the completion signatures, results in the following diagnostic:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a>[<span class="ex">build</span>] /home/eniebler/Code/ustdex/include/ustdex/detail/sync_wait.hpp:139:7: error: static_assert failed due to requirement <span class="st">&#39;_is_completion_signatures&lt;</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true"></a><span class="st">ustdex::ERROR&lt;ustdex::WHERE (ustdex::IN_ALGORITHM, ustdex::let_value_t), ustdex::WHAT (ustdex::FUNCTION_MUST_RETURN_A_SENDER), ustdex::WITH_FUNCTION ((l</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true"></a><span class="st">ambda at /home/eniebler/Code/ustdex/examples/scratch.cpp:64:25))&gt;&gt;&#39;</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true"></a>[<span class="ex">build</span>]       static_assert(_is_completion_signatures<span class="op">&lt;</span>_completions<span class="op">&gt;</span>);</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true"></a>[<span class="ex">build</span>]       ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true"></a>[<span class="ex">build</span>] /home/eniebler/Code/ustdex/examples/scratch.cpp:74:12: note: in instantiation of function template specialization <span class="st">&#39;ustdex::sync_wait_t::operator</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true"></a><span class="st">()&lt;ustdex::start_on_t::_sndr_t&lt;ustdex::run_loop::_scheduler, ustdex::_let&lt;ustdex::_value&gt;::_sndr_t&lt;ustdex::read_env_t::_sndr_t&lt;ustdex::get_delegatee_sch</span></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true"></a><span class="st">eduler_t&gt;, (lambda at /home/eniebler/Code/ustdex/examples/scratch.cpp:64:25)&gt;&gt; &amp;&gt;&#39;</span> requested here</span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true"></a>[<span class="ex">build</span>]   sync_wait(s);</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true"></a>[<span class="ex">build</span>]            ^</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true"></a>[<span class="ex">build</span>] 1 error generated.</span></code></pre></div>
<p>This is the complete diagnostic. As you can see, the source of the error has been propagated out of the sender expression tree and reported at the API boundary, in <code>sync_wait</code>. The diagnostic contains only the information the user needs to fix the problem.</p>
<h3 id="another-problem-and-a-solution">Another problem and a solution</h3>
<p>For the authors of sender adaptor algorithms, this meta-error propagation technique presents a neigh insurmountable metaprogramming challenge. Computing completion signatures is hard enough. But now a child sender&#39;s completion signatures may not actually be completion signatures! They could instead be an error that the algorithm author must propagate in <em>their</em> completion signatures, or else lose the information about the root cause.</p>
<p>P2300 recognizes that manipulating completion signatures at compile time is taxing. It provides a utility to help: <code>transform_completion_signatures</code>. Given a set of completion signatures and some alias templates, it applies transformations to the set, resulting in a new set of completion signatures. Sender adaptor authors can use <code>transform_completion_signatures</code> to adapt the child sender&#39;s completions.</p>
<p>With a few small changes, <code>transform_completions_signatures</code> can be made to automatically propagate any intermediate types that might represent errors, saving users the trouble of doing so manually. This paper proposes those design changes.</p>
<h3 id="comparison-table-1">Comparison Table</h3>
<p>Consider the following use of <code>transform_completion_signatures</code>:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode c++"><code class="sourceCode cpp"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true"></a><span class="kw">struct</span> ERROR_TOO_MANY_VALUES {};</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true"></a><span class="kw">template</span> &lt;<span class="kw">class</span>... Values&gt;</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true"></a><span class="kw">using</span> _value_completions_t = <span class="bu">std::</span>conditional_t&lt;</span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true"></a>                                 <span class="kw">sizeof</span>...(Values) &gt; <span class="dv">1</span>,</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true"></a>                                 ERROR_TOO_MANY_VALUES,</span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true"></a>                                 completion_signatures&lt;<span class="dt">set_value_t</span>(Values...)&gt;</span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true"></a>                              &gt;;</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true"></a></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true"></a><span class="co">// For a given sender and environment, check that the value completions</span></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true"></a><span class="co">// never send more than one value.</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true"></a><span class="kw">template</span> &lt;<span class="kw">class</span> Sndr, <span class="kw">class</span> Env&gt;</span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true"></a><span class="kw">using</span> _checked_completions = transform_completion_signatures&lt;</span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true"></a>                                 <span class="dt">completion_signatures_of_t</span>&lt;Sndr, Env&gt;,</span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true"></a>                                 completion_signatures&lt;&gt;,</span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true"></a>                                 _value_completions_t</span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true"></a>                              &gt;;</span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true"></a></span></code></pre></div>
<table style="table-layout: fixed; width: 100%;">
<thead>
<tr>
<th style="width:30%">

<p>Type computation</p>
</th>
<th style="width:10%">

<p>Result Before</p>
</th>
<th>

<p>Result After</p>
</th>
</tr>
</thead>

<tbody>
<tr>
<td>

<pre>
using S =
   decltype(just(1, 2, 3));

using T =
  _checked_completions&lt;S, empty_env&gt;;
</pre>

</td>
<td>

<p><em>hard error</em></p>
</td>
<td>

<p><code>T</code> is <code>ERROR_TOO_MANY_VALUES</code></p>
</td>
</tr>

<tr>
<td>

<pre>
using S =
   decltype(read_env(get_scheduler));

using T =
  _checked_completions&lt;S, empty_env&gt;;
</pre>

</td>
<td>

<p><em>hard error</em></p>
</td>
<td>

<p><code>T</code> is:</p>
<pre>
ustdex::ERROR&lt;ustdex::WHERE (ustdex::IN_ALGORITHM, ustdex::read_env_t),
ustdex::WHAT (ustdex::THE_CURRENT_ENVIRONMENT_LACKS_THIS_QUERY), ustdex
::WITH_QUERY (ustdex::get_scheduler_t), ustdex::WITH_ENVIRONMENT (ustde
x::env&lt;&gt;&gt;)
</pre>

<p>(with the <a href="https://github.com/ericniebler/ustdex">µstdex</a> library)</p>
</td>
</tr>
</tbody>
</table>

<p>In the first case, the type error happens in the <code>_value_completions_t</code> alias template. In the second case, the type error happens when trying to ask the <code>read_env(get_scheduler)</code> sender what its completions are when used with <code>empty_env</code>. That&#39;s an error because the <code>empty_env</code> does not have a value for the <code>get_scheduler</code> query. In both cases, the error gets propagated by <code>transform_completion_signatures</code> after the proposed change.</p>
<h2 id="proposed-wording">Proposed Wording</h2>
<p><span class="ednote">This proposed wording is based on <a href="https://wg21.link/P2300R9.html">P2300R9</a>.</span></p>
<p><span class="ednote">Change [async.ops]/13 as follows:</span></p>
<blockquote>

<ol start="13">
<li>A completion signature is a function type that describes a completion operation. An asychronous operation has a finite set of possible completion signatures corresponding to the completion operations that the asynchronous operation potentially evaluates ([basic.def.odr]). For a completion function <code>set</code>, receiver <code>rcvr</code>, and pack of arguments <code>args</code>, let <code>c</code> be the completion operation <code>set(rcvr, args...)</code>, and let <code>F</code> be the function type <code>decltype(auto(set))(decltype((args))...)</code>. A completion signature <code>Sig</code> is associated with <code>c</code> if and only if <code>MATCHING-SIG(Sig, F)</code> is <code>true</code> ([exec.general]). Together, a sender type and an environment type <code>Env</code> determine the set of completion signatures of an asynchronous operation that results from connecting the sender with a receiver that has an environment of type <code>Env</code>. <span class="note">The type of the receiver does not affect an asychronous operation’s completion signatures, only the type of the receiver’s environment.</span> <ins>A sender type whose completion signatures are knowable independent of an execution environment is known as a <dfn>non-dependent sender</dfn>.</li>
</ol>
</blockquote>

<p><span class="ednote">Change [exec.syn] as follows:</span></p>
<blockquote><pre style="white-space: pre-wrap; font-size: 85%;">
...

<p>template&lt;class Sndr, class<ins>...</ins> Env <del>= empty_env</del>&gt; concept sender_in = <em>see below</em>; ...</p>
<p>template&lt;class Sndr, class<ins>...</ins> Env <del>= empty_env</del>&gt; requires sender_in&lt;Sndr, Env<ins>...</ins>&gt; using completion_signatures_of_t = call-result-t&lt;get_completion_signatures_t, Sndr, Env<ins>...</ins>&gt;; ... </pre></blockquote></p>
<p><span class="ednote">Change [exec.snd.concepts] as follows:</span></p>
<blockquote><pre style="white-space: pre-wrap; font-size: 85%;">
template&lt;class Sndr, class<ins>...</ins> Env <del>= empty_env</del>&gt;
  concept sender_in =
    sender&lt;Sndr&gt; &amp;&amp;
    <ins>(sizeof...(Env) &lt;= 1)</ins>
    <ins>(</ins>queryable&lt;Env&gt;<ins> &amp;&amp;...)</ins> &amp;&amp;
    requires (Sndr&amp;&amp; sndr, Env&amp;&amp;<ins>...</ins> env) {
      { get_completion_signatures(
           std::forward&lt;Sndr&gt;(sndr), std::forward&lt;Env&gt;(env)<ins>...</ins>) }
        -&gt; <em>valid-completion-signatures</em>;
    };
</pre></blockquote>

<p><span class="ednote">this subtly changes the meaning of <code>sender_in&lt;Sndr&gt;</code>. Before the change, it tests whether a type is a sender when used specifically with the environment <code>empty_env</code>. After the change, it tests whether a type is a non-dependent sender. This is a stronger assertion to make about the type; it says that this type is a sender <em>regardless of the environment</em>. One can still get the old behavior with <code>sender_in&lt;Sndr, empty_env&gt;</code>.</span></p>
<p><span class="ednote">Change [exec.awaitables] as follows:</span></p>
<blockquote>

<ol>
<li><p>The sender concepts recognize awaitables as senders. For this clause ([exec]), an <em><strong>awaitable</strong></em> is an expression that would be well-formed as the operand of a <code>co_await</code> expression within a given context.</p></li>
<li><p>For a subexpression <code>c</code>, let <code>GET-AWAITER(c, p)</code> be expression-equivalent to the series of transformations and conversions applied to <code>c</code> as the operand of an <em>await-expression</em> in a coroutine, resulting in lvalue <code>e</code> as described by [expr.await]/3.2-4, where <code>p</code> is an lvalue referring to the coroutine’s promise type, <code>Promise</code>. This includes the invocation of the promise type’s <code>await_transform</code> member if any, the invocation of the <code>operator co_await</code> picked by overload resolution if any, and any necessary implicit conversions and materializations. <ins>Let <code>GET-AWAITER(c)</code> be expression-equivalent to <code>GET-AWAITER(c, q)</code> where <code>q</code> is an lvalue of an unspecified empty class type <em><code>none-such</code></em> that lacks an <code>await_transform</code> member, and where <code>coroutine_handle&lt;none-such&gt;</code> behaves as <code>coroutine_handle&lt;void&gt;</code>.</ins></p></li>
<li><p>Let <em><code>is-awaitable</code></em> be the following exposition-only concept:</p>
 <pre style="white-space: pre-wrap; font-size: 85%;">
     template&lt;class T&gt;
     concept <em>await-suspend-result</em> = <em>see below</em>;

     template&lt;class A, class<ins>...</ins> Promise&gt;
     concept <em>is-awaiter</em> = <em>// exposition only</em>
         requires (A&amp; a, coroutine_handle&lt;Promise<ins>...</ins>&gt; h) {
             a.await_ready() ? 1 : 0;
             { a.await_suspend(h) } -&gt; <em>await-suspend-result</em>;
             a.await_resume();
         };

     template&lt;class C, class<ins>...</ins> Promise&gt;
     concept <em>is-awaitable</em> =
         requires (C (*fc)() noexcept, Promise&amp;<ins>...</ins> p) {
             { GET-AWAITER(fc(), p<ins>...</ins>) } -&gt; is-awaiter&lt;Promise<ins>...</ins>&gt;;
         };
 </pre>

<p><code>await-suspend-result&lt;T&gt;</code> is <code>true</code> if and only if one of the following is <code>true</code>:</p>
<ul>
<li><code>T</code> is <code>void</code>, or</li>
<li><code>T</code> is <code>bool</code>, or</li>
<li><code>T</code> is a specialization of <code>coroutine_handle</code>.</li>
</ul></li>
<li><p>For a subexpression <code>c</code> such that <code>decltype((c))</code> is type <code>C</code>, and an lvalue <code>p</code> of type <code>Promise</code>, <code>await-result-type&lt;C, Promise&gt;</code> denotes the type <code>decltype(GET-AWAITER(c, p).await_resume())</code> <ins>, and <code>await-result-type&lt;C&gt;</code> denotes the type <code>decltype(GET-AWAITER(c).await_resume())</code></ins>.</p></li>
</ol>
</blockquote>

<p><span class="ednote">Change [exec.getcomplsigs] as follows:</span></p>
<blockquote>

<ol>
<li><p><code>get_completion_signatures</code> is a customization point object. Let <code>sndr</code> be an expression such that <code>decltype((sndr))</code> is <code>Sndr</code>, and let <code>env</code> be <del>an expression such that <code>decltype((env))</code> is <code>Env</code></del> <ins>a pack of zero or one expressions</ins>. Then <code>get_completion_aignatures(sndr, env<ins><code>...</code></ins>)</code> is expression-equivalent to:</p>
<ol>
<li><ins><code>remove_cvref_t&lt;Sndr&gt;::completion_signatures{}</code> if that expression is well-formed,</ins></li>
</ol>
 <!-- -->

<ol start="2">
<li>Otherwise, <code>decltype(sndr.get_completion_signatures(env<ins><code>...</code></ins>)){}</code> if that expression is well-formed,</li>
</ol>
 <!-- -->

<ol start="2">
<li><p><del>Otherwise, <code>remove_cvref_t&lt;Sndr&gt;::completion_signatures{}</code> if that expression is well-formed,</del></p></li>
<li><p>Otherwise, if <code><em>is-awaitable</em>&lt;Sndr, <em>env-promise</em>&lt;Env&gt;<ins><code>...</code></ins>&gt;</code> is <code>true</code>, then:</p>
 <pre style="white-space: pre-wrap;">
     completion_signatures&lt;
         <em>SET-VALUE-SIG</em>(<em>await-result-type</em>&lt;Sndr, <em>env-promise</em>&lt;Env&gt;<ins>...</ins>&gt;), // see [exec.snd.concepts]
         set_error_t(exception_ptr),
         set_stopped_t()&gt;{}
 </pre>
</li>
<li><p>Otherwise, <code>get_completion_signatures(sndr, env<ins><code>...</code></ins>)</code> is ill-formed.</p></li>
</ol></li>
</ol>
<div class="ins">

<ol start="3">
<li><p>If <code>get_completion_signatures(sndr)</code> is well-formed and its type denotes a specialization of the <code>completion_signatures</code> class template, then <code>Sndr</code> is a non-dependent sender type ([async.ops]).</p></li>
<li><p>Given a type <code>Env</code>, if <code>completion_signatures_of_t&lt;Sndr&gt;</code> and <code>completion_signatures_of_t&lt;Sndr, Env&gt;</code> are both well-formed and denote instantiations of the <code>completion_signatures</code> class template, they shall denote the same set of completion signatures, with type equality determined with <code><em>MATCHING-SIG</em></code> ([exec.general]).</p></li>
</ol>
</div>

<ol start="6">
<li>Let <code>rcvr</code> be an rvalue receiver of type <code>Rcvr</code>....</li>
</ol>
</blockquote>

<p><span class="ednote">To [exec.adapt.general], add a paragraph (8) as follows:</span></p>
<blockquote><div class="ins">

<ol start="8">
<li>Unless otherwise specified, an adaptor whose child senders are all non-dependent ([async.ops]) is itself non-dependent. This requirement applies to any function that is selected by the implementation of the sender adaptor.</li>
</ol>
</div></blockquote>

<p><span class="ednote">Change [exec.then] as follows:</span></p>
<blockquote>

<ol start="2">
<li>The names <code>then</code>, <code>upon_error</code>, and <code>upon_stopped</code> denote customization point objects. <ins>For <code>then</code>, <code>upon_error</code>, and <code>upon_stopped</code>, let <em><code>set-cpo</code></em> be <code>set_value</code>, <code>set_error</code>, and <code>set_stopped</code> respectively.</ins> Let the expression <em><code>then-cpo</code></em> be one of <code>then</code>, <code>upon_error</code>, or <code>upon_stopped</code>. For subexpressions <code>sndr</code> and <code>f</code>, let <code>Sndr</code> be <code>decltype((sndr))</code> and let <code>F</code> be the decayed type of <code>f</code>. If <code>Sndr</code> does not satisfy sender, or <code>F</code> does not satisfy <em><code>movable-value</code></em>, <code><em>then-cpo</em>(sndr, f)</code> is ill-formed.</li>
</ol>
<div class="ins">

<ol start="3">
<li>Otherwise, let <em><code>invoke-result</code></em> be an alias template such that <code><em>invoke-result</em>&lt;Ts...&gt;</code> denotes the type <code>invoke_result_t&lt;F, Ts...&gt;</code>. If <code>sender_in&lt;Sndr&gt;</code> is <code>true</code> and <code><em>gather-signatures</em>&lt;<em>decayed-typeof</em>&lt;<em>set-cpo</em>&gt;, completion_signatures_of_t&lt;Sndr&gt;, <em>invoke-result</em>, <em>type-list</em>&gt;</code> is ill-formed, the program is ill-formed.</li>
</ol>
</div>

<ol start="4">
<li><p>Otherwise, the expression <code><em>then-cpo</em>(sndr, f)</code> is expression-equivalent to:.....</span></p></li>
<li><p><del>For <code>then</code>, <code>upon_error</code>, and <code>upon_stopped</code>, let <em><code>set-cpo</code></em> be <code>set_value</code>, <code>set_error</code>, and <code>set_stopped</code> respectively.</del> The exposition-only class template <em><code>impls-for</code></em> ([exec.snd.general]) is specialized for <em><code>then-cpo</code></em> as follows:....</p></li>
</ol>
<div>
</blockquote>

<p><span class="ednote">Change [exec.let] by inserting a new paragraph between (4) and (5) as follows:</span></p>
<blockquote>
<div class="ins">

<ol start="5">
<li>Let <em><code>invoke-result</code></em> be an alias template such that <code><em>invoke-result</em>&lt;Ts...&gt;</code> denotes the type <code>invoke_result_t&lt;F, Ts...&gt;</code>. If <code>sender_in&lt;Sndr&gt;</code> is <code>true</code> and <code><em>gather-signatures</em>&lt;<em>decayed-typeof</em>&lt;<em>set-cpo</em>&gt;, completion_signatures_of_t&lt;Sndr&gt;, <em>invoke-result</em>, <em>type-list</em>&gt;</code> is ill-formed, or if any of the types in the resulting type list fail to satisfy <code>sender</code>, the program is ill-formed.</li>
</ol>
</div>
</blockquote>

<p><span class="ednote">Change [exec.bulk] by inserting a new paragraph between (3) and (4) as follows:</span></p>
<blockquote>
<div class="ins">

<ol start="4">
<li>Let <em><code>invoke-result</code></em> be an alias template such that <code><em>invoke-result</em>&lt;Ts...&gt;</code> denotes the type <code>invoke_result_t&lt;F, Shape, Ts...&gt;</code>. If <code>sender_in&lt;Sndr&gt;</code> is <code>true</code> and <code><em>gather-signatures</em>&lt;<em>decayed-typeof</em>&lt;<em>set-cpo</em>&gt;, completion_signatures_of_t&lt;Sndr&gt;, <em>invoke-result</em>, <em>type-list</em>&gt;</code> is ill-formed, the program is ill-formed.</li>
</ol>
</div>
</blockquote>

<p><span class="ednote">Change [exec.split] as follows:</span></p>
<blockquote>

<ol start="3">
<li>The names <code>split</code> and <code>ensure_started</code> denote customization point objects. Let the expression <em><code>shared-cpo</code></em> be one of <code>split</code> or <code>ensure_started</code>. For a subexpression <code>sndr</code>, let <code>Sndr</code> be <code>decltype((sndr))</code>. If <code>sender_in&lt;Sndr, <em>shared-env</em>&gt;</code> is <code>false</code>, <del><code><em>shared-cpo</em>(sndr)</code></del> <ins>the program</ins> is ill-formed.</li>
</ol>
</blockquote>

<p><span class="ednote">Change [exec.start.detached] as follows:</span></p>
<blockquote>

<ol>
<li><p><code>start_detached</code> eagerly starts a sender without the caller needing to manage the lifetimes of any objects.</p></li>
<li><p>The name <code>start_detached</code> denotes a customization point object. For a subexpression <code>sndr</code>, let <code>Sndr</code> be <code>decltype((sndr))</code>. If <code>sender_in&lt;Sndr, empty_env&gt;</code> is <code>false</code>, <del><code>start_detached</code></del> <ins>the program</ins> is ill-formed. Otherwise ...</p></li>
</ol>
</blockquote>

<p><span class="ednote">Change [exec.sync.wait] as follows:</span></p>
<blockquote>

<ol start="4">
<li>The name <code>this_thread::sync_wait</code> denotes a customization point object. For a subexpression <code>sndr</code>, let <code>Sndr</code> be <code>decltype((sndr))</code>. If <code>sender_in&lt;Sndr, <i>sync-wait-env</i>&gt;</code> is <code>false</code>, the <del>expression <code>this_thread::sync_wait(sndr)</code></del> <ins>the program</ins> is ill-formed. Otherwise, <del>it</del> <ins>the expression <code>this_thread::sync_wait(sndr)</code></ins> is expression-equivalent to ...</li>
</ol>
</blockquote>

<p><span class="ednote">Change [exec.utils.tfxcmplsigs]</span> as follows:</span></p>
<blockquote>

<ol start="4">
<li><pre highlight="c++">
namespace std::execution {
  template&lt;<del><em>valid-completion-signatures</em></del><ins>class</ins> InputSignatures,
           <del><em>valid-completion-signatures</em></del><ins>class</ins> AdditionalSignatures = completion_signatures&lt;&gt;,
           template&lt;class...&gt; class SetValue = <em>default-set-value</em>,
           template&lt;class&gt; class SetError = <em>default-set-error</em>,
           <del><em>valid-completion-signatures</em></del><ins>class</ins> SetStopped = completion_signatures&lt;set_stopped_t()&gt;&gt;
  using transform_completion_signatures = completion_signatures&lt;<em>see below</em>&gt;;
}
</pre>

<div class="del">

<ol>
<li><p><code>SetValue</code> shall name an alias template such that for any template parameter pack <code>As...</code>, the type <code>SetValue&lt;As...&gt;</code> is either ill-formed or else <code><em>valid-completion-signatures</em>&lt;SetValue&lt;As...&gt;&gt;</code> is satisfied.</p></li>
<li><p><code>SetError</code> shall name an alias template such that for any type <code>Err</code>, <code>SetError&lt;Err&gt;</code> is either ill-formed or else <code><em>valid-completion-signatures</em>&lt;SetError&lt;Err&gt;&gt;</code> is satisfied.</p></li>
</ol>
</div>

<p>Then:</p>
<div class="ins">

<ol>
<li>If <code><em>valid-completion-signatures</em>&lt;E&gt;</code> is <code>false</code> where <code>E</code> is one of <code>InputSignatures</code>, <code>AdditionalSignatures</code>, or <code>SetStopped</code>, <code>transform_completion_signatures&lt;InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped&gt;</code> denotes the type <code>E</code>. <span class="note">If there are multiple types that fail to satisfy <em><code>valid-completion-signatures</code></em>, it is unspecified which is chosen.</span></li>
</ol>
</div>

<ol start="2">
<li><p>Let <code>Vs<del>...</del></code> be a pack of the types in the <em><code>type-list</code></em> named by <code><em>gather-signatures</em>&lt;set_value_t, InputSignatures, SetValue, <em>type-list</em>&gt;</code>.</p></li>
<li><p>Let <code>Es<del>...</del></code> be a pack of the types in the <em><code>type-list</code></em> named by <code><em>gather-signatures</em>&lt;set_error_t, InputSignatures, type_identity_t, <em>error-list</em>&gt;</code>, where <em><code>error-list</code></em> is an alias template such that <code><em>error-list</em>&lt;Ts...&gt;</code> names <code><em>type-list</em>&lt;SetError&lt;Ts&gt;...&gt;</code>.</p></li>
<li><p>Let <code>Ss</code> name the type <code>completion_signatures&lt;&gt;</code> if <code><em>gather-signatures</em>&lt;set_stopped_t, InputSignatures, <em>type-list</em>, <em>type-list</em>&gt;</code> is an alias for the type <code><em>type-list</em>&lt;&gt;</code>; otherwise, <code>SetStopped</code>.</p></li>
</ol>
<p>Then:</p>
<ol start="5">
<li>If any of the types in <code>Vs</code> or <code>Es</code> are ill-formed, then <code>transform_completion_signatures&lt;InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped&gt;</code> is ill-formed,<p></p></li>
</ol>
<div class="ins">

<ol start="6">
<li>Otherwise, if any type <code>E</code> from set of types in <code>Vs</code> and <code>Es</code> fails to satisfy <em><code>valid-completion-signatures</code></em>, then <code>transform_completion_signatures&lt;InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped&gt;</code> denotes the type <code>E</code>. <span class="note">If more than one type in <code>Vs</code> and <code>Es</code> fail to satisfy <em><code>valid-completion-signatures</code></em>, it is unspecified which is chosen.</span><p></p></li>
</ol>
</div>

<ol start="7">
<li>Otherwise, <code>transform_completion_signatures&lt;InputSignatures, AdditionalSignatures, SetValue, SetError, SetStopped&gt;</code> names the type <code>completion_signatures&lt;Sigs...&gt;</code> where <code>Sigs...</code> is the unique set of types in all the template arguments of all the <code>completion_signatures</code> specializations in <code>[AdditionalSignatures, Vs..., Es..., Ss]</code>. <ins>For the purpose of uniqueness, type equality is determined with <em><code>MATCHING-SIG</code></em> ([exec.general]).</ins></li>
</ol></li>
</ol>
</blockquote>

<h2 id="acknowlegments">Acknowlegments</h2>
<p>We owe our thanks to Ville Voutilainen who first noticed that most sender expressions could be type-checked eagerly but are not by P2300R8.</p>
</body>
</html>
