<!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>p2921r0&colon; Exploring std&colon;&colon;expected based API alternatives for buffer&lowbar;queue</title>
        <style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only] {
	display: none;
}

</style>
        
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
                font-size: 14px;
                line-height: 1.6;
            }
        </style>
        <style>
.task-list-item {
    list-style-type: none;
}

.task-list-item-checkbox {
    margin-left: -20px;
    vertical-align: middle;
    pointer-events: none;
}
</style>
        
    </head>
    <body class="vscode-body vscode-light">
        <table>
<thead>
<tr>
<th>Document Number:</th>
<th>p2921r0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Date:</td>
<td>2023-06-16</td>
</tr>
<tr>
<td>Target:</td>
<td>LEWG</td>
</tr>
<tr>
<td>Reply to:</td>
<td><a href="mailto:gorn@microsoft.com">gorn@microsoft.com</a></td>
</tr>
</tbody>
</table>
<h1 id="p2921r0-exploring-stdexpected-based-api-alternatives-for-buffer_queue">p2921r0: Exploring std::expected based API alternatives for buffer_queue</h1>
<p>This paper explores extending interface of Buffered Queue (<a href="https://wg21.link/p0260r6">https://wg21.link/p0260r6</a>) with APIs
returning <code>std::expected</code> as opposed to taking <code>std::error_code</code>.</p>
<h1 id="summary-of-the-relevant-buffer_queue-apis">Summary of the relevant buffer_queue APIs</h1>
<pre><code class="language-c++"><span class="hljs-keyword">enum class</span> <span class="hljs-title class_">conqueue_errc</span> { success = <span class="hljs-number">0</span>, empty, full, closed };
<span class="hljs-keyword">class</span> <span class="hljs-title class_">conqueue_error</span> : runtime_exception { ... };
... make_error_code, make_error_condition, conqueue_category, ...
</code></pre>
<pre><code class="language-c++"><span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>, <span class="hljs-keyword">class</span> <span class="hljs-title class_">Alloc</span> = std::allocator&lt;T&gt;&gt;
<span class="hljs-keyword">class</span> buffer_queue {
<span class="hljs-keyword">public</span>:
   ...
  <span class="hljs-comment">// modifiers</span>
  <span class="hljs-type">void</span> <span class="hljs-built_in">close</span>() <span class="hljs-keyword">noexcept</span>;

  <span class="hljs-function">T <span class="hljs-title">pop</span><span class="hljs-params">()</span></span>;
  <span class="hljs-function">std::optional&lt;T&gt; <span class="hljs-title">pop</span><span class="hljs-params">(std::error_code&amp; ec)</span></span>;
  <span class="hljs-function">std::optional&lt;T&gt; <span class="hljs-title">try_pop</span><span class="hljs-params">(std::error_code&amp; ec)</span></span>;

  <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp; x)</span></span>;
  <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp; x, error_code&amp; ec)</span></span>;
  <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">try_push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp; x, error_code&amp; ec)</span></span>;

  <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(T&amp;&amp; x)</span></span>;
  <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">push</span><span class="hljs-params">(T&amp;&amp; x, error_code&amp; ec)</span></span>;
  <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp; x, error_code&amp; ec)</span></span>;
};
</code></pre>
<p><code>T pop()</code> - throws if queue is empty and closed, blocks if the queue is empty</p>
<p><code>void push(T)</code> - throws if queue is closed, blocks if the queue is full</p>
<p>APIs that take error_code parameter, would set <code>error_code</code> parameter to <code>conqueue_errc::closed)</code> instead of throwing an exception.</p>
<p>Additionally, <code>try_push</code> and <code>try_pop</code> APIs never block and would report errors <code>conqueue_errc::full</code> and <code>conqueue_errc::empty</code> respectively.</p>
<h2 id="exploring-alternative-apis-shapes">Exploring alternative APIs shapes</h2>
<p>After discussion in LEWG in Varna 2023, we were asked to
explore restyling of the APIs that take <code>std::error_code</code>
parameter with <code>std::expected</code> returning ones, which we dutifully
did and present the results below.</p>
<h1 id="encoding-expected-variant-with-stdnothrow-parameter">Encoding expected variant with std::nothrow parameter</h1>
<p>To distinguish between throwing and expected returning flavors, we use
<code>std::nothrow_t</code> parameter for disambiguation (alternative was to change the name to, say, <code>push_nothrow</code>, which we find less aesthetically appealing).</p>
<pre><code class="language-c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span></span>;
<span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;, error_code&amp; ec)</span></span>;
  <span class="hljs-function">vs
<span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">auto</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;, <span class="hljs-type">nothrow_t</span>)</span> -&gt; expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt;</span>;
</code></pre>
<table>
  <tr>
    <th>non-throwing: status-quo</th>
    <th>non-throwing: expected with nothrow</th>
  </tr>
  <tr>
    <td>
<pre>
std::error_code ec;
if (q.push(5, ec))
  return;
println("got {}", ec);
</pre>
    </td>
    <td>
<pre>
if (auto result = q.push(5, nothrow))
  return;
else
  println("got {}", result.error());
</pre>
    </td>
  </tr>
</table>
<p>The benefit of expected here is that we don't have to declare ec before calling the API.</p>
<h1 id="status-quo-vs-only-expected-based-apis">Status quo vs only expected based APIs</h1>
<p>Another alternative suggested to us by some LEWG members
was to eliminate the throwing versions altogether and
replace them with the expected returning ones.</p>
<pre><code class="language-c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span></span>;
<span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;, error_code&amp; ec)</span></span>;
  <span class="hljs-function">vs
<span class="hljs-keyword">auto</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span> -&gt; expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt;</span>;
</code></pre>
<table>
  <tr>
    <th>non-throwing: status-quo</th>
    <th>non-throwing: expected only</th>
  </tr>
  <tr>
    <td>
<pre>
std::error_code ec;
if (q.push(5, ec))
  return;
println("got {}", ec);
</pre>
    </td>
    <td>
<pre>
if (auto result = q.push(5))
  return;
else
  println("got {}", result.error());
</pre>
    </td>
  </tr>
</table>
<table>
  <tr>
    <th>throwing: status quo</th>
    <th>throwing: expected only</th>
  </tr>
  <tr>
    <td>
<pre>
q.push(5);
  ...
catch(const conqueue_error& e)
</pre>
    </td>
    <td>
<pre>
q.push(5).or_else([](auto code) {
  throw conqueue_error(code);
});
  ...
catch(const conqueue_error& e)
</pre>
    </td>
  </tr>
</table>
<table>
  <tr>
    <th>throwing: status quo</th>
    <th>throwing: expected only (awkward exception type)</th>
  </tr>
  <tr>
    <td>
<pre>q.push(5);
  ...
catch(const conqueue_error& e)
</pre>
    </td>
    <td>
<pre>
q.push(5).value();
...
catch(const 
   bad_expected_access&lt;conqueue_errc&gt;& e) 
</pre>
    </td>
  </tr>
</table>
<p>This alternative disfavors an exception throwing and inconsistent with
standard library containers. We consider this to be an inferior to the alternative offered in the previous section.</p>
<h1 id="protection-against-consumption-of-values">Protection against consumption of values</h1>
<p>While in our paper we suggested to follow the precedent of <code>try_emplace</code>
that guarantees that:</p>
<blockquote>
<p>If the map already contains an element whose key is equivalent to k, *this and args... are unchanged.</p>
</blockquote>
<p>We recommend to follow <code>try_emplace</code> example in this regard as we do not envision
that providing this guarantee for the <code>buffer_queue</code> will be a burden for the implementors.</p>
<p>Nevertheless, we will explore in this section alternative API shapes:</p>
<pre><code class="language-c++"><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp; x, error_code&amp; ec)</span></span>; <span class="hljs-function"><span class="hljs-keyword">or</span>
<span class="hljs-keyword">auto</span> <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp; x, <span class="hljs-type">nothrow_t</span>)</span> -&gt; expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt;</span>;

<span class="hljs-function">vs

<span class="hljs-keyword">auto</span> <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp; x, <span class="hljs-type">nothrow_t</span>)</span> -&gt; expected&lt;<span class="hljs-type">void</span>, std::pair&lt;T, conqueue_errc&gt;&gt;
</span></code></pre>
<table>
  <tr>
    <th>unchanged guaranteed</th>
    <th>return back</th>
  </tr>
  <tr>
    <td>
<pre>
T val = get_value();
if (auto result = q.try_push(std::move(val)))
  return;
else
  println("failed {}, value {}", 
     result.error(), val);
</pre>
    </td>
    <td>
<pre>
if (auto result = q.try_push(get_value()))
   return;
else
  println("failed {}, value {}", 
     result.error().second,
     result.error().first);
</pre>
    </td>
  </tr>
</table>
<p>Returning back the value, impose a burden on the user and
forces even more awkward catch clauses if bad_unexpected_access is thrown
in response to <code>expected&lt;T&gt;::value()</code> if the expected stores error,
since now error encodes the value as well.</p>
<p>Staying with guarantees similar to <code>try_emplace</code> remains our
preferred alternative.</p>
<h2 id="linters-and-coding-guidelines">Linters and coding guidelines</h2>
<p>There is another consideration brought up by LEWG is that
clang-tidy warns on use after std::move. Luckily, clang-tidy recognizes
try_emplace being special and have special comment in the documentation:</p>
<p><a href="https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#move">https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html#move</a></p>
<blockquote>
<p>There is one special case: A call to std::move inside a try_emplace call is conservatively assumed not to move. This is to avoid spurious warnings, as the check has no way to reason about the bool returned by try_emplace.</p>
</blockquote>
<p>For C++26, clang-tidy will need to be updated to include relevant buffer_queue APIs that do not consume the value on failure.</p>
<h3 id="coding-guidelines">Coding Guidelines</h3>
<p>Andreas Weis shared that upcoming Misra C++202x will be having a rule that bans use after move.</p>
<p>C++ Core Guidelines also has a rule <a href="https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es56-write-stdmove-only-when-you-need-to-explicitly-move-an-object-to-another-scope">ES.56</a> that</p>
<blockquote>
<p>Usually, a std::move() is used as an argument to a &amp;&amp; parameter. And after you do that, assume the object has been moved from (see C.64) and don’t read its state again until you first set it to a new value.</p>
</blockquote>
<p>We may choose to update the guidelines to carve an exception to APIs like <code>try_emplace</code> rather
than pessimizing <code>try_</code> API requiring incurring a move-construction as opposed to a promise
not to touch the <code>T&amp;&amp;</code> arguments on failure.</p>
<h1 id="other-apis">Other APIs</h1>
<p>Assuming that we are restyling non-throwing versions
of the APIs along the lines that takes <code>std::nothrow</code> for disambiguation only,
and follow the precedent of <code>try_emplace</code> with regard to
not consuming <code>T&amp;&amp;</code> values on failure to push, the APIs will look like:</p>
<pre><code class="language-c++"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span></span>;
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push</span><span class="hljs-params">(T&amp;&amp;)</span></span>;

<span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;, <span class="hljs-type">nothrow_t</span>)</span></span>;
<span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">push</span><span class="hljs-params">(T&amp;&amp;, <span class="hljs-type">nothrow_t</span>)</span></span>;

<span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">try_push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;)</span></span>;
<span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp;)</span></span>;

<span class="hljs-function">T <span class="hljs-title">pop</span><span class="hljs-params">()</span></span>;
<span class="hljs-function">expected&lt;T, conqueue_errc&gt; <span class="hljs-title">pop</span><span class="hljs-params">(<span class="hljs-type">nothrow_t</span>)</span></span>;
<span class="hljs-function">expected&lt;T, conqueue_errc&gt; <span class="hljs-title">try_pop</span><span class="hljs-params">()</span></span>;
</code></pre>
<p>If the policy of LEWG would be to always add nothrow_t argument to <code>std::expected</code>
returning APIs, <code>try_</code> versions will become:</p>
<pre><code class="language-c++"><span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">try_push</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp;, <span class="hljs-type">nothrow_t</span>)</span></span>;
<span class="hljs-function">expected&lt;<span class="hljs-type">void</span>, conqueue_errc&gt; <span class="hljs-title">try_push</span><span class="hljs-params">(T&amp;&amp;, <span class="hljs-type">nothrow_t</span>)</span></span>;
<span class="hljs-function">expected&lt;T, conqueue_errc&gt; <span class="hljs-title">try_pop</span><span class="hljs-params">(<span class="hljs-type">nothrow_t</span>)</span></span>;
</code></pre>
<h3 id="pop-examples">Pop examples</h3>
<p>To complete the pictures let's compare how the status quo
and <code>nothrow_t</code> versions of the API fare in the pop scenarios:</p>
<h2 id="drain-the-queue-with-blocking">Drain the queue with blocking</h2>
<p>Status quo:</p>
<pre><code class="language-c++">error_code ec;
<span class="hljs-keyword">while</span> (<span class="hljs-keyword">auto</span> val = q.<span class="hljs-built_in">pop</span>(ec))
   <span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;got {}&quot;</span>, *val);
</code></pre>
<p>expected based ones (one line shorter and no unneeded ec)</p>
<pre><code class="language-c++"><span class="hljs-keyword">while</span> (<span class="hljs-keyword">auto</span> val = q.<span class="hljs-built_in">pop</span>(nothrow))
  <span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;got {}&quot;</span>, *val);
</code></pre>
<h2 id="drain-the-queue-without-blocking">Drain the queue without blocking</h2>
<p>Status quo:</p>
<pre><code class="language-c++">error_code ec;
<span class="hljs-keyword">while</span> (<span class="hljs-keyword">auto</span> val = q.<span class="hljs-built_in">try_pop</span>(ec))
   <span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;got {}&quot;</span>, *val);
<span class="hljs-keyword">if</span> (ec == conqueue_errc::closed)
  <span class="hljs-keyword">return</span>;
<span class="hljs-comment">// do something else.</span>
</code></pre>
<p>std::expected based has unfortunate duplication since
we want to know why the pop failed and we have to move
val out of the loop condition.</p>
<pre><code class="language-c++"><span class="hljs-keyword">auto</span> val = q.<span class="hljs-built_in">try_pop</span>();
<span class="hljs-keyword">while</span> (val) {
  <span class="hljs-built_in">println</span>(<span class="hljs-string">&quot;got {}&quot;</span>, *val);
  val = q.<span class="hljs-built_in">try_pop</span>();
}
<span class="hljs-keyword">if</span> (val.<span class="hljs-built_in">error</span>() == conqueue_errc::closed)
  <span class="hljs-keyword">return</span>;
<span class="hljs-comment">// do something else</span>
</code></pre>
<p>The surprising observation we can make is that <code>std::expected</code> based API
do not appear to be a clearcut winners and only offer
marginal improvement over <code>std::error_code</code> based ones and only
in some scenarios.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Based on this thought experiment. We did not find <code>expected</code> based API
a clear improvement over the status quo.</p>
<h2 id="references">References</h2>
<ol>
<li><a href="https://wg21.link/p1958r0">p1958r0: C++ Concurrent Buffer Queue</a></li>
<li><a href="https://wg21.link/p0260r6">p0260r6: A proposal to add a concurrent queue
to the standard library</a></li>
<li><a href="https://wg21.link/p2882r0">p2882r0: An Event Model for C++ Executors</a></li>
<li><a href="https://wg21.link/p2300r7">p2300r7: std::execution</a></li>
</ol>

        
        
    </body>
    </html>