<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://quuxplusone.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://quuxplusone.github.io/blog/" rel="alternate" type="text/html" /><updated>2026-06-12T18:18:10+00:00</updated><id>https://quuxplusone.github.io/blog/feed.xml</id><title type="html">Arthur O’Dwyer</title><subtitle>Stuff mostly about C++</subtitle><entry><title type="html">Katakana and Cypriot</title><link href="https://quuxplusone.github.io/blog/2026/06/12/syllabaries/" rel="alternate" type="text/html" title="Katakana and Cypriot" /><published>2026-06-12T00:01:00+00:00</published><updated>2026-06-12T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/06/12/syllabaries</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/06/12/syllabaries/"><![CDATA[<p>From Louis Godart’s <em>The Phaistos Disc: The Enigma of an Aegean Script</em> (1994,
tr. Alexandra Doumas):</p>

<blockquote>
  <p>Whoever studies the history of writing, ancient or modern, should be mindful of
one basic methodological principle: a comparison between the signs encountered
in different scripts should always be treated with skepticism and subjected
to severe criticism. There are plenty of examples of signs that are graphically
the same, and indeed have the same phonetic value, in scripts that have
absolutely nothing in common. A case in point are the nine signs in the classical
Cypriot syllabic script, in use on Cyprus until the fourth century BC, and
modern Japanese. The signs YE [<em>sic</em>], VE [<em>sic</em>], RO, SE, TO, KO, RU, ME, and PE are written
in exactly the same way in both, without anyone ever even imagining an affinity
between the script of ancient Cyprus and the <em>kana</em> script of Japan.</p>
</blockquote>

<p>These days both <a href="https://en.wikipedia.org/wiki/Katakana_(Unicode_block)">katakana</a>
and the <a href="https://en.wikipedia.org/wiki/Cypriot_syllabary">Cypriot syllabary</a>
are encoded in Unicode, so it’s convenient to present the following table of glyphs
that (according to their Unicode character names) represent the “same” phonetic values:</p>

<table>
  <thead>
    <tr>
      <th>Cypriot</th>
      <th>Kana</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>𐠀</td>
      <td>ア</td>
      <td>A</td>
    </tr>
    <tr>
      <td>𐠊</td>
      <td>カ</td>
      <td>KA</td>
    </tr>
    <tr>
      <td>𐠔</td>
      <td>マ</td>
      <td>MA</td>
    </tr>
    <tr>
      <td>𐠙</td>
      <td>ナ</td>
      <td>NA</td>
    </tr>
    <tr>
      <td>𐠞</td>
      <td>パ</td>
      <td>PA</td>
    </tr>
    <tr>
      <td>𐠣</td>
      <td>ラ</td>
      <td>RA</td>
    </tr>
    <tr>
      <td>𐠨</td>
      <td>サ</td>
      <td>SA</td>
    </tr>
    <tr>
      <td>𐠭</td>
      <td>タ</td>
      <td>TA</td>
    </tr>
    <tr>
      <td>𐠲</td>
      <td>ワ</td>
      <td>WA</td>
    </tr>
    <tr>
      <td>𐠼</td>
      <td>ザ</td>
      <td>ZA</td>
    </tr>
    <tr>
      <td>𐠁</td>
      <td>エ</td>
      <td>E</td>
    </tr>
    <tr>
      <td>𐠋</td>
      <td>ケ</td>
      <td>KE</td>
    </tr>
    <tr>
      <td>𐠕</td>
      <td>メ</td>
      <td><b>ME</b></td>
    </tr>
    <tr>
      <td>𐠚</td>
      <td>ネ</td>
      <td>NE</td>
    </tr>
    <tr>
      <td>𐠟</td>
      <td>ペ</td>
      <td><b>PE</b> (cf. katakana ヘ, “HE”)</td>
    </tr>
    <tr>
      <td>𐠤</td>
      <td>レ</td>
      <td>RE</td>
    </tr>
    <tr>
      <td>𐠩</td>
      <td>セ</td>
      <td><b>SE</b></td>
    </tr>
    <tr>
      <td>𐠮</td>
      <td>テ</td>
      <td>TE</td>
    </tr>
    <tr>
      <td>𐠳</td>
      <td>ヱ</td>
      <td><b>WE</b> (cf. katakana エ, “E”)</td>
    </tr>
    <tr>
      <td>𐠂</td>
      <td>イ</td>
      <td>I</td>
    </tr>
    <tr>
      <td>𐠌</td>
      <td>キ</td>
      <td>KI</td>
    </tr>
    <tr>
      <td>𐠖</td>
      <td>ミ</td>
      <td>MI</td>
    </tr>
    <tr>
      <td>𐠛</td>
      <td>ニ</td>
      <td>NI</td>
    </tr>
    <tr>
      <td>𐠠</td>
      <td>ピ</td>
      <td>PI</td>
    </tr>
    <tr>
      <td>𐠥</td>
      <td>リ</td>
      <td>RI</td>
    </tr>
    <tr>
      <td>𐠪</td>
      <td>シ</td>
      <td>SI</td>
    </tr>
    <tr>
      <td>𐠯</td>
      <td>チ</td>
      <td>TI</td>
    </tr>
    <tr>
      <td>𐠴</td>
      <td>ヰ</td>
      <td>WI</td>
    </tr>
    <tr>
      <td>𐠃</td>
      <td>オ</td>
      <td>O</td>
    </tr>
    <tr>
      <td>𐠍</td>
      <td>コ</td>
      <td><b>KO</b></td>
    </tr>
    <tr>
      <td>𐠗</td>
      <td>モ</td>
      <td>MO</td>
    </tr>
    <tr>
      <td>𐠜</td>
      <td>ノ</td>
      <td>NO</td>
    </tr>
    <tr>
      <td>𐠡</td>
      <td>ポ</td>
      <td>PO</td>
    </tr>
    <tr>
      <td>𐠦</td>
      <td>ロ</td>
      <td><b>RO</b></td>
    </tr>
    <tr>
      <td>𐠫</td>
      <td>ソ</td>
      <td>SO</td>
    </tr>
    <tr>
      <td>𐠰</td>
      <td>ト</td>
      <td><b>TO</b></td>
    </tr>
    <tr>
      <td>𐠵</td>
      <td>ヲ</td>
      <td>WO</td>
    </tr>
    <tr>
      <td>𐠿</td>
      <td>ゾ</td>
      <td>ZO</td>
    </tr>
    <tr>
      <td>𐠄</td>
      <td>ウ</td>
      <td>U</td>
    </tr>
    <tr>
      <td>𐠎</td>
      <td>ク</td>
      <td>KU</td>
    </tr>
    <tr>
      <td>𐠘</td>
      <td>ム</td>
      <td>MU</td>
    </tr>
    <tr>
      <td>𐠝</td>
      <td>ヌ</td>
      <td>NU</td>
    </tr>
    <tr>
      <td>𐠢</td>
      <td>プ</td>
      <td>PU</td>
    </tr>
    <tr>
      <td>𐠧</td>
      <td>ル</td>
      <td><b>RU</b></td>
    </tr>
    <tr>
      <td>𐠬</td>
      <td>ス</td>
      <td>SU</td>
    </tr>
    <tr>
      <td>𐠱</td>
      <td>ツ</td>
      <td>TU</td>
    </tr>
  </tbody>
</table>

<p>See also a nice diagram of a few of these on <a href="https://air.unimi.it/retrieve/dfa8b990-b058-748b-e053-3a05fe0a3a96/phd_unimi_R08526.pdf">page 11</a>
of Francesco Soldani’s doctoral thesis “Interconnessione grafica tra i vari sillabari egei e loro leggibilità”
(University of Milan, 2013).</p>]]></content><author><name></name></author><category term="linguistics" /><category term="typography" /><summary type="html"><![CDATA[From Louis Godart’s The Phaistos Disc: The Enigma of an Aegean Script (1994, tr. Alexandra Doumas):]]></summary></entry><entry><title type="html">C++26 Reflection gives us universal template parameters</title><link href="https://quuxplusone.github.io/blog/2026/06/07/meta-to/" rel="alternate" type="text/html" title="C++26 Reflection gives us universal template parameters" /><published>2026-06-07T00:01:00+00:00</published><updated>2026-06-07T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/06/07/meta-to</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/06/07/meta-to/"><![CDATA[<p>Keenan Horrigan on the std-proposals mailing list <a href="https://lists.isocpp.org/std-proposals/2026/06/18544.php">pointed out</a>
an interesting consequence of C++26 Reflection: it seems to give us “universal template parameters”
almost for free.</p>

<p>The STL sometimes tries to pretend we have “universal template parameters” by overloading templates
with the same name. For example, we have both <code class="language-plaintext highlighter-rouge">std::get&lt;T&gt;(tp)</code> taking a type and <code class="language-plaintext highlighter-rouge">std::get&lt;I&gt;(tp)</code>
taking an integer; but these are just two completely different signatures of <code class="language-plaintext highlighter-rouge">std::get</code> in the same overload
set. Likewise in C++23 you can write either <code class="language-plaintext highlighter-rouge">rg | ranges::to&lt;std::vector&lt;int&gt;&gt;()</code> (where the argument to
<code class="language-plaintext highlighter-rouge">to</code> is a type) or <code class="language-plaintext highlighter-rouge">rg | ranges::to&lt;std::vector&gt;()</code> (where the argument is a class template).
Again this is accomplished with an overload set — roughly like this (modulo I’ve totally mangled
the actual job of <code class="language-plaintext highlighter-rouge">ranges::to</code>, which is generally <em>not</em> to forward its arguments to <code class="language-plaintext highlighter-rouge">To</code>’s constructor):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class To, class... Args&gt;  // #1
auto rangish_to(Args&amp;&amp;... args) {
  return To(std::forward&lt;Args&gt;(args)...);
}
template&lt;template&lt;class...&gt; class To, class... Args&gt;  // #2
auto rangish_to(Args&amp;&amp;... args) {
  return To(std::forward&lt;Args&gt;(args)...);
}

int *a, *b;
auto x = rangish_to&lt;std::vector&lt;int&gt;&gt;(a, b);      // OK, #1
auto y = rangish_to&lt;std::vector&gt;(a, b);           // OK, #2
</code></pre></div></div>

<p>But even this overload set won’t accept a caller like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>auto z = rangish_to&lt;std::ranges::subrange&gt;(a, b); // error
</code></pre></div></div>

<p>because <code class="language-plaintext highlighter-rouge">std::ranges::subrange</code> as a template argument matches neither <code class="language-plaintext highlighter-rouge">class To</code> nor
<code class="language-plaintext highlighter-rouge">template&lt;class...&gt; class To</code>. It’s actually a class template of two type parameters and a constant!
(<a href="https://en.cppreference.com/cpp/ranges/subrange">cppreference.</a>)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum class subrange_kind : bool {
  unsized,
  sized,
};
template&lt;
  input_or_output_iterator I,
  sentinel_for&lt;I&gt; S = I,
  subrange_kind K = sized_sentinel_for&lt;S, I&gt; ? sized : unsized&gt;
class subrange { ~~~~ };
</code></pre></div></div>

<p>We could accept <code class="language-plaintext highlighter-rouge">subrange</code> ad-hoc via a third overload:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;template&lt;class,class,auto&gt; class To, class... Args&gt;  // #3
auto rangish_to(Args&amp;&amp;... args) {
  return To(std::forward&lt;Args&gt;(args)...);
}
</code></pre></div></div>

<p>but in general there’s no way to write out the set of <em>all possible</em> kinds of
class templates, so we can’t make our <code class="language-plaintext highlighter-rouge">rangish_to</code> accept all of them.</p>

<blockquote>
  <p>You might point out that the real <code class="language-plaintext highlighter-rouge">ranges::to</code> wouldn’t accept <code class="language-plaintext highlighter-rouge">subrange</code> anyway, because the real
<code class="language-plaintext highlighter-rouge">ranges::to</code> accepts only containers like <code class="language-plaintext highlighter-rouge">vector</code>, not views like <code class="language-plaintext highlighter-rouge">subrange</code> or <code class="language-plaintext highlighter-rouge">span</code>. However,
this deficiency is likely to be <a href="/blog/2019/08/02/the-tough-guide-to-cpp-acronyms/#dr">DR’ed</a>
in the near future thanks to Hewill Kang’s well-received
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3544r0.html">P3544 “<code class="language-plaintext highlighter-rouge">ranges::to&lt;view&gt;</code>.”</a></p>
</blockquote>

<p>C++26 Reflection to the rescue! C++26 Reflection is “untyped”: it uses
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2996r13.html#why-a-single-opaque-reflection-type">a single type</a>
<code class="language-plaintext highlighter-rouge">std::meta::info</code> to represent the reflections of every kind of thing
in the language. Therefore a template parameter of type <code class="language-plaintext highlighter-rouge">meta::info</code> alone can represent
<code class="language-plaintext highlighter-rouge">std::vector</code>, <code class="language-plaintext highlighter-rouge">std::ranges::subrange</code>, <code class="language-plaintext highlighter-rouge">std::array</code>, or anything else (<code class="language-plaintext highlighter-rouge">std::vector&lt;int&gt;</code>,
<code class="language-plaintext highlighter-rouge">size_t</code>, <code class="language-plaintext highlighter-rouge">42</code>, <code class="language-plaintext highlighter-rouge">std::ignore</code>…) and so we can write a single function template like this
(<a href="https://godbolt.org/z/czYzKEczT">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;std::meta::info To, class... Args&gt;
auto meta_to(Args&amp;&amp;... args) {
  return typename [:To:](std::forward&lt;Args&gt;(args)...);
}

auto x = meta_to&lt;^^std::vector&lt;int&gt;&gt;(a, b);      // OK
auto y = meta_to&lt;^^std::vector&gt;(a, b);           // OK
auto z = meta_to&lt;^^std::ranges::subrange&gt;(a, b); // OK!
</code></pre></div></div>

<p>For the cost of two characters <code class="language-plaintext highlighter-rouge">^^</code> (and a really cryptic error message if you forget
the <code class="language-plaintext highlighter-rouge">typename</code> keyword), C++26 seems to have given us universal template parameters!</p>

<p>(Does this mean <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2989r2.pdf">P2989 “Universal template parameters”</a>
is obsolete? IMHO, yes. But I can see how one might debate it: those two <code class="language-plaintext highlighter-rouge">^^</code> characters
<em>are</em> kind of ugly.)</p>]]></content><author><name></name></author><category term="class-template-argument-deduction" /><category term="paradigm-shift" /><category term="ranges" /><category term="reflection" /><category term="templates" /><summary type="html"><![CDATA[Keenan Horrigan on the std-proposals mailing list pointed out an interesting consequence of C++26 Reflection: it seems to give us “universal template parameters” almost for free.]]></summary></entry><entry><title type="html">Branchless sorting of trivially relocatable types</title><link href="https://quuxplusone.github.io/blog/2026/06/05/branchless-sorting/" rel="alternate" type="text/html" title="Branchless sorting of trivially relocatable types" /><published>2026-06-05T00:01:00+00:00</published><updated>2026-06-05T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/06/05/branchless-sorting</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/06/05/branchless-sorting/"><![CDATA[<p>A few days ago Christof Kaser posted a very impressive blog post on
<a href="https://tiki.li/blog/blqsort">“Fast Branchless Quicksort using Sorting-Networks”</a>
(<a href="https://github.com/chkas/blqsort">chkas/blqsort</a>). A “branchless” algorithm is
one designed to exploit modern processors’ conditional-move instructions. So for
example the <code class="language-plaintext highlighter-rouge">blqs::sort2</code> primitive, which looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class T, class Compare&gt;
void sort2(T&amp; a, T&amp; b, Compare comp) {
  T x = a;
  T y = b;
  bool m = comp(x, y);
  a = m ? x : y;
  b = m ? y : x;
}
</code></pre></div></div>

<p>when instantiated for <code class="language-plaintext highlighter-rouge">int</code> compiles down to a couple of <code class="language-plaintext highlighter-rouge">cmov</code> instructions on x86-64
and a couple of <code class="language-plaintext highlighter-rouge">csel</code> instructions on ARM64. (<a href="https://godbolt.org/z/v34v5oor1">Godbolt.</a>)</p>

<p>But at the higher generic-programming level, count all the copy operations in <code class="language-plaintext highlighter-rouge">sort2</code>!
It copies <code class="language-plaintext highlighter-rouge">a</code> into <code class="language-plaintext highlighter-rouge">x</code>; then copy-assigns <code class="language-plaintext highlighter-rouge">x</code> back into <code class="language-plaintext highlighter-rouge">a</code>. If <code class="language-plaintext highlighter-rouge">T</code> were an expensive-to-copy
type like <code class="language-plaintext highlighter-rouge">std::string</code>, this would be slow code; and if <code class="language-plaintext highlighter-rouge">T</code> were <code class="language-plaintext highlighter-rouge">unique_ptr</code>, it wouldn’t
compile at all. Therefore, <code class="language-plaintext highlighter-rouge">blqsort</code> enables its entire branchless “fast path” only for
types that are trivially copyable and roughly register-sized.</p>

<blockquote>
  <p>As of this writing the gating condition is <code class="language-plaintext highlighter-rouge">std::is_trivially_copyable&lt;T&gt;::value &amp;&amp; sizeof(T) &lt;= 16</code>,
but I’ve pointed out to Christof that his <code class="language-plaintext highlighter-rouge">heap_sort</code> also depends on <code class="language-plaintext highlighter-rouge">T</code> to be trivially
default-constructible. It’s also possible (if pathological) for <code class="language-plaintext highlighter-rouge">T</code> to be trivially copyable
yet not copy-constructible or (more commonly) not copy-assignable. But this blog post isn’t
really about narrowing the gate; we’re going to broaden it instead!</p>
</blockquote>

<p>“Trivially copyable” is what I call a “holistic” trait: it means something about the behavior
of the entire type, rather than about just one special member function or just one kind of expression.
And specifically <em>what</em> it means is that you can do any value-semantic operation — bringing new
copies into existence, poofing them out of existence, overwriting one’s value with another’s,
swapping or permuting or copying — as if the objects were just bags of bits. As long as you
never try to invent a <em>new</em> value out of whole cloth, you can shift copies of your <em>given</em> values
around as much as you like, even into completely new areas of memory, simply by memcpying them.
In the following diagram, each box represents a C++ <em>object</em>, and the color of the box represents
the object’s <em>value</em> (for example 42, or 3.14, or “hello,” or Tuesday).</p>

<p><img src="/blog/images/2026-06-05-holistic-tc.png" alt="" /></p>

<p>We see that in the “After” picture, the green object has become blue. Was that by copy-assignment
from the original blue object? or move-assignment? or copying from the yellow, destroying, and
then copy-constructing from a blue object? With trivial copyability, we needn’t say! Each of those
possible operation-sequences is guaranteed to be <em>physically</em> tantamount to simply memcpying the
“blue” bytes into their final location.</p>

<p>“Trivially relocatable” (the widely deployed P1144 idiom, I mean, not the unusable version that
was briefly merged into the C++26 draft in 2025) is another “holistic” trait. Specifically
what <em>trivially relocatable</em> means is that you can do any <em>affine</em> value-semantic operation —
swapping, permuting, relocating from one place to another — as if the objects were just bags of
bits. As long as you preserve the <em>number of copies of each given value,</em> you can shift that
particular set of values around as much as you like, even into completely new areas of memory,
simply by memcpying them. (But unlike two paragraphs ago, with <em>trivially relocatable</em> you’re
not allowed to turn one value into two, or poof a value completely out of existence: each and
every input value must be represented the same number of times in the final output.)</p>

<p><img src="/blog/images/2026-06-05-holistic-tr.png" alt="" /></p>

<p>As long as whatever highest-level algorithm we’re doing preserves this “affine,” one-to-one
property, every possible operation-sequence is guaranteed to be <em>physically</em> tantamount to
simply memcpying the bytes around.</p>

<blockquote>
  <p>The above images come from a ten-slide presentation on holistic traits I wrote in mid-2024.
See the whole slide deck <a href="/blog/code/2026-06-05-holistic-traits.pdf">here</a>.</p>
</blockquote>

<p>Algorithms that have this “affine” one-to-one property include <code class="language-plaintext highlighter-rouge">swap</code>, <code class="language-plaintext highlighter-rouge">rotate</code>, <code class="language-plaintext highlighter-rouge">partition</code>, and… <code class="language-plaintext highlighter-rouge">sort</code>!
Imagine rewriting <code class="language-plaintext highlighter-rouge">blqs::sort2</code> like this (<a href="https://godbolt.org/z/czfaKWrcc">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class T, class Compare&gt;
void sort2(T&amp; a, T&amp; b, Compare comp) {
  union U { T t; U() {} ~U() {} };
  U x, y;
  std::relocate_at(&amp;a, &amp;x.t);
  std::relocate_at(&amp;b, &amp;y.t);
  bool m = comp(x.t, y.t);
  std::relocate_at(m ? &amp;x.t : &amp;y.t, &amp;a);
  std::relocate_at(m ? &amp;y.t : &amp;x.t, &amp;b);
}
</code></pre></div></div>

<p>This is conceptually closer to what our <code class="language-plaintext highlighter-rouge">sort2</code> algorithm actually “needs” to do.
It doesn’t really care that it’s making copies of <code class="language-plaintext highlighter-rouge">T</code> objects; conceptually it’s just
bringing the values “closer to hand” (which could be a relocate), comparing its close-up
copies, and then putting the values back “in memory” (which could be a relocate). For
trivially copyable <code class="language-plaintext highlighter-rouge">T</code>, it’s totally fine to replace the first relocate with a copy-construct
and the second relocate with a copy-assign: the end result is guaranteed to be the same,
assuming it compiles at all. The relocation-based version merely extends that guarantee
from “trivially copyable <code class="language-plaintext highlighter-rouge">T</code>” to “trivially relocatable <code class="language-plaintext highlighter-rouge">T</code>.”</p>

<p>But the above code both depends on P1144/P3516 <code class="language-plaintext highlighter-rouge">relocate_at</code> and is super ugly.
Imagine patching every helper in <code class="language-plaintext highlighter-rouge">blqsort.h</code> with this pedantry! We can actually
achieve the same effect much more easily by leaning on the holistic guarantee: We know
that the end result of <code class="language-plaintext highlighter-rouge">sort2</code> — in fact the end result of <code class="language-plaintext highlighter-rouge">blqsort</code> — will be an affine,
one-to-one, permutation of the input values. So we can actually run the whole algorithm
working entirely with object representations! The patch for this is only a few lines long:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  constexpr bool copy_is_cheap =
    std::is_trivially_copyable&lt;T&gt;::value &amp;&amp; sizeof(T) &lt;= 16;

+ constexpr bool relocate_is_cheap =
+   std::is_trivially_relocatable&lt;T&gt;::value &amp;&amp; sizeof(T) &lt;= 16;

  if constexpr (copy_is_cheap) {
    blqsort(first, last - 1, comp);
+ } else if constexpr (relocate_is_cheap) {
+   struct Rep {
+     alignas(T) char data_[sizeof(T)];
+   };
+   blqsort((Rep*)first, (Rep*)(last - 1),
+     [&amp;](const Rep&amp; a, const Rep&amp; b) { return comp(*(const T*)&amp;a, *(const T*)&amp;b); });
  } else {
    block_qsort(first, last - 1, comp);
  }
</code></pre></div></div>

<p>This says, “If we know that <code class="language-plaintext highlighter-rouge">T</code> is trivially relocatable, then let’s just pretend we’re sorting
anonymous blocks of bytes instead. Make sure to treat them as <code class="language-plaintext highlighter-rouge">T</code> objects for the purposes
of <code class="language-plaintext highlighter-rouge">comp</code>, but don’t worry about the value-semantic bookkeeping; I promise it will all come out
correctly in the end.”</p>

<p>Christof’s <code class="language-plaintext highlighter-rouge">blqs::sort</code> uses the branchless fast path, <code class="language-plaintext highlighter-rouge">blqsort</code>, only for trivially copyable
types; types that don’t make it through that gate will use the slow path, <code class="language-plaintext highlighter-rouge">block_qsort</code>, instead.
This patch broadens the gate: now we can use the fast path for all trivially relocatable types,
including for example <code class="language-plaintext highlighter-rouge">unique_ptr</code> and <code class="language-plaintext highlighter-rouge">shared_ptr</code>. (But not <code class="language-plaintext highlighter-rouge">string</code>, because it fails the
<code class="language-plaintext highlighter-rouge">sizeof</code> check.)</p>

<h2 id="benchmark-results">Benchmark results</h2>

<p>This turns out to be a perfect testbed for the kinds of performance improvements you can get
from P1144-style trivial relocation on something as familiar as sorting a vector of <code class="language-plaintext highlighter-rouge">shared_ptr</code>.
I wrote a benchmark (<a href="/blog/code/2026-06-05-blqs-bench.cpp">backup</a>) comparing the performance
of the high-level <code class="language-plaintext highlighter-rouge">blqs::sort</code> on four different kinds of <code class="language-plaintext highlighter-rouge">T</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>struct TC {
  Int *p_;
  void *ctrl_;
  explicit TC() = default;
  explicit TC(Int&amp; i) : p_(&amp;i) {}
  friend auto operator&lt;=&gt;(const TC&amp; a, const TC&amp; b) { return *a.p_ &lt;=&gt; *b.p_; }
};

struct TR {
  std::shared_ptr&lt;Int&gt; p_;
  explicit TR() = default;
  explicit TR(Int&amp; i) : p_(&amp;i, [](Int*){}) {}
  friend auto operator&lt;=&gt;(const TR&amp; a, const TR&amp; b) { return *a.p_ &lt;=&gt; *b.p_; }
};

struct NTC : TC {
  using TC::TC;
  ~NTC() {}
};

struct NTR : TR {
  using TR::TR;
  NTR(NTR&amp;&amp;) = default;
  NTR(const NTR&amp;) = default;
  NTR&amp; operator=(NTR&amp;&amp;) = default;
  NTR&amp; operator=(const NTR&amp;) = default;
  ~NTR() {}
};
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">TR</code> is a Rule-of-Zero type that pays all the same costs as <code class="language-plaintext highlighter-rouge">shared_ptr</code>
for its value-semantic operations — yet it is trivially relocatable. Then <code class="language-plaintext highlighter-rouge">NTC</code> is
exactly the same as <code class="language-plaintext highlighter-rouge">TC</code> except that its no-op destructor makes it not-trivially-anything;
and <code class="language-plaintext highlighter-rouge">NTR</code> is exactly the same as <code class="language-plaintext highlighter-rouge">TR</code> (paying <code class="language-plaintext highlighter-rouge">shared_ptr</code> costs) except that its no-op
destructor makes it not-trivially-anything.</p>

<p>Sorting a vector of 50 million elements with Christof’s (current) implementation of
<code class="language-plaintext highlighter-rouge">blqs::sort</code> out of the box shows <code class="language-plaintext highlighter-rouge">TC</code> taking the fast path and all three of
<code class="language-plaintext highlighter-rouge">TR</code>, <code class="language-plaintext highlighter-rouge">NTC</code>, <code class="language-plaintext highlighter-rouge">NTR</code> taking the slow path, as predicted. Here the “<code class="language-plaintext highlighter-rouge">std::sort</code>” column
refers to libc++’s current implementation.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Type</th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">std::sort</code></th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">blqs::sort</code></th>
      <th style="text-align: right">Savings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TC</code></td>
      <td style="text-align: right">5.9s</td>
      <td style="text-align: right">3.5s</td>
      <td style="text-align: right">41%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TR</code></td>
      <td style="text-align: right">6.5s</td>
      <td style="text-align: right"><b>6.2s</b></td>
      <td style="text-align: right"><b>4%</b></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">NTC</code></td>
      <td style="text-align: right">5.8s</td>
      <td style="text-align: right">4.3s</td>
      <td style="text-align: right">25%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">NTR</code></td>
      <td style="text-align: right">6.3s</td>
      <td style="text-align: right">6.7s</td>
      <td style="text-align: right">−6%</td>
    </tr>
  </tbody>
</table>

<p>Now we apply the patch above, to enable the fast path whenever <code class="language-plaintext highlighter-rouge">relocate_is_cheap</code>.
The numbers for <code class="language-plaintext highlighter-rouge">TC</code>, <code class="language-plaintext highlighter-rouge">NTC</code>, and <code class="language-plaintext highlighter-rouge">NTR</code> aren’t expected to change; so you can see
roughly how noisy this microbenchmark is.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Type</th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">std::sort</code></th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">blqs::sort</code></th>
      <th style="text-align: right">Savings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TC</code></td>
      <td style="text-align: right">5.8s</td>
      <td style="text-align: right">3.4s</td>
      <td style="text-align: right">42%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">TR</code></td>
      <td style="text-align: right">6.5s</td>
      <td style="text-align: right"><b>4.0s</b></td>
      <td style="text-align: right"><b>38%</b></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">NTC</code></td>
      <td style="text-align: right">5.8s</td>
      <td style="text-align: right">4.4s</td>
      <td style="text-align: right">24%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">NTR</code></td>
      <td style="text-align: right">6.2s</td>
      <td style="text-align: right">6.3s</td>
      <td style="text-align: right">−2%</td>
    </tr>
  </tbody>
</table>

<p>That is, by applying an eight-line patch to use the fast path for trivially relocatable
<code class="language-plaintext highlighter-rouge">shared_ptr</code>, we’ve turned <code class="language-plaintext highlighter-rouge">blqs::sort</code> from a 4% win into a 38% win, right in line
with its speedup for trivially copyable types.</p>

<h2 id="what-gave-us-this-speedup">What gave us this speedup?</h2>

<p>To get this speedup, we used one “dirty trick” and one compiler extension.
Our dirty trick was:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>struct Rep {
  alignas(T) char data_[sizeof(T)];
};
blqsort((Rep*)first, (Rep*)(last - 1),
  [&amp;](const Rep&amp; a, const Rep&amp; b) { return comp(*(const T*)&amp;a, *(const T*)&amp;b); });
</code></pre></div></div>

<p>Those casts are <code class="language-plaintext highlighter-rouge">reinterpret_cast</code> in disguise. This is type-punning, and
technically it’s undefined behavior. (It’s certainly not constexpr-friendly.)
We can make it well-defined and constexpr-friendly by using the P1144/P3516
entrypoint <code class="language-plaintext highlighter-rouge">std::relocate_at</code> on <code class="language-plaintext highlighter-rouge">T</code> objects instead of copy-assignment on <code class="language-plaintext highlighter-rouge">Rep</code>
objects; but as we’ve seen, that’s very ugly — and in fact very error-prone.
Changing a copy-assignment-based algorithm to use relocation takes a major
code audit to make sure you balanced out every construction and destruction, never
moved or relocated twice from the same object, never accessed an object outside
its lifetime, etc. Our “dirty trick” foolproofly accomplished the same codegen,
with just a little surgical patch at the top level. If we really needed constexpr-friendliness,
I’d add an <code class="language-plaintext highlighter-rouge">if consteval</code> at the top level sooner than I’d give up this trick.</p>

<p>But of course the thing we <em>must</em> have, in order to apply this optimization,
is a reliable way to detect <code class="language-plaintext highlighter-rouge">is_trivially_relocatable&lt;T&gt;</code>. My code makes it look
easy, because I use a compiler (and standard library) with full P1144 support.
Sadly neither Clang nor GCC is such a compiler.</p>

<p>Mainline Clang actually has two
different builtins in this area — <code class="language-plaintext highlighter-rouge">__is_trivially_relocatable</code> and
<code class="language-plaintext highlighter-rouge">__builtin_is_cpp_trivially_relocatable</code> — but both of them have too many false
positives to use safely in production: the latter by design (it intended to
implement the failed P2786 proposal) and the former merely by neglect. An attempt
was made to fix up the former in <a href="https://github.com/llvm/llvm-project/pull/84621">Clang #84621</a>,
but the maintainers resisted, and the patch was eventually abandoned.
Obviously I’d like to see it revived.</p>]]></content><author><name></name></author><category term="algorithms" /><category term="benchmarks" /><category term="llvm" /><category term="relocatability" /><category term="triviality" /><category term="value-semantics" /><summary type="html"><![CDATA[A few days ago Christof Kaser posted a very impressive blog post on “Fast Branchless Quicksort using Sorting-Networks” (chkas/blqsort). A “branchless” algorithm is one designed to exploit modern processors’ conditional-move instructions. So for example the blqs::sort2 primitive, which looks like this:]]></summary></entry><entry><title type="html">Does bulk memmove speed up `std::remove_if`? (No.)</title><link href="https://quuxplusone.github.io/blog/2026/05/23/chunked-remove/" rel="alternate" type="text/html" title="Does bulk memmove speed up `std::remove_if`? (No.)" /><published>2026-05-23T00:01:00+00:00</published><updated>2026-05-23T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/23/chunked-remove</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/23/chunked-remove/"><![CDATA[<p>This morning I was reading the umpteenth std-proposals thread proposing some variety of
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0041r0.html"><code class="language-plaintext highlighter-rouge">unstable_remove</code></a>
and it occurred to me that one odd thing about a swap-and-pop-based <code class="language-plaintext highlighter-rouge">unstable_remove</code>
is that it tends to replace large swaths of contiguous removals by <em>reversing</em> the
elements that are kept. For example (<a href="https://godbolt.org/z/f9doebhch">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class BidirIt, class Pred&gt;
BidirIt unstable_remove_if(BidirIt first, BidirIt last, Pred pred) {
  while (true) {
    first = std::find_if(first, last, pred);
    if (first == last) return first;
    while (true) {
      --last;
      if (first == last) return first;
      if (!pred(*last)) break;
    }
    *first++ = std::move(*last);
  }
}

int main() {
  auto in234 = [](int x) { return 2 &lt;= x &amp;&amp; x &lt;= 4; };
  std::vector&lt;int&gt; v = {1,2,3,4,5,6,7,8,9};
  v.erase(unstable_remove_if(v.begin(), v.end(), in234), v.end());
  // v is {1,9,8,7,5,6}
}
</code></pre></div></div>

<p>It would be more aesthetically pleasing to produce <code class="language-plaintext highlighter-rouge">{1,7,8,9,5,6}</code>: for trivially copyable
elements, this would be a single memmove. In a sense, “reversing the elements” is extra work
that we might save time by avoiding. But to avoid <em>that</em> work, we might have to do <em>even more</em>
work in the form of bookkeeping. I haven’t attempted to benchmark any such change to
<code class="language-plaintext highlighter-rouge">unstable_remove</code>.</p>

<p>But the same aesthetic consideration applies to the ordinary, stable <code class="language-plaintext highlighter-rouge">std::remove_if</code>.
Traditionally it’s a loop over <code class="language-plaintext highlighter-rouge">operator=</code>, like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template &lt;class FwdIt, class Pred&gt;
FwdIt smooth_remove_if(FwdIt first, FwdIt last, Pred pred) {
  FwdIt dfirst = std::find_if(first, last, pred);
  if (dfirst != last) {
    for (first = std::next(dfirst); first != last; ++first) {
      if (!pred(*first)) {
        *dfirst++ = std::move(*first);
      }
    }
  }
  return dfirst;
}
</code></pre></div></div>

<p>But what if we “chunked” the writes to <code class="language-plaintext highlighter-rouge">*dfirst</code>, like this?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template &lt;class FwdIt, class Pred&gt;
FwdIt chunky_remove_if(FwdIt first, FwdIt last, Pred pred) {
  FwdIt dfirst = std::find_if(first, last, pred);
  if (dfirst != last) {
    for (first = std::next(dfirst); first != last; ++first) {
      if (!pred(*first)) {
        FwdIt sfirst = first;
        first = std::find_if(std::next(first), last, pred);
        dfirst = std::move(sfirst, first, dfirst);
        if (first == last) break;
      }
    }
  }
  return dfirst;
}
</code></pre></div></div>

<p>Delegating the work to the <a href="https://en.cppreference.com/cpp/algorithm/move"><code class="language-plaintext highlighter-rouge">std::move</code></a> algorithm
allows the library to use memmove for that work, if the element type happens to be trivially
copyable. The nested loop complicates the generated code (<a href="https://godbolt.org/z/ja48jh4nv">Godbolt</a>),
but is it worth it? Do we actually save CPU cycles?</p>

<p>Well, if the average length of a “run” of survivors is long, then we
might expect the cost of a single large memmove to beat out the cost of <code class="language-plaintext highlighter-rouge">operator=</code>-in-a-loop.
But at the other extreme, if the average length of a run is only 1 element, then memmove won’t
save anything; we’ll be paying all the bookkeeping cost for none of the benefit.</p>

<blockquote>
  <p>By “bookkeeping” I mean not only the extra register and icache pressure of the more complicated
algorithm, but also the overhead of the call to memmove itself, and that memmove will necessarily
start by checking whether it needs to handle forward overlap. (That branch is completely predictable
— it doesn’t — but is still overhead absent from the “smooth” version.)</p>
</blockquote>

<p>I wrote a little benchmark (<a href="/blog/code/2026-05-23-chunked-remove-benchmark.cpp">backup</a>)
to time a call to <code class="language-plaintext highlighter-rouge">std::remove_if</code> on an array of a million ints,
with a bit-masking predicate that removed half of the elements; one in 8;
one in 128; or one in 1024. I contrived this benchmark deliberately to play
to the “chunky” algorithm’s strengths: trivially copyable elements, with large swaths moved
contiguously.</p>

<p>The “smooth” column here is merely to prove that my <code class="language-plaintext highlighter-rouge">smooth_remove_if</code> implementation
is essentially the same as libc++’s default implementation:
my <code class="language-plaintext highlighter-rouge">chunky_remove_if</code> can’t blame its defeat on “library vendor magic.”
Yet it <em>is</em> defeated, and decisively so:</p>

<table class="smaller">
  <thead>
    <tr>
      <th style="text-align: left">Elements removed</th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">std</code></th>
      <th style="text-align: right">smooth</th>
      <th style="text-align: right">chunky</th>
      <th style="text-align: right">Penalty</th>
      <th style="text-align: right">smooth<br />assignments</th>
      <th style="text-align: right">chunky<br />memmoves</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">1 in 2</td>
      <td style="text-align: right">3122 us</td>
      <td style="text-align: right">3157 us</td>
      <td style="text-align: right">3797 us</td>
      <td style="text-align: right">+20%</td>
      <td style="text-align: right">499549</td>
      <td style="text-align: right">249971</td>
    </tr>
    <tr>
      <td style="text-align: left">1 in 8</td>
      <td style="text-align: right">1082 us</td>
      <td style="text-align: right">1105 us</td>
      <td style="text-align: right">1471 us</td>
      <td style="text-align: right">+33%</td>
      <td style="text-align: right">874707</td>
      <td style="text-align: right">109724</td>
    </tr>
    <tr>
      <td style="text-align: left">1 in 128</td>
      <td style="text-align: right">416 us</td>
      <td style="text-align: right">420 us</td>
      <td style="text-align: right">468 us</td>
      <td style="text-align: right">+11%</td>
      <td style="text-align: right">992148</td>
      <td style="text-align: right">7750</td>
    </tr>
    <tr>
      <td style="text-align: left">1 in 1024</td>
      <td style="text-align: right">327 us</td>
      <td style="text-align: right">326 us</td>
      <td style="text-align: right">396 us</td>
      <td style="text-align: right">+21%</td>
      <td style="text-align: right">998482</td>
      <td style="text-align: right">958</td>
    </tr>
  </tbody>
</table>

<p>As expected, <code class="language-plaintext highlighter-rouge">chunky_remove_if</code> loses when the expected length of a memmove is short.
But it also loses when the expected length of a memmove is long! And, counterintuitively,
as we remove <em>fewer</em> elements (and thus execute <em>more</em> individual assignments in the “smooth”
case and <em>fewer</em> individual memmoves in the “chunky” case), <code class="language-plaintext highlighter-rouge">smooth_remove_if</code> gets faster and
faster, and <code class="language-plaintext highlighter-rouge">chunky_remove_if</code>’s performance penalty doesn’t budge.</p>

<p>I tentatively blame this on the branch predictor. The fewer elements we remove, the more
predictable the result of <code class="language-plaintext highlighter-rouge">pred(*first)</code>. Removing half the elements is the worst case for
<code class="language-plaintext highlighter-rouge">smooth_remove_if</code> simply because that turns it into a tight loop over a single completely
unpredictable branch. Making that branch more predictable dominates every other consideration,
including any speedup we might get from memmove (which, after all, is also a tight loop over
a mostly predictable branch: “have we counted all the way to <code class="language-plaintext highlighter-rouge">n</code> yet?”)</p>

<p>Conclusion: “Chunking” the elements moved by <code class="language-plaintext highlighter-rouge">remove_if</code> into larger memmoves
isn’t an optimization; it’s a pessimization.</p>

<p>This doesn’t directly say anything about <code class="language-plaintext highlighter-rouge">unstable_remove</code>, but it does suggest that
when it comes to benchmarking <code class="language-plaintext highlighter-rouge">remove</code>-related algorithms, we ought not to rabbit-hole on
<em>simply</em> minimizing the number of move-assignments; that kind of thing may be outweighed
by cache and branch-prediction effects.</p>]]></content><author><name></name></author><category term="benchmarks" /><category term="stl-classic" /><category term="triviality" /><summary type="html"><![CDATA[This morning I was reading the umpteenth std-proposals thread proposing some variety of unstable_remove and it occurred to me that one odd thing about a swap-and-pop-based unstable_remove is that it tends to replace large swaths of contiguous removals by reversing the elements that are kept. For example (Godbolt):]]></summary></entry><entry><title type="html">The _Book of St Albans_ (1486)</title><link href="https://quuxplusone.github.io/blog/2026/05/22/exaltation-of-larks/" rel="alternate" type="text/html" title="The _Book of St Albans_ (1486)" /><published>2026-05-22T00:01:00+00:00</published><updated>2026-05-22T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/22/exaltation-of-larks</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/22/exaltation-of-larks/"><![CDATA[<p>This week I read James Lipton’s <em>An Exaltation of Larks; or, The Venereal Game</em> (2nd edition, 1977),
a discursive collection and celebration of “terms of venery” — the collective nouns like
“school of fish” and “pride of lions” that were (so the story goes) mostly invented by
medieval hunters who wanted to have their own proper jargon to distinguish the real hunters
from the dilettantes.</p>

<blockquote>
  <p>Hark ye! Only last week that jack-fool, the young Lord of Brocas, was here talking
of having seen a covey of pheasants in the wood. One such speech would have been the
ruin of a young squire at the court.</p>

  <p>—Arthur Conan Doyle, <a href="https://archive.org/details/sirnigel02doylgoog/page/123"><em>Sir Nigel</em></a> (1905)</p>
</blockquote>

<p>Lipton’s starting point was a list of 164 terms for “the companies of beasts and fowls”
included — merely to fill out the last few pages of a <a href="https://en.wikipedia.org/wiki/Section_(bookbinding)">signature</a>
(so <a href="https://archive.org/details/bokeofsaintalban00bernuoft/page/21">says</a> William Blades’ excellent preface) —
in the <a href="https://en.wikipedia.org/wiki/Book_of_Saint_Albans"><em>Book of St Albans</em></a> (1486).
But from Lipton’s entertaining notes we can see he modernized a few of the terms; for example,
he discusses his difficulty in rendering the term “corueseris” (corvisers) for modern readers,
and ultimately lands on “shoemakers.”
This led me to wonder exactly what other changes Lipton might have snuck in, and therefore to look up the
original orthography of the <em>Book of St Albans</em>. That orthography is visible today both
<a href="https://archive.org/details/bokeofsaintalban00bernuoft/page/115/mode/1up">in Blades’ facsimile</a>
and <a href="https://www.gutenberg.org/files/71266/71266-h/71266-h.htm">in Unicode transliteration</a> at Project Gutenberg.
I was surprised to find that Lipton’s title, in the <em>Book of St Albans,</em> is rendered as “an exaltyng of larks”;
it’s only in <a href="https://searcharchives.bl.uk/catalog/032-001982919">Egerton MS 1995</a> (fols. 55b–58),
Lipton says, that we find “an exaltacyon of larks” proper.</p>

<hr />

<p>C. E. Hare, in <a href="https://archive.org/details/bwb_KQ-367-113/page/226"><em>The Language of Field Sports</em></a> (2nd edition 1949)
— which Lipton cites! — points out that many of the <em>Book’s</em> terms are not really “company terms.”
For example, “a couple or pair of bottles” is no more a company term than “a brace of pheasants”
or “a trio of tenors”; and as far as I can tell, “a cast of bread” parallels our “loaf of bread”:
no more a company term than “a jug of wine.”</p>

<p>Hare’s notes on the <em>Book</em> are well worth reading. See pages 232–239 for his collection
of neologisms including “a glitter of flying-fish” (R. Beale), “a sending of robins” (L. Dawson Campbell),
and “a scold of magpies.”</p>

<p>Hare credits “a scold of magpies” to <em>Country Life</em> magazine; but searching that periodical
I find only a Letter to the Editor from J. H. Owen (in the 1941-09-26 issue) on the subject
of <a href="https://archive.org/details/sim_country-life_1941-09-26_90_2332/page/590">“Congregations of Magpies”</a>
which also uses the terms “flock” and “party” but never (as noun nor verb) “scold.”</p>

<hr />

<p>Here are the terms of venery from the <em>Book of St Albans</em>, starting with those for
beasts and fowls that might be considered “serious,” and followed by those for human beings
of various professions which we might consider “jocular.” The original makes no such
distinction: serious and jocular are thoroughly intermixed. My tables appear in alphabetical
order, except that I’ll start as the <em>Book</em> does with “herd,” a term associated with
the royalty of the animal world.</p>

<table class="smaller">
  <thead>
    <tr>
      <th>Original orthography</th>
      <th>Modern orthography</th>
      <th>Lipton</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The Compaẏnẏs of beeſtẏs and fowlẏs</td>
      <td>The Companies of Beasts and Fowls</td>
      <td> </td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <td>an Herde of Hertis<br />an herde of all manͬ dere</td>
      <td>a herd of harts<br />  " of all manner of deer</td>
      <td>a herd of harts<br />(etc)</td>
    </tr>
    <tr>
      <td>an Herde of Swannys</td>
      <td> " of swans</td>
      <td>a wedge of swans</td>
    </tr>
    <tr>
      <td>an Herde of Cranys</td>
      <td> " of cranes</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Herde of Corlewys</td>
      <td> " of curlews</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Herde of wrennys</td>
      <td> " of wrens</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a badelyng of Dokis</td>
      <td>a badling of ducks</td>
      <td>a paddling of ducks</td>
    </tr>
    <tr>
      <td>a Baren of Mulis</td>
      <td>a barren of mules</td>
      <td>a barren (or bearing) of mules</td>
    </tr>
    <tr>
      <td>a Beuy of Roos</td>
      <td>a bevy of <a href="https://en.wikipedia.org/wiki/Roe_deer">roes</a></td>
      <td>a bevy of roebucks</td>
    </tr>
    <tr>
      <td>a Beuy of Quaylis</td>
      <td> " of quails</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a beldyng of Rookes</td>
      <td>a building of rooks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Bery of Conyis</td>
      <td>a bury of coneys</td>
      <td>(a colony of rabbits)</td>
    </tr>
    <tr>
      <td>a Beſynes of ferettis</td>
      <td>a business of ferrets</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Brace of grehoundis of ij<br />a Lece of Grehoundis of iij</td>
      <td>a brace of greyhounds (2)<br />a lease of greyhounds (3)</td>
      <td>a leash of greyhounds<br />also a brace</td>
    </tr>
    <tr>
      <td>a Brode of hennys</td>
      <td>a brood of hens</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a caſt of haukis of yᵉ tour ij<br />a Lece of theſſame haukis iij</td>
      <td>a cast of hawks of the tower (2)<br />a lease of the same hawks (3)</td>
      <td> </td>
    </tr>
    <tr>
      <td>a Cete of Graies</td>
      <td>a cete of <a href="https://quod.lib.umich.edu/m/middle-english-dictionary/dictionary/MED19331">greys</a></td>
      <td>a cete of badgers</td>
    </tr>
    <tr>
      <td>a Cherme of Goldefynches</td>
      <td>a charm of goldfinches</td>
      <td>a charm of finches<br />(a glister of goldfinches)</td>
    </tr>
    <tr>
      <td>a Clateryng of choughes</td>
      <td>a clattering of choughs</td>
      <td>a chattering of choughs</td>
    </tr>
    <tr>
      <td>a Congregacion of peple</td>
      <td>a congregation of people</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Congregacion of Pleuers</td>
      <td>a congregation of plovers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Couert of cootis</td>
      <td>a covert of coots</td>
      <td>a cover of coots</td>
    </tr>
    <tr>
      <td>a Couple of rennyng houndis</td>
      <td>a couple of running hounds</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Coupull of ſpaynellis</td>
      <td>a couple of spaniels</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Couy of partrichis</td>
      <td>a covey of partridges</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Cowardnes of curris</td>
      <td>a cowardness of curs</td>
      <td>a cowardice of curs</td>
    </tr>
    <tr>
      <td>a Deſſerte of Lapwyngꝭ</td>
      <td>a desert [<em>sic</em>] of lapwings</td>
      <td>a deceit of lapwings</td>
    </tr>
    <tr>
      <td>a Diſſimulacion of breddis</td>
      <td>a dissimulation of birds</td>
      <td>a dissimulation of birds</td>
    </tr>
    <tr>
      <td>a Droue of Nete</td>
      <td>a drove of <a href="https://en.wiktionary.org/wiki/neat#Old_English">neats</a></td>
      <td>a drove of cattle</td>
    </tr>
    <tr>
      <td>a Dryft of tame Swyne</td>
      <td>a drift of tame swine</td>
      <td>a drift of hogs</td>
    </tr>
    <tr>
      <td>a Duell of Turtillis</td>
      <td>a <a href="https://en.wiktionary.org/wiki/deuil#French">deuil</a> of <a href="https://en.wikipedia.org/wiki/European_turtle_dove">turtles</a></td>
      <td>a true love of turtledoves</td>
    </tr>
    <tr>
      <td>an Exaltyng of Larkis</td>
      <td>an exalting of larks</td>
      <td>an exaltation of larks</td>
    </tr>
    <tr>
      <td>a fall of woodecockis</td>
      <td>a fall of woodcocks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a fflight of Doues</td>
      <td>a flight of doves</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a fflocke of Shepe</td>
      <td>a flock of sheep</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Flight of Goſhaukes</td>
      <td>a flight of goshawks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Flight of ſwalowes</td>
      <td> " of swallows</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Gagle of gees</td>
      <td>a gaggle of geese</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Harraſſe of horſe</td>
      <td>a <a href="https://en.wiktionary.org/wiki/harras">harras</a> of horses</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an hooſt of men</td>
      <td>a host of men</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Oſt of ſparowis</td>
      <td> " of sparrows</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Kenell of Rachis</td>
      <td>a kennel of <a href="https://en.wiktionary.org/wiki/rach#English">raches</a></td>
      <td>a kennel of dogs</td>
    </tr>
    <tr>
      <td>a Kyndyll of yong Cattis</td>
      <td>a kindle of young cats</td>
      <td>a kindle of kittens</td>
    </tr>
    <tr>
      <td>a Labor of Mollis</td>
      <td>a labor of moles</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Lepe of Lebardis</td>
      <td>a leap of leopards</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Litter of welpis</td>
      <td>a litter of whelps</td>
      <td>a litter of pups</td>
    </tr>
    <tr>
      <td>a Murmuracion of ſtares</td>
      <td>a murmuration of starlings</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Muſtre of Pecockys</td>
      <td>a muster of peacocks</td>
      <td>an ostentation of peacocks</td>
    </tr>
    <tr>
      <td>a Mute of houndes</td>
      <td>a mute of hounds</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Neſt of Rabettis</td>
      <td>a nest of rabbits</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Nye of ffeſaunttys</td>
      <td>a nye of pheasants</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Paſe of Aſſis</td>
      <td>a pass of asses</td>
      <td>a pass of asses<br />(1st ed.: “pace”)</td>
    </tr>
    <tr>
      <td>a Pepe of chykennys</td>
      <td>a peep of chickens</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Pride of Lionys</td>
      <td>a pride of lions</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Ragg of coltis<br />or a Rake</td>
      <td>a rag (or rake) of colts</td>
      <td>a rag of colts</td>
    </tr>
    <tr>
      <td>a Riches of Martronys</td>
      <td>a richesse of martens</td>
      <td>a richness of martens</td>
    </tr>
    <tr>
      <td>a Route of woluess</td>
      <td>a rout of wolves</td>
      <td>a route of wolves</td>
    </tr>
    <tr>
      <td>a Scoll of ffyſh</td>
      <td>a school of fish</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sculke of foxis</td>
      <td>a skulk of foxes</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sege of heronnys</td>
      <td>a siege of herons</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sege of betouris</td>
      <td> " of bitterns</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Shrewdenes of Apis</td>
      <td>a shrewdness of apes</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a ſkulke of ffoxis</td>
      <td>a skulk of foxes</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sleuth of Beeris</td>
      <td>a sleuth of bears</td>
      <td>a sloth of bears</td>
    </tr>
    <tr>
      <td>a Sorde or a ſute of malardis</td>
      <td>a sord (or suit) of mallards</td>
      <td>a sorde of mallards<br />(2nd ed.: “sord”)</td>
    </tr>
    <tr>
      <td>a Soundre of wilde ſwyne</td>
      <td>a sounder of wild swine</td>
      <td>a sounder of swine</td>
    </tr>
    <tr>
      <td>a Sprynge of Telis</td>
      <td>a spring of teals</td>
      <td>a spring of teal</td>
    </tr>
    <tr>
      <td>a Stode of Maris</td>
      <td>a stud of mares</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sute of a lyam</td>
      <td>a suit [?] of a <a href="https://en.wiktionary.org/wiki/lyam-hound">lyam</a></td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Swarme of bees</td>
      <td>a swarm of bees</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Synguler of Boris</td>
      <td>a singular of boars</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Titengis of Pies</td>
      <td>a tidings of pies</td>
      <td>a tidings of magpies</td>
    </tr>
    <tr>
      <td>a Trippe of Gete</td>
      <td>a trip of goats</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Trippe of haaris</td>
      <td> " of hares</td>
      <td>a husk (or down or drove) of hares</td>
    </tr>
    <tr>
      <td>an vnkyndenes of Rauenes</td>
      <td>an unkindness of ravens</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a walke of Snytis</td>
      <td>a walk of snipe</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a wache of Nyghtingalis</td>
      <td>a watch of nightingales</td>
      <td>✓</td>
    </tr>
  </tbody>
</table>

<p>The jocular terms for companies of human beings and/or inanimate objects:</p>

<table class="smaller">
  <tbody>
    <tr>
      <td>a Beuy of Ladies</td>
      <td>a bevy of ladies</td>
      <td>a bevy of beauties</td>
    </tr>
    <tr>
      <td>a bhomynable ſight of mõkis</td>
      <td>an abominable sight of monks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Blaſt of hunteris</td>
      <td>a blast of hunters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Bleche of ſowteris</td>
      <td>a blatch of souters</td>
      <td>a blackening of shoemakers</td>
    </tr>
    <tr>
      <td>a bluſh of boyes</td>
      <td>a blush of boys</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Booſt of ſaudiouris</td>
      <td>a boast of soldiers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Caſt of Brede</td>
      <td>a cast of bread</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Charge of curatis</td>
      <td>a charge of curates</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Cluſtre of chorlis</td>
      <td>a cluster of churls</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Cluſtre of Grapys</td>
      <td> " of grapes</td>
      <td>” (or bunch) of grapes</td>
    </tr>
    <tr>
      <td>a Cluſtre of Nottis</td>
      <td> " of knots</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Conuͬtyng of prechouris</td>
      <td>a converting of preachers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Couple or a payer of botillis</td>
      <td>a couple (or pair) of bottles</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Credens of Seweris</td>
      <td>a credence of <a href="https://quod.lib.umich.edu/m/middle-english-dictionary/dictionary/MED39665">sewers</a></td>
      <td>a credence of tasters</td>
    </tr>
    <tr>
      <td>a Dampnyng of Iurrouris</td>
      <td>a damning of jurors</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Dignyte of chanonys</td>
      <td>a dignity of canons</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Diligens of Meſſangeris</td>
      <td>a diligence of messengers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Diſcrecion of Preſtis</td>
      <td>a discretion of priests</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Diſgyſyng of Taylours</td>
      <td>a disguising of tailors</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Diſworſhip of Scottis</td>
      <td>a disworship of Scots</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Doctryne of doctoris</td>
      <td>a doctrine of doctors</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Draught of boteleris</td>
      <td>a draught of butlers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Drifte of fiſhers</td>
      <td>a drift of fishers</td>
      <td>a drift of fishermen</td>
    </tr>
    <tr>
      <td>a Dronkſhip of Coblers</td>
      <td>a drunkship of cobblers</td>
      <td>a drunkenness of cobblers</td>
    </tr>
    <tr>
      <td>an Eloquens of laweyeris</td>
      <td>an eloquence of lawyers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Example of Maiſteris</td>
      <td>an example of masters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Execucion of Officerys</td>
      <td>an execution of officers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a faith of Marchandis</td>
      <td>a faith of merchants</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Feſtre of Brewris</td>
      <td>a fester of brewers</td>
      <td>a <a href="https://en.wiktionary.org/wiki/sester">sester</a> of brewers</td>
    </tr>
    <tr>
      <td>a ffeliſhippyng of yomen</td>
      <td>a fellowshipping of yeomen</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a ffraunch of Mylneris</td>
      <td>a fraunch of millers<br />(not “a french of milliners”!)</td>
      <td>a gobble of millers</td>
    </tr>
    <tr>
      <td>a Fightyng of beggers</td>
      <td>a fighting of beggars</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Gagle of women</td>
      <td>a gaggle of women</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Gloſyng of Tauerneris</td>
      <td>a glosing of taverners</td>
      <td>a cajolery of taverners</td>
    </tr>
    <tr>
      <td>a Goryng of Bochouris</td>
      <td>a goring of butchers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Herde of harlottys</td>
      <td>a herd of harlots</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Kerff of Panteris</td>
      <td>a kerf of panterers</td>
      <td>a slice of pantrymen</td>
    </tr>
    <tr>
      <td>a Laſh of Carteris</td>
      <td>a lash of carters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Laughtre of Oſteloris</td>
      <td>a laughter of ostlers</td>
      <td>a laughter of hostelers</td>
    </tr>
    <tr>
      <td>a Lyeng of pardeneris</td>
      <td>a lying of pardoners</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Malepertnes of pedleres</td>
      <td>a malapertness of pedlars</td>
      <td>an impudence of peddlers</td>
    </tr>
    <tr>
      <td>a Melody of Harpers</td>
      <td>a melody of harpers</td>
      <td>a melody of harpists</td>
    </tr>
    <tr>
      <td>a Miſbeleue of paynteris</td>
      <td>a misbelief of painters</td>
      <td>an illusion of painters</td>
    </tr>
    <tr>
      <td>a Multiplieng of huſbondis</td>
      <td>a multiplying of husbands</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Neuͬthriuyng of Iogoleris</td>
      <td>a neverthriving of jugglers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Noonpaciens of wyues</td>
      <td>a nonpatience of wives</td>
      <td>an impatience of wives</td>
    </tr>
    <tr>
      <td>an Obeiſians of ſͬuauntis</td>
      <td>an obeisance of servants</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>an Obſͬuans of herimytis</td>
      <td>an observance of hermits</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Pauuerty of pypers</td>
      <td>a poverty of pipers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Plocke of Shoturneris</td>
      <td>a pluck of shoeturners</td>
      <td>a plocke of shoeturners</td>
    </tr>
    <tr>
      <td>a Pontificalite of prelatis</td>
      <td>a pontificality of prelates</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Promyſe of Tapſteris</td>
      <td>a promise of tapsters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Proude ſhewyng of taloris</td>
      <td>a proud showing of tailors</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a ꝑuiſion of ſtewardꝭ of hous</td>
      <td>a provision of stewards-of-house</td>
      <td>a provision of stewards</td>
    </tr>
    <tr>
      <td>a Prudens of vikeris</td>
      <td>a prudence of vicars</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Rafull of Knauys</td>
      <td>a raffle of knaves</td>
      <td>a riffraff of knaves</td>
    </tr>
    <tr>
      <td>a Rage of Maydenys</td>
      <td>a rage of maidens</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Rage of the teethe</td>
      <td>a rage of the teeth</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Raſcall of Boyes</td>
      <td>a rascal of boys</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Route of Knyghtis</td>
      <td>a rout of knights</td>
      <td>a route of knights</td>
    </tr>
    <tr>
      <td>a Safegarde of Porteris</td>
      <td>a safeguard of porters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Scoldyng of Kemſteris</td>
      <td>a scolding of <a href="https://en.wiktionary.org/wiki/kembster">kembsters</a></td>
      <td>a scold of seamstresses</td>
    </tr>
    <tr>
      <td>a Scole of clerkes</td>
      <td>a school of clerks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sentence of Iuges</td>
      <td>a sentence of judges</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Sete of vſſheris</td>
      <td>a set (or seat?) of ushers</td>
      <td>a set of ushers</td>
    </tr>
    <tr>
      <td>a Sculke of freris</td>
      <td>a skulk of friars</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Skulke of Theuys</td>
      <td> " of thieves</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Smere of Coryouris</td>
      <td>a smear of curriers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a ſotelty of ſergeauntis</td>
      <td>a subtlety of serjeants</td>
      <td>a subtlety of sergeants</td>
    </tr>
    <tr>
      <td>a ſquatte of Dawberis</td>
      <td>a squat of daubers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Stalke of foſteris</td>
      <td>a stalk of foresters</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a State of Prynces</td>
      <td>a state of princes</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Suꝑfluyte of Nunnys</td>
      <td>a superfluity of nuns</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Tabernacle of bakers</td>
      <td>a tabernacle of bakers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Temꝑans of cokys</td>
      <td>a temperance of cooks</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Thongh of barons</td>
      <td>a thong [<em>sic</em>] of barons</td>
      <td>a troth of barons</td>
    </tr>
    <tr>
      <td>a Thraue of Throſheris</td>
      <td>a thrave of threshers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Thretenyng of courteyeris</td>
      <td>a threatening of courtiers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a Trynket of Corueſeris</td>
      <td>a trinket of corvisers</td>
      <td>a cutting of cobblers</td>
    </tr>
    <tr>
      <td>an vnbrewyng of Kerueris</td>
      <td>an imbruing of carvers</td>
      <td>a mess of carvers</td>
    </tr>
    <tr>
      <td>an vncredibilite of Cocoldis</td>
      <td>an incredibility of cuckolds</td>
      <td>an incredulity of cuckolds</td>
    </tr>
    <tr>
      <td>an vntrouth of ſompneris</td>
      <td>an untruth of summoners</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a waywardnes of haywardis</td>
      <td>a waywardness of haywards</td>
      <td>a waywardness of herdsmen</td>
    </tr>
    <tr>
      <td>a wonderyng of Tynkeris</td>
      <td>a wandering of tinkers</td>
      <td>✓</td>
    </tr>
    <tr>
      <td>a worſhip of writeris</td>
      <td>a worship of writers</td>
      <td>✓</td>
    </tr>
  </tbody>
</table>

<p>Leaving <em>An Exaltation of Larks</em> behind, the next list in the <em>Book of St Albans</em> interested me
because I had serendipitously just seen it quoted in H. H. Furness’s notes on <em>Love’s Labours Lost</em>,
regarding the use of the technical term “break up” in
<a href="https://archive.org/details/bwb_C0-BNX-215/page/117">IV.i</a>:</p>

<blockquote>
  <p>COSTARD: I have a letter from Monsieur Berowne to one Lady Rosaline.</p>

  <p>PRINCESS: O, thy letter, thy letter! He’s a good friend of mine.
Stand aside, good bearer.—Boyet, you can carve;
Break up this capon.</p>
</blockquote>

<p>In fact the <em>Book of St Albans</em> says that a capon is not “broken up” at table but “sawsed.”
One might guess (as Henry Scougal apparently does in his 1789
<a href="https://archive.org/details/bim_eighteenth-century_a-new-academy-of-complim_scougal-henry_1789/page/38"><em>New Academy of Compliments</em></a>)
that that word means “sawn,” i.e. “cut up,” but in fact it means “sauced.”</p>

<p>Many of these terms are repeated, as commands, in Wynken de Worde’s
<a href="https://archive.org/details/boke-of-keruynge/page/n11"><em>Boke of Kervynge</em></a> (1508).</p>

<table class="smaller">
  <thead>
    <tr>
      <th>St Albans</th>
      <th>Modern orthography</th>
      <th>Boke of Kervynge</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>a Dere brokenne</td>
      <td>a deer broken</td>
      <td>Breke that dere</td>
    </tr>
    <tr>
      <td>a Gooſe rerede</td>
      <td>a goose reared</td>
      <td>rere that gooſe</td>
    </tr>
    <tr>
      <td>a Pigge hedede and ſydede</td>
      <td>a pig headed and sided</td>
      <td> </td>
    </tr>
    <tr>
      <td>a Capoon ſawſede</td>
      <td>a capon sauced</td>
      <td>ſauce that capon</td>
    </tr>
    <tr>
      <td>a Checoon fruſſhyd</td>
      <td>a chicken <a href="https://en.wiktionary.org/wiki/frush">frushed</a></td>
      <td>fruche that chekyn</td>
    </tr>
    <tr>
      <td>a Cony vnlaceedde</td>
      <td>a coney unlaced</td>
      <td>vnlace that conye</td>
    </tr>
    <tr>
      <td>a Crane diſplayde</td>
      <td>a crane displayed</td>
      <td>dyſplaye that crane</td>
    </tr>
    <tr>
      <td>a Curlew vnioyntede</td>
      <td>a curlew unjointed</td>
      <td>vntache that curlewe</td>
    </tr>
    <tr>
      <td>a ffeſawnt alet</td>
      <td>a pheasant allayed</td>
      <td>alaye that feſande</td>
    </tr>
    <tr>
      <td>a Quayle wyngged</td>
      <td>a quail winged</td>
      <td>wynge that quayle</td>
    </tr>
    <tr>
      <td>a Plouer Mynſed</td>
      <td>a plover minced</td>
      <td>mynce that plouer</td>
    </tr>
    <tr>
      <td>a Pegeon thyghed</td>
      <td>a pigeon thighed</td>
      <td>thye that pegyon</td>
    </tr>
    <tr>
      <td>Brawne leechyd</td>
      <td><a href="https://en.wikipedia.org/wiki/Head_cheese">brawn</a> <a href="https://archive.org/details/bim_eighteenth-century_le-dictionnaire-royal-fr_boyer-abel_1759_2/page/n509/mode/1up?q=l%C3%A9che">leached</a></td>
      <td>leſche yͭ brawne</td>
    </tr>
    <tr>
      <td>a Swanne lyfte</td>
      <td>a swan lifted</td>
      <td>lyft that ſwanne</td>
    </tr>
    <tr>
      <td>a Lambe ſhulderide</td>
      <td>a lamb shouldered</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Kidde ſhulderide</td>
      <td>a kid "</td>
      <td>—</td>
    </tr>
    <tr>
      <td>an Hen ſpoylede</td>
      <td>a hen spoiled</td>
      <td>ſpoyle that henne</td>
    </tr>
    <tr>
      <td>a Malarde vnbraſid</td>
      <td>a mallard unbraced</td>
      <td>vnbrace that malarde</td>
    </tr>
    <tr>
      <td>an Heron dyſmembrid</td>
      <td>a heron dismembered</td>
      <td>dyſmembre that heron</td>
    </tr>
    <tr>
      <td>a Pecoke diſfigured</td>
      <td>a peacock disfigured</td>
      <td>dyſfygure that pecocke</td>
    </tr>
    <tr>
      <td>a Beture vntachid</td>
      <td>a bittern untached</td>
      <td>vnioynt that bytture</td>
    </tr>
    <tr>
      <td>a Partrich alet</td>
      <td>a partridge allayed</td>
      <td>wynge that partryche</td>
    </tr>
    <tr>
      <td>a Raale breſtyde</td>
      <td>a rail breasted</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Wodecoke thyghed</td>
      <td>a woodcock thighed</td>
      <td>thye that woodcocke</td>
    </tr>
    <tr>
      <td>—</td>
      <td>(thigh all manner of small birds)</td>
      <td>thye all maner ſmall byrdes</td>
    </tr>
    <tr>
      <td>A Sawmon Chyned</td>
      <td>a salmon chined</td>
      <td>chynne that ſamon</td>
    </tr>
    <tr>
      <td>a Pyke ſplatted</td>
      <td>a pike splatted</td>
      <td>ſplatte that pyke</td>
    </tr>
    <tr>
      <td>an Haddoke ſided</td>
      <td>a haddock sided</td>
      <td>ſyde that haddocke</td>
    </tr>
    <tr>
      <td>a Cheuen fynned</td>
      <td>a <a href="https://en.wiktionary.org/wiki/chevin">chevin</a> finned</td>
      <td>fynne that cheuen</td>
    </tr>
    <tr>
      <td>a Sole loyned</td>
      <td>a sole loined</td>
      <td>—</td>
    </tr>
    <tr>
      <td>a Gurnarde chyned</td>
      <td>a <a href="https://en.wikipedia.org/wiki/Grey_gurnard">gurnard</a> chined</td>
      <td> </td>
    </tr>
    <tr>
      <td>—</td>
      <td>a <a href="https://en.wiktionary.org/wiki/plaice">plaice</a> sauced</td>
      <td>ſauce that place</td>
    </tr>
    <tr>
      <td>a Tenche ſawced</td>
      <td>a tench sauced</td>
      <td>ſauce that tenche</td>
    </tr>
    <tr>
      <td>an Ele trouſoned</td>
      <td>an eel trassened</td>
      <td>traſſene that ele</td>
    </tr>
    <tr>
      <td>a Breme ſplayed</td>
      <td>a bream splayed</td>
      <td>ſplaye that breme</td>
    </tr>
    <tr>
      <td>a Barbill tuſkyd</td>
      <td>a barbel tusked</td>
      <td>tuſke that berbell</td>
    </tr>
    <tr>
      <td>a Trought gobettid</td>
      <td>a trout gobbeted<br />or <a href="https://archive.org/details/newdictionaryofe01rich/page/459">culponed</a></td>
      <td>culpon that troute</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a lamprey strung</td>
      <td>ſtrynge that lampraye</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a sturgeon <a href="https://en.wiktionary.org/wiki/tranch">tranched</a></td>
      <td>traunche that ſturgyon</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a porpoise undertranched</td>
      <td>vndertraunche that purpos</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a crab tamed</td>
      <td>tayme that crabbe</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a lobster barbed</td>
      <td>barbe that lopſter</td>
    </tr>
    <tr>
      <td>an Egge Tyred</td>
      <td>an egg tired</td>
      <td>tyere that egge</td>
    </tr>
    <tr>
      <td>—</td>
      <td>a pasty bordered</td>
      <td>border that paſty</td>
    </tr>
    <tr>
      <td>a ffyre Tymbered</td>
      <td>the fire timbered</td>
      <td>tymbre that fyre</td>
    </tr>
  </tbody>
</table>

<p>Note the difference between <em>St Albans’</em> “an ele trousoned” and <em>Kervynge’s</em> “trassene that ele.”
Charles Cooper in <em>The English Table in History and Literature</em> (1929) splits the difference
and <a href="https://archive.org/details/b3135533x/page/20">writes</a> “<em>traunsene</em> an eel.”
A comic poem in the <em>Literary Gazette</em> of 1827-06-23 <a href="https://archive.org/details/sim_literary-gazette_1827-06-23_544/page/396">has</a> “eels were <em>transened</em>.”
A correspondent in <em>Notes &amp; Queries</em> (January 1918) <a href="https://archive.org/details/s12notesqueries04londuoft/s12notesqueries04londuoft/page/26">mangles it</a>
all the way to “<em>transom</em> an eel.” I’ve gone with “trassened” for no particular reason.</p>

<p>Note that the two <em>Bokes</em> swap their verbs for bittern and curlew: you <em>unjoint</em> the one and <em>untach</em> the other,
but they can’t decide which is which. Some modernizations have “<em>untack</em> that curlew,” but <em>untach</em>
(like <em>tranch</em>) seems to be good old French: already in Middle English <em>tachen</em> means
“<a href="https://quod.lib.umich.edu/m/middle-english-dictionary/dictionary/MED44371">pin together</a>,”
and so <em>untach</em> would mean “take apart.” (Another sense of <em>tachen</em>
<a href="https://quod.lib.umich.edu/m/middle-english-dictionary/dictionary/MED44373">relates</a>
to a visible <em>token</em> predictive of <a href="https://www.etymonline.com/word/tetchy"><em>tetchiness.</em></a>)</p>

<hr />

<p>Finally, the <em>Book of St Albans</em> provides a short list of words for when animals bed down
for the night:</p>

<table class="smaller">
  <tbody>
    <tr>
      <td>An hert <a href="https://quod.lib.umich.edu/m/middle-english-dictionary/dictionary/MED20464">Herbourghith</a></td>
      <td>a hart harbors</td>
    </tr>
    <tr>
      <td>a Bucke lodgith</td>
      <td>a buck lodges</td>
    </tr>
    <tr>
      <td>an Eſquyer lodgith</td>
      <td>a squire "</td>
    </tr>
    <tr>
      <td>a Roo beddith</td>
      <td>a roe beds</td>
    </tr>
    <tr>
      <td>a yoman beddith</td>
      <td>a yeoman "</td>
    </tr>
    <tr>
      <td>an haare in her forme<br />ſhulderyng or leenyng</td>
      <td>a hare in her <a href="https://archive.org/details/bim_eighteenth-century_the-sportsmans-dictiona_1735_1/page/n239/mode/1up">form</a><br />shouldering or leaning</td>
    </tr>
    <tr>
      <td>a Cony ſittyng</td>
      <td>a coney sitting</td>
    </tr>
    <tr>
      <td>a Wodecoke beekyng</td>
      <td>a woodcock <a href="https://en.wiktionary.org/wiki/beek">beeking</a></td>
    </tr>
  </tbody>
</table>

<p>The <em>hare in her form</em> is proverbial for being snug-fitting and inconspicuous
(like a <a href="https://en.wikipedia.org/wiki/Aplysiida">sea hare</a> in its own habitat),
as in Jonathan Swift’s <a href="https://archive.org/details/bim_eighteenth-century_the-poetical-works-of-j_swift-jonathan_1736/page/160">poem</a>
on mint-master for Ireland <a href="https://en.wikipedia.org/wiki/William_Wood_(ironmaster)">William Wood</a> (1725):</p>

<blockquote>
  <p>The next is an Insect we call a Wood-worm,<br />
That lies in old Wood like a Hare in her Form.</p>
</blockquote>

<p>The shallow temporary scrapes of rabbit or roe posed a hazard to hikers: stepping into one
and taking a tumble, one might find oneself “in a scrape.”
(<em>Eclectic Magazine</em> <a href="https://archive.org/details/sim_eclectic-magazine-of-foreign-literature_1865-07_2_1/page/87">July 1865</a>.)
Similarly, if one took a tumble aboard ship, one might be said to be “scuppered.”
(<a href="https://archive.org/details/sim_notes-and-queries_1910-10-08_2_41/page/298">“Scupper,”</a> <em>Notes &amp; Queries</em> 1910-10-08).</p>

<p><em>A</em> to <em>B:</em> “Mr. <em>C</em> reminds me of a hare.” — “In what way?” — “In that he’ll always lie in a scrape.”</p>

<p>The opposite of “in her form” is <a href="https://archive.org/details/bim_early-english-books-1641-1700_a-new-dictionary-french-_miege-guy_1677/page/n369/mode/1up">“in relief,”</a>
i.e., “standing out” (cf. <em>bas-relief</em>). Reverse the syllables and you get <em>lièvre</em>, the French for “hare,”
which relates to <em>livre</em> as <em>coney</em> does to <em>coin</em> (i.e., not at all).</p>]]></content><author><name></name></author><category term="etymology" /><category term="litclub" /><category term="old-shit" /><category term="transcription" /><category term="word-ways" /><summary type="html"><![CDATA[This week I read James Lipton’s An Exaltation of Larks; or, The Venereal Game (2nd edition, 1977), a discursive collection and celebration of “terms of venery” — the collective nouns like “school of fish” and “pride of lions” that were (so the story goes) mostly invented by medieval hunters who wanted to have their own proper jargon to distinguish the real hunters from the dilettantes.]]></summary></entry><entry><title type="html">`std::is_heap` could be faster</title><link href="https://quuxplusone.github.io/blog/2026/05/11/is-heap/" rel="alternate" type="text/html" title="`std::is_heap` could be faster" /><published>2026-05-11T00:01:00+00:00</published><updated>2026-05-11T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/11/is-heap</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/11/is-heap/"><![CDATA[<p>The other day I was noodling around with some libc++ unit-test code that looked
roughly like this (<a href="https://godbolt.org/z/cGWcrdjcs">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class A&gt;
auto extract_container(A&amp; a) {
  struct UnwrapAdaptor : A { A::container_type&amp; cc = A::c; };
  return UnwrapAdaptor(a).cc;
}

template&lt;class Adaptor&gt;
void test_push_range(bool is_heapified) {
  int in1[] = {1,3,7};
  int in2[] = {2,4,5,6};
  int expected[] = {1,3,7,2,4,5,6};
  Adaptor a;
  a.push_range(in1);
  a.push_range(in2);
  if (auto c = extract_container(a); is_heapified) {
    assert(std::ranges::is_heap(c));
    assert(std::ranges::is_permutation(c, expected));
  } else {
    assert(std::ranges::equal(c, expected));
  }
}

int main() {
  test_push_range&lt;std::stack&lt;int&gt;&gt;(false);
  test_push_range&lt;std::queue&lt;int&gt;&gt;(false);
  test_push_range&lt;std::priority_queue&lt;int&gt;&gt;(true);
}
</code></pre></div></div>

<p>I tried to extend <code class="language-plaintext highlighter-rouge">main</code> to test also some non-default containers.
(Incidentally, did you know <code class="language-plaintext highlighter-rouge">stack</code>’s default container is <code class="language-plaintext highlighter-rouge">deque</code>,
not <code class="language-plaintext highlighter-rouge">vector</code>?)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  test_push_range&lt;std::stack&lt;int, std::vector&lt;int&gt;&gt;&gt;(false);
  test_push_range&lt;std::stack&lt;int, std::list&lt;int&gt;&gt;&gt;(false);
</code></pre></div></div>

<p>…And suddenly the unit test no longer compiled!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error: no matching function for call to object of type 'const __is_heap'
   22 |     assert(std::ranges::is_heap(c));
      |            ^~~~~~~~~~~~~~~~~~~~
[...]
note: because 'std::list&lt;int&gt; &amp;' does not satisfy 'random_access_range'
</code></pre></div></div>

<p>That caught me by surprise. Why should testing whether a range is heapified
require random access? It’s simple to implement with only forward traversal,
and <code class="language-plaintext highlighter-rouge">make_heap</code>/<code class="language-plaintext highlighter-rouge">is_heap</code> seems to analogize perfectly against <code class="language-plaintext highlighter-rouge">sort</code>/<code class="language-plaintext highlighter-rouge">is_sorted</code>.
<code class="language-plaintext highlighter-rouge">is_sorted</code> doesn’t require random access; why should <code class="language-plaintext highlighter-rouge">is_heap</code>?</p>

<table>
  <thead>
    <tr>
      <th>Ranges algorithm</th>
      <th>Constraint</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sort</code></td>
      <td><code class="language-plaintext highlighter-rouge">random_access_range</code></td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">is_sorted</code></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">is_sorted_until</code></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">make_heap</code></td>
      <td><code class="language-plaintext highlighter-rouge">random_access_range</code></td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">is_heap</code></td>
      <td><code class="language-plaintext highlighter-rouge">random_access_range</code> (could be <code class="language-plaintext highlighter-rouge">forward_range</code>)</td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">is_heap_until</code></td>
      <td><code class="language-plaintext highlighter-rouge">random_access_range</code> (could be <code class="language-plaintext highlighter-rouge">forward_range</code>)</td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">partition</code></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">is_partitioned</code></td>
      <td><code class="language-plaintext highlighter-rouge">input_range</code></td>
    </tr>
    <tr>
      <td>  <a href="https://www.boost.org/doc/libs/latest/libs/algorithm/doc/html/algorithm/Misc.html#the_boost_algorithm_library.Misc.misc_inner_algorithms.is_partitioned_until"><code class="language-plaintext highlighter-rouge">is_partitioned_until</code></a></td>
      <td><code class="language-plaintext highlighter-rouge">input_range</code></td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">unique</code></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
    <tr>
      <td>  <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2848r1.html"><code class="language-plaintext highlighter-rouge">is_uniqued</code></a></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
    <tr>
      <td>  <code class="language-plaintext highlighter-rouge">adjacent_find</code></td>
      <td><code class="language-plaintext highlighter-rouge">forward_range</code></td>
    </tr>
  </tbody>
</table>

<p>Note that <code class="language-plaintext highlighter-rouge">is_partitioned</code> only needs to view one element at a time, and remember
the boolean value of <code class="language-plaintext highlighter-rouge">pred(elt)</code>. By contrast, <code class="language-plaintext highlighter-rouge">is_sorted</code>, <code class="language-plaintext highlighter-rouge">adjacent_find</code>, and <code class="language-plaintext highlighter-rouge">is_heap</code>
need to view two elements at a time; that’s why they can’t handle <code class="language-plaintext highlighter-rouge">input_range</code>.</p>

<p>The two “could bes” in the table above seem to indicate a design defect.</p>

<h2 id="benchmark-it">Benchmark it!</h2>

<p>libc++’s implementation of <code class="language-plaintext highlighter-rouge">std::is_heap</code> looks <a href="https://github.com/llvm/llvm-project/blob/17e0686ab1107a1a675d8783383dedf70fa24033/libcxx/include/__algorithm/is_heap_until.h#L23-L46">shockingly inefficient</a>:
its 24 lines spend a lot of time recomputing subexpressions like <code class="language-plaintext highlighter-rouge">__first + __c</code> (to get to the <code class="language-plaintext highlighter-rouge">c</code>’th element)
and <code class="language-plaintext highlighter-rouge">2 * __p + 1</code> (to compute the child index from the parent). A straight-line implementation,
avoiding all arithmetic and just advancing two iterators in lockstep,
would have taken only 18 lines:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template &lt;class _Compare, class _ForwardIterator, class _Sentinel&gt;
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _ForwardIterator
__is_heap_until(_ForwardIterator __first, _Sentinel __last, _Compare&amp;&amp; __comp) {
  _ForwardIterator __child = __first;
  if (__child == __last) {
    return __child;
  }
  while (true) {
    ++__child;
    if (__child == __last || __comp(*__first, *__child))
      break;
    ++__child;
    if (__child == __last || __comp(*__first, *__child))
      break;
    ++__first;
  }
  return __child;
}
</code></pre></div></div>

<p>Surprisingly, <a href="https://github.com/gcc-mirror/gcc/blob/319c0f0249cb30c2290426a3a9d4ce81a47a684d/libstdc%2B%2B-v3/include/bits/stl_heap.h#L72-L94">libstdc++</a>
and <a href="https://github.com/microsoft/STL/blob/main/stl/inc/algorithm#L7667-L7679">MS STL</a> also spend time adding and dividing
(or shifting) when they don’t need to.</p>

<p>Still, just because a piece of code <em>looks</em> inefficient doesn’t always mean that it <em>is</em> inefficient.
So I whipped up a simple benchmark:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void BM_vector_half(benchmark::State&amp; state) {
  auto n = state.range(0);
  auto v = std::vector&lt;unsigned&gt;(n);
  std::mt19937 g;
  std::generate(v.begin(), v.end(), g);
  std::make_heap(v.begin(), v.begin() + (n / 2));
  for (auto _ : state) {
    benchmark::DoNotOptimize(v);
    auto it = std::is_heap_until(v.begin(), v.end());
    benchmark::DoNotOptimize(it);
  }
}
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">vector_full</code> is the same but with <code class="language-plaintext highlighter-rouge">make_heap(v.begin(), v.end())</code>.
<code class="language-plaintext highlighter-rouge">deque_{half,full}</code> are the same but with <code class="language-plaintext highlighter-rouge">deque</code> instead of <code class="language-plaintext highlighter-rouge">vector</code>.</p>

<p>Here’s the benchmark result before and after applying the patch above to libc++.
In this case, our eyes don’t lie: what <em>looks</em> inefficient <em>is</em> inefficient!
(Note that our comparator here is <code class="language-plaintext highlighter-rouge">less&lt;int&gt;</code>, which is cheap. An expensive comparator
could dwarf the cost of arithmetic, making the benefit of this patch less perceptible.)</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Benchmark</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">n</code></th>
      <th style="text-align: right">CPU time (before)</th>
      <th style="text-align: right">CPU time (after)</th>
      <th style="text-align: right">%</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_half</code></td>
      <td style="text-align: left">1K</td>
      <td style="text-align: right">4155 ns</td>
      <td style="text-align: right">2658 ns</td>
      <td style="text-align: right">−36%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_half</code></td>
      <td style="text-align: left">100K</td>
      <td style="text-align: right">311560 ns</td>
      <td style="text-align: right">263807 ns</td>
      <td style="text-align: right">−15%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_half</code></td>
      <td style="text-align: left">10M</td>
      <td style="text-align: right">32487789 ns</td>
      <td style="text-align: right">26089364 ns</td>
      <td style="text-align: right">−19%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_full</code></td>
      <td style="text-align: left">1K</td>
      <td style="text-align: right">8813 ns</td>
      <td style="text-align: right">5294 ns</td>
      <td style="text-align: right">−39%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_full</code></td>
      <td style="text-align: left">100K</td>
      <td style="text-align: right">644535 ns</td>
      <td style="text-align: right">535987 ns</td>
      <td style="text-align: right">−16%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vector_full</code></td>
      <td style="text-align: left">10M</td>
      <td style="text-align: right">59771100 ns</td>
      <td style="text-align: right">52807359 ns</td>
      <td style="text-align: right">−11%</td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_half</code></td>
      <td style="text-align: left">1K</td>
      <td style="text-align: right">4186 ns</td>
      <td style="text-align: right">2662 ns</td>
      <td style="text-align: right">−36%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_half</code></td>
      <td style="text-align: left">100K</td>
      <td style="text-align: right">375844 ns</td>
      <td style="text-align: right">264856 ns</td>
      <td style="text-align: right">−29%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_half</code></td>
      <td style="text-align: left">10M</td>
      <td style="text-align: right">31999750 ns</td>
      <td style="text-align: right">26152052 ns</td>
      <td style="text-align: right">−18%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_full</code></td>
      <td style="text-align: left">1K</td>
      <td style="text-align: right">9002 ns</td>
      <td style="text-align: right">5271 ns</td>
      <td style="text-align: right">−41%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_full</code></td>
      <td style="text-align: left">100K</td>
      <td style="text-align: right">619876 ns</td>
      <td style="text-align: right">523727 ns</td>
      <td style="text-align: right">−15%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">deque_full</code></td>
      <td style="text-align: left">10M</td>
      <td style="text-align: right">65697333 ns</td>
      <td style="text-align: right">52488343 ns</td>
      <td style="text-align: right">−20%</td>
    </tr>
  </tbody>
</table>

<p>Even with the current specification of <code class="language-plaintext highlighter-rouge">is_heap</code>, we can achieve this performance today.
The algorithm can remain <em>constrained</em> on <code class="language-plaintext highlighter-rouge">random_access_range</code>
(thus doing nothing to solve the motivating use-case that introduced this post)
while internally <em>using</em> nothing more than forward-iterator operations.
But once the algorithm uses nothing more than forward-iterator operations…
wouldn’t it be nice for the Standard to say so?</p>

<h2 id="conclusions">Conclusions</h2>

<ul>
  <li>
    <p>libc++ should improve its <code class="language-plaintext highlighter-rouge">is_heap</code> implementation along the lines above,
  reaping the performance benefit.</p>
  </li>
  <li>
    <p>So should libstdc++ and Microsoft STL, I expect.</p>
  </li>
  <li>
    <p>WG21 should consider relaxing the constraint on <code class="language-plaintext highlighter-rouge">is_heap</code> and <code class="language-plaintext highlighter-rouge">is_heap_until</code> from “random access” to “forward,”
  incidentally solving my original use-case. I’ll probably bring a paper to this effect at some point.
  Note that if such a paper were adopted, all three vendors would <em>have</em> to change their
  implementations to the faster one.</p>
  </li>
</ul>]]></content><author><name></name></author><category term="benchmarks" /><category term="library-design" /><category term="proposal" /><category term="stl-classic" /><summary type="html"><![CDATA[`is_sorted` doesn't require random access; why should `is_heap`?]]></summary></entry><entry><title type="html">Two-Minute _Iolanthe_</title><link href="https://quuxplusone.github.io/blog/2026/05/08/two-minute-iolanthe/" rel="alternate" type="text/html" title="Two-Minute _Iolanthe_" /><published>2026-05-08T00:01:00+00:00</published><updated>2026-05-08T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/08/two-minute-iolanthe</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/08/two-minute-iolanthe/"><![CDATA[<p>The other day I came across Connie Kleinjans’ <a href="https://www.misosoup.com/connie/TwoMin/TwoMin.html">page of “two-minute versions”</a>
of G&amp;S shows. She’s got two versions of <em>Gondoliers</em> and one each of <em>Iolanthe</em> and <em>Ruddigore</em>.
The technique is the same as in <a href="https://en.wikipedia.org/wiki/Blackout_poetry">blackout poetry</a>:
take the whole work and black out all but the most important and/or funniest bits.</p>

<p><img src="/blog/images/2026-05-08-iolanthe-blackout.png" alt="" /></p>

<p>Kleinjans’ short scripts are funny in their own rights, but I wanted audio versions; so I made one.
Presenting <a href="https://www.youtube.com/watch?v=lhXP-kBlt5k">“Two-Minute <em>Iolanthe</em> in five minutes”</a>.</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/lhXP-kBlt5k" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>The <a href="https://www.youtube.com/watch?v=DLYSZN2IqeU">original recording</a> I “blacked out” for this
video is a TV broadcast of a 1976 production at the Sydney Opera House featuring Rosemary Gunn
(Iolanthe), Heather Begg (Fairy Queen), Dennis Olsen (the Lord Chancellor), June Bronhill (Phyllis),
Lyndon Terracini (Strephon), Graeme Ewer (Mountararat), Ronald Maconaghie (Tolloller), and
Alan Light (Private Willis). This is a low-quality VHS rip of an excellent performance.</p>

<p>The VHS rip on YouTube is missing a chunk of the finale, which
prompted some alterations to the script; I made a few other alterations for pacing. Even after
those cuts, this “two-minute” <em>Iolanthe</em> is almost five minutes long; watch at 2.3x speed for
a true two-minute experience.</p>

<hr />

<p>To create the video, I used <code class="language-plaintext highlighter-rouge">ffmpeg</code> to clip and concat the snippets. When concatenating 169 tiny snippets,
your biggest problem will be “timestamp drift” between the audio and video channels. I spent a long
time cajoling ChatGPT into giving me new permutations of command-line switches to try before finally
settling on the programs linked below.</p>

<p>Step 1 was to get the original video (2.4 gigabytes, saved as <code class="language-plaintext highlighter-rouge">input.mkv</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install ffmpeg yt-dlp
yt-dlp 'https://www.youtube.com/watch?v=DLYSZN2IqeU'
</code></pre></div></div>

<p>Step 2 was to snip the constituent bits via <code class="language-plaintext highlighter-rouge">ffmpeg</code> commands. I factored out the common arguments
into environment variables so I didn’t have to keep typing or tabbing over them.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PREFIX="-y"
SUFFIX="-i input.mkv -c:v libx264 -preset veryfast -crf 20 -c:a aac"
ffmpeg $PREFIX -ss 00:07:07.2 -to 00:07:10.5 $SUFFIX part001.mkv
ffmpeg $PREFIX -ss 00:07:45.0 -to 00:07:48.6 $SUFFIX part002.mkv
~~~~
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ffmpeg</code> turns out to be supremely sensitive to whether you put the input
(<code class="language-plaintext highlighter-rouge">-i input.mkv</code>) before, or after, the <code class="language-plaintext highlighter-rouge">-ss</code> and <code class="language-plaintext highlighter-rouge">-to</code> switches. With <code class="language-plaintext highlighter-rouge">-i input.mkv</code> as
part of the <code class="language-plaintext highlighter-rouge">SUFFIX</code>, my whole script.txt runs in 61 seconds; as part of the <code class="language-plaintext highlighter-rouge">PREFIX</code>,
it takes 98 <em>minutes.</em></p>

<p>To concatenate all those clips and re-encode a “preview” video, we can do this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm list.txt
for i in part*.mkv ; do echo "file $i" &gt;&gt;list.txt ; done
ffmpeg -y -f concat -safe 0 -i list.txt \
  -c:v libx264 -crf 20 -preset veryfast -c:a aac -ar 48000 output.mp4
</code></pre></div></div>

<p>Step 3 was to fight timestamp desynchronization. My solution here was generated entirely
by blind fumbling and incantations, with input from ChatGPT. It seems that there are
basically two ways to get <code class="language-plaintext highlighter-rouge">ffmpeg</code> to “supercut” a video as we’re doing here: either
clip out the clips into temporary files and then concatenate all those little files
(as we did above — this way causes a lot of drift), or do one big “filter” operation
to take just the frames you care about in a single <code class="language-plaintext highlighter-rouge">ffmpeg</code> invocation. That looks like
this, except with 169 clips instead of 3:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg -y -i input.mkv -filter_complex \
 "[0:v]trim=start=427.2:end=430.5,setpts=PTS-STARTPTS[v0];
  [0:a]atrim=start=427.2:end=430.5,asetpts=PTS-STARTPTS[a0];
  [0:v]trim=start=465.0:end=468.6,setpts=PTS-STARTPTS[v1];
  [0:a]atrim=start=465.0:end=468.6,asetpts=PTS-STARTPTS[a1];
  [0:v]trim=start=616.5:end=618.7,setpts=PTS-STARTPTS[v2];
  [0:a]atrim=start=616.5:end=618.7,asetpts=PTS-STARTPTS[a2];
  [v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]"
  -map [v] -map [a] \
  -c:v libx264 -crf 20 -preset veryfast \
  -c:a aac -b:a 192k -ar 48000 \
  output.mp4
</code></pre></div></div>

<p>That’s horribly slow; it seems to have quadratic behavior as the number of clips
increases. Even worse is trying to use the non-“<code class="language-plaintext highlighter-rouge">complex</code>” filter options <code class="language-plaintext highlighter-rouge">-vf</code> and <code class="language-plaintext highlighter-rouge">-af</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg -y -i input.mkv \
  -vf "select=between(t,427.2,430.5)+between(t,465.0,468.6)+between(t,616.5,618.7),setpts=N/FRAME_RATE/TB" \
  -af "aselect=between(t,427.2,430.5)+between(t,465.0,468.6)+between(t,616.5,618.7),asetpts=N/SR/TB" \
  -c:v libx264 -crf 20 -preset veryfast \
  -c:a aac -b:a 192k -ar 48000 \
  output.mp4
</code></pre></div></div>

<p>That just makes <code class="language-plaintext highlighter-rouge">ffmpeg</code> run out of memory before you’ve even hit 50 clips.
So I ended up using a hybrid approach: I used <code class="language-plaintext highlighter-rouge">-filter_complex</code> to produce
nine intermediate concatenations of 20 clips at a time, and then used</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg -y -f concat -i list.txt -c copy output.mp4
</code></pre></div></div>

<p>to paste those nine files together. I’ve saved my programs for posterity:
<a href="/blog/code/2026-05-08-iolanthe-script.txt">script.txt</a> runs as a Bash script
(in just over 1 minute on my machine) to create that “draft preview” video output;
its textual contents also serve as input to <a href="/blog/code/2026-05-08-iolanthe-script.py">script.py</a>,
which creates the final product (in about 9 minutes) using the two-level <code class="language-plaintext highlighter-rouge">-filter_complex</code>
approach. The finished <code class="language-plaintext highlighter-rouge">output.mp4</code> is about 42 megabytes in size.</p>]]></content><author><name></name></author><category term="gilbert-and-sullivan" /><category term="how-to" /><category term="jokes" /><category term="memes" /><category term="television" /><category term="web" /><summary type="html"><![CDATA[The other day I came across Connie Kleinjans’ page of “two-minute versions” of G&amp;S shows. She’s got two versions of Gondoliers and one each of Iolanthe and Ruddigore. The technique is the same as in blackout poetry: take the whole work and black out all but the most important and/or funniest bits.]]></summary></entry><entry><title type="html">C++ Alignment Chart</title><link href="https://quuxplusone.github.io/blog/2026/05/06/cpp-alignment-chart/" rel="alternate" type="text/html" title="C++ Alignment Chart" /><published>2026-05-06T00:01:00+00:00</published><updated>2026-05-06T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/06/cpp-alignment-chart</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/06/cpp-alignment-chart/"><![CDATA[<p><img src="/blog/images/2026-05-06-alignment-chart.jpg" alt="" class="meme" /></p>]]></content><author><name></name></author><category term="alignment" /><category term="memes" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">ELF’s ways to combine potentially non-unique objects</title><link href="https://quuxplusone.github.io/blog/2026/05/05/potentially-nonunique-strategies/" rel="alternate" type="text/html" title="ELF’s ways to combine potentially non-unique objects" /><published>2026-05-05T00:01:00+00:00</published><updated>2026-05-05T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/05/05/potentially-nonunique-strategies</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/05/05/potentially-nonunique-strategies/"><![CDATA[<p>Previously <a href="/blog/2026/04/24/define-static-array/">I wrote</a>:</p>

<blockquote>
  <p>[Template parameter objects of array type] are permitted to overlap or be
coalesced, just like <code class="language-plaintext highlighter-rouge">initializer_list</code>s and string literals. Clang trunk
isn’t smart enough to coalesce potentially non-unique objects [but]
GCC, once it implements <code class="language-plaintext highlighter-rouge">define_static_array</code>, will presumably make them the same.</p>
</blockquote>

<p>Well, GCC 16 has an experimental implementation of <code class="language-plaintext highlighter-rouge">define_static_array</code>
(compile with <code class="language-plaintext highlighter-rouge">g++ -std=c++26 -freflection</code>), and it does <em>not</em> coalesce
template parameter objects of array type in the way I expected. Digging deeper
into why not, I learned that there are at least three ways compilers and linkers
(on ELF — that is, non-Windows — platforms) conspire to “merge”
potentially non-unique objects:</p>

<ul>
  <li>Merging at the compiler level (for <code class="language-plaintext highlighter-rouge">initializer_list</code> backing arrays)</li>
  <li>Sections with <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> (for string literals and backing arrays)</li>
  <li>Sections with <code class="language-plaintext highlighter-rouge">SHF_GROUP</code>, a.k.a. COMDAT sections (for inline variables)</li>
</ul>

<p>Sadly, no combination of these facilities <em>quite</em> achieves
ideal behavior for <code class="language-plaintext highlighter-rouge">define_static_array</code>. Let’s take a look.</p>

<h2 id="the-compiler-can-merge-similar-data">The compiler can merge similar data</h2>

<p>GCC itself merges similar <code class="language-plaintext highlighter-rouge">initializer_list</code> backing arrays. For example
(<a href="https://godbolt.org/z/EnzxoM9ch">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void f(std::initializer_list&lt;int&gt;);
int main() {
  f({1,2,3}); // C.0.0
  f({1,2,3}); // C.1.1
}
</code></pre></div></div>

<p>turns into the assembly directives</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.cst16,"aM",@progbits,16
  .align 8
  .type C.0.0, @object
  .size C.0.0, 12
C.0.0:
  .long 1
  .long 2
  .long 3
  .zero 4
  .set C.1.1,C.0.0
</code></pre></div></div>

<p>The symbols <code class="language-plaintext highlighter-rouge">C.0.0</code> and <code class="language-plaintext highlighter-rouge">C.1.1</code> are set to the same memory address, because GCC
itself can see that the two <code class="language-plaintext highlighter-rouge">initializer_list</code> objects should have the same backing
array.</p>

<p>This is the most powerful approach, but at the same time the least elegant, because
it requires ad-hoc “smarts” built directly into the compiler. For example, we
could imagine GCC generating code that merges one list into the tail of another:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void f(std::initializer_list&lt;int&gt;);
int main() {
  f({1,2,3}); // C.0.0
  f({2,3});   // C.2.2
}

C.0.0:
  .long 1
  .long 2
  .long 3
  .zero 4
  .set C.2.2,C.0.0 + 4
</code></pre></div></div>

<p>but in fact GCC doesn’t generate that code, because nobody has taught GCC that specific
trick. Nor will GCC 16 merge the backing arrays of <code class="language-plaintext highlighter-rouge">{1,2,3}</code> and <code class="language-plaintext highlighter-rouge">{1u,2u,3u}</code> using
this technique, again because it hasn’t been taught to.</p>

<p>Merging things at the compiler level also, by definition, works only within a single
translation unit (a single .cpp file). If you want to merge things between different TUs,
you’ll need help from the linker. Which brings us to…</p>

<h2 id="shf_merge-sections"><code class="language-plaintext highlighter-rouge">SHF_MERGE</code> sections</h2>

<p>I said GCC 16 wouldn’t merge <code class="language-plaintext highlighter-rouge">{1,2,3}</code> and <code class="language-plaintext highlighter-rouge">{1u,2u,3u}</code> in the compiler. But if you
try this program (<a href="https://godbolt.org/z/MqxsPE98z">Godbolt</a>), you’ll see that the
backing arrays are indeed merged at runtime — the same pointer value is printed twice:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;class... Ts&gt;
void f(std::initializer_list&lt;Ts&gt;... ils) {
  (printf("%p\n", (const void*)ils.begin()) , ...);
}
int main() {
  f({1,2,3},     // C.0.0
    {1u,2u,3u}); // C.1.1
}
</code></pre></div></div>

<p>The same pointer value is printed twice, despite that we can see GCC emitting two
different objects into the assembly file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.cst16,"aM",@progbits,16
  .align 8
  .type C.0.0, @object
  .size C.0.0, 12
C.0.0:
  .long 1
  .long 2
  .long 3
  .zero 4
  .align 8
  .type C.1.1, @object
  .size C.1.1, 12
C.1.1:
  .long 1
  .long 2
  .long 3
  .zero 4
</code></pre></div></div>

<p>The trick here is in the <code class="language-plaintext highlighter-rouge">.section</code> directive, which creates an ELF section in the
object file with the name <code class="language-plaintext highlighter-rouge">.rodata.cst16</code>, the section flag <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> (that’s the <code class="language-plaintext highlighter-rouge">M</code>
in <code class="language-plaintext highlighter-rouge">"aM"</code>), and a <code class="language-plaintext highlighter-rouge">sh_entsize</code> of <code class="language-plaintext highlighter-rouge">16</code> bytes. After concatenating every object file’s
<code class="language-plaintext highlighter-rouge">.rodata.cst16</code> sections as usual, the linker is permitted (but not required) to treat
the contents of this section as an array of 16-byte elements, and to deduplicate any
identical elements it finds. Since the 16-byte region starting at <code class="language-plaintext highlighter-rouge">C.1.1</code> matches the
16-byte region starting at <code class="language-plaintext highlighter-rouge">C.0.0</code>, the linker is allowed to eliminate the 16 bytes
at <code class="language-plaintext highlighter-rouge">C.1.1</code> and point the label <code class="language-plaintext highlighter-rouge">C.1.1</code> at <code class="language-plaintext highlighter-rouge">C.0.0</code> (or vice versa).</p>

<p>The <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> trick works across TUs, and even across types. For example, GCC 14+
makes the following <em>four</em> initializer lists share a single backing array
at runtime by putting them all into <code class="language-plaintext highlighter-rouge">.rodata.cst16</code> sections with the
<code class="language-plaintext highlighter-rouge">SHF_MERGE</code> flag set. This works even if the lists appear in different TUs!
(<a href="https://godbolt.org/z/19ch8oYK9">Godbolt.</a>)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{1,2,3,4}
{1u,2u,3u,4u}
{0x200000001, 0x400000003} // given little-endian int64
{4.2439915824e-314, 8.4879831654e-314} // ditto
</code></pre></div></div>

<p>The major optimization-related downside of the <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> approach — as it is sketched in the
<a href="https://refspecs.linuxbase.org/elf/gabi4+/ch4.sheader.html#:~:text=The%20data%20in%20the%20section%20may%20be%20merged">System V ABI document</a>
(2001) and as it is implemented in GNU <code class="language-plaintext highlighter-rouge">ld</code> as far as I know — is that you can’t use it to merge data
elements of different sizes or alignments. GCC 16 won’t merge <code class="language-plaintext highlighter-rouge">{1,2}</code> with <code class="language-plaintext highlighter-rouge">{1,2,3}</code>
because GCC puts the former in section <code class="language-plaintext highlighter-rouge">.rodata.cst8</code> and the latter in <code class="language-plaintext highlighter-rouge">.rodata.cst16</code>.
(GCC 14 and 15 put the latter in plain old unmergeable <code class="language-plaintext highlighter-rouge">.rodata</code> instead, because
its size — 12 bytes — isn’t precisely 16 bytes. GCC 16 fixed that.) Basically, GCC has to
precommit every data element to a specific “bucket”; the linker will consider merging
it <em>only</em> with other elements in its own bucket.</p>

<p>And <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> cannot merge parts of elements; it can merge only full elements.
So while you might think a “sufficiently smart linker” could merge <code class="language-plaintext highlighter-rouge">{2,3}</code> across the
conjunction of <code class="language-plaintext highlighter-rouge">{1,2}</code> and <code class="language-plaintext highlighter-rouge">{3,4}</code>, the <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> algorithm by itself will never do that.
Some users might even rely on that guarantee (somehow), so I don’t imagine that
any linker will ever gain the smarts to do that.</p>

<h2 id="shf_merge--shf_strings"><code class="language-plaintext highlighter-rouge">SHF_MERGE | SHF_STRINGS</code></h2>

<p>When an ELF section specifies both <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> and <code class="language-plaintext highlighter-rouge">SHF_STRINGS</code>, then instead of chunking
the section into elements of size <code class="language-plaintext highlighter-rouge">sh_entsize</code> bytes, the linker chunks the section into
variable-length elements each of which is a null-terminated C-style string. It then
deduplicates those strings.</p>

<p>As in the previous subsection, it seems that no linker will merge
<code class="language-plaintext highlighter-rouge">"ello"</code> into the tail of <code class="language-plaintext highlighter-rouge">"Hello"</code>; the <code class="language-plaintext highlighter-rouge">SHF_MERGE|SHF_STRINGS</code> algorithm alone
will merge only full elements. (In this case, full null-terminated strings.)</p>

<p>The <code class="language-plaintext highlighter-rouge">SHF_MERGE|SHF_STRINGS</code> algorithm finds the “elements” of the section by simplemindedly
scanning for null bytes; no additional metadata is involved. Therefore, such a section must
never contain strings with embedded null bytes. Try the following on your machine:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt;x.c &lt;&lt;EOF
#include &lt;stdio.h&gt;
int main() {
  puts("p");
  puts(&amp;"qxp"[2]);
  puts("r");
}
EOF
gcc -S x.c -o - | sed 's/qxp/q\\0p/' &gt; x.s
gcc x.s
./a.out
</code></pre></div></div>

<p>The assembly file <code class="language-plaintext highlighter-rouge">x.s</code> should end up containing something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.str1.1,"aMS",@progbits
.LC0:
  .string "p"
.LC1:
  .string "q\0p"  # Danger!
.LC2:
  .string "r"
</code></pre></div></div>

<p>and when executed, will print not <code class="language-plaintext highlighter-rouge">p p r</code> but rather <code class="language-plaintext highlighter-rouge">p r r</code>, because the <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>
algorithm understands <code class="language-plaintext highlighter-rouge">"q\0p"</code> as <code class="language-plaintext highlighter-rouge">"q" "p"</code> and eliminates the second <code class="language-plaintext highlighter-rouge">"p"</code> as a duplicate.
Therefore, a C++ compiler must go out of its way never to store a string literal containing
embedded null bytes into such a section.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const char *p1 = "hello world"; // literal in .rodata.str1.1 (SHF_MERGE)
const char *p2 = "hell\0world"; // literal in .rodata (not SHF_MERGE)
</code></pre></div></div>

<p>In theory, a compiler could place the backing array for an <code class="language-plaintext highlighter-rouge">initializer_list&lt;char&gt;</code>
into a mergeable string section, and potentially merge the backing arrays for
<code class="language-plaintext highlighter-rouge">{'x','y','z','\0'}</code> and <code class="language-plaintext highlighter-rouge">"xyz"</code>. In practice, neither GCC nor Clang does this (yet).</p>

<hr />

<p>The big disadvantage of <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> merging from the C++ compiler’s point of view —
the thing that makes it unsuitable for certain use-cases such as merging the duplicate definitions
of template parameter objects — is that it is completely optional. It’s legal for a
dumb linker to just ignore the <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> flag. It would be unwise to rely on <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>
to take care of merging objects that the C++ standard <em>requires</em> us to merge, such as the
duplicate definitions of inline variables or template parameter objects.</p>

<p>And, of course, it would be wrong to place an inline variable into an <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> section
anyway, because the standard (and common sense) <em>forbids</em> us to merge unrelated inline
variables just because they happen to have the same value!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inline constexpr char a[] = "hello world";
inline constexpr char b[] = "hello world";
</code></pre></div></div>

<p>Here <code class="language-plaintext highlighter-rouge">&amp;a == &amp;b</code> is guaranteed to be false; an implementation that made it true would be
non-conforming. (Even <code class="language-plaintext highlighter-rouge">gcc -fmerge-all-constants</code> will make it true, non-conformingly,
only if you remove the <code class="language-plaintext highlighter-rouge">inline</code> keyword in both places.)</p>

<p>To handle inline variables, which are deduplicated according to their <em>symbol names</em>
rather than their <em>data contents</em>, we need the next approach, which is…</p>

<h2 id="shf_group-aka-comdat-sections"><code class="language-plaintext highlighter-rouge">SHF_GROUP</code>, a.k.a. COMDAT sections</h2>

<p>For the past twenty-some years, C and C++ compilers have traditionally compiled
inline functions, inline variables, and implicit instantiations of function and variable
templates into what are called “COMDAT sections.” This feature came late to ELF;
the name “COMDAT” <a href="https://itanium-cxx-abi.github.io/cxx-abi/abi/prop-72-comdat.html">apparently comes</a>
from Windows NT. For more than you ever wanted to know about COMDAT, see
<a href="https://maskray.me/blog/2021-07-25-comdat-and-section-group">“COMDAT and section group”</a>
(Fangrui Song, July 2021).</p>

<p>ELF’s version of COMDAT was basically designed to do <em>exactly</em> what a C++ compiler needs
in order to implement inline functions. The compiler can take C++ code such as</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inline int f() {
  static int i = 42;
  return ++i;
}
</code></pre></div></div>

<p>and turn it into a whole group of sections (text, data, rodata, whatever else it needs) —
basically a whole mini object file of its own — something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .text._Z1fv,"axG",@progbits,fgroup,comdat
  .globl _Z1fv
_Z1fv:
  movl _ZZ1fvE1i(%rip), %eax
  addl $1, %eax
  movl %eax, _ZZ1fvE1i(%rip)
  ret

  .section .data._ZZ1fvE1i,"awG",@progbits,fgroup,comdat
  .globl _ZZ1fvE1i
_ZZ1fvE1i:
  .long 42
</code></pre></div></div>

<p>The assembler emits an ELF section of type <code class="language-plaintext highlighter-rouge">SHT_GROUP</code> representing the group of sections
with “group identifier” <code class="language-plaintext highlighter-rouge">fgroup</code>. The linker, at link time, will pick one object file’s
<code class="language-plaintext highlighter-rouge">fgroup</code> group and throw away the rest.</p>

<blockquote>
  <p>Now, I simplified that codegen quite a bit. Really, GCC doesn’t make such a human-friendly
section group; it dumps each section into its own <em>individual</em> section group (so in this
example there will be two different group identifiers, not just one); and GCC also marks
both <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">i</code> as <code class="language-plaintext highlighter-rouge">.weak</code> symbols rather than global. I’m not sure why GCC does these
things; I conjecture “intermediate codegen targeting an object format less powerful than ELF”
and “compatibility with very old ELF linkers lacking <code class="language-plaintext highlighter-rouge">SHT_GROUP</code> support” respectively,
but I don’t know. Email and tell me!</p>
</blockquote>

<p>COMDAT sections are exactly what you need to implement the definitions of (1) inline functions;
(2) implicit instantiations of function templates; (3) the static local variables of
inline functions and implicitly instantiated function templates; (4) implicit instantiations
of variable templates; (5) inline variables and static inline data members of classes;
and probably a few more things I’m forgetting. All of these are entities with <em>names</em>,
and C++ requires them to be properly deduplicated by name: <code class="language-plaintext highlighter-rouge">&amp;myInlineFunc</code> must have the same pointer
value no matter what translation unit you’re in.</p>

<p>Another kind of entity we talked about the other day in
<a href="/blog/2026/04/24/define-static-array/">“Things <code class="language-plaintext highlighter-rouge">define_static_array</code> can’t do”</a> (2026-04-24):
template parameter objects of class type. Code like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>template&lt;A t&gt;
const A *f() { return &amp;t; }
</code></pre></div></div>

<p>will produce a template parameter object “variable” in its own COMDAT section,
like this (<a href="https://godbolt.org/z/Pb9WdKezh">Godbolt</a>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata._ZTAXtl1AEE,"aG",@progbits,_ZTAXtl1AEE,comdat
  .weak _ZTAXtl1AEE
_ZTAXtl1AEE:
  .zero 1
</code></pre></div></div>

<p>That’s exactly the same strategy the compiler would use for a simple inline variable like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>inline const A v;
</code></pre></div></div>

<p>Now, when the linker deduplicates COMDAT sections, it looks at the group identifier (a symbol name)
to decide if two sections are “duplicates” or not. It doesn’t care whether they have the same
bytewise contents. That makes sense, because usually the text sections corresponding to
instantiations of the same inline function in different TUs <em>won’t</em> be byte-for-byte identical
(especially if the two TUs were compiled with different optimization levels). For inline functions,
that’s exactly what we need: deduping by name, not by contents.</p>

<hr />

<p>You might imagine abusing COMDAT sections to do content-addressed deduplication à la <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>.
We just have to put each element in its own individual section group, with a
group identifier based on (the hash of) its contents. For example, instead of</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.str1.1,"aMS",@progbits
.LC0:
  .string "hello"
.LC1:
  .string "world"
.LC2:
  .string "hello"
</code></pre></div></div>

<p>we could emit</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.str1.hello,"aG",@progbits,group_hello,comdat
  .globl str_hello
str_hello:
  .string "hello"
  .section .rodata.str1.world,"aG",@progbits,group_world,comdat
  .globl str_world
str_world:
  .string "world"
</code></pre></div></div>

<p>But that would be vastly increase the linker’s burden — not just processing all those tiny
sections, but keeping track of all those new global symbols. (We couldn’t use local, internal-linkage
symbols anymore, because the linker wouldn’t be helping us to repoint one symbol’s relocations
at another.) And the compiler would have to dedupe <em>within</em> the TU: we could no longer
refer to <code class="language-plaintext highlighter-rouge">"hello"</code> by local symbols like <code class="language-plaintext highlighter-rouge">.LC0</code> and <code class="language-plaintext highlighter-rouge">.LC2</code>, but at the same time we couldn’t emit the
global symbol <code class="language-plaintext highlighter-rouge">str_hello</code> twice in the same TU.</p>

<blockquote>
  <p>By the way, making symbol names that incorporate hashes of user-provided data is just asking for trouble.
See <a href="/blog/2022/12/31/mid-snow-and-ice/">“Hash-colliding string literals on MSVC”</a> (2022-12-31).
A hash collision is disastrous; and when someone discovers how to generate hash collisions and starts
writing blog posts like the above, you can’t switch out your hash function for a better one because
that would break ABI.</p>
</blockquote>

<p>So it’s very good that we have different, essentially custom-fitted, tools for deduping inline functions
versus string literals.</p>

<ul>
  <li>
    <p>Duplicate inline functions <em>must</em> be merged (we forbid false negatives);
  non-duplicates <em>must not</em> be merged (we forbid false positives). Dupe-ness is decided by symbol name;
  contents can be different. Duplication need be detected only across TUs; duplication within a single TU
  is impossible. Use COMDAT sections.</p>
  </li>
  <li>
    <p>Duplicate strings can be left unmerged (we permit false negatives), although we still forbid false positives.
  Dupe-ness is decided by bytewise contents. Duplication should be detected both within and across TUs.
  Use <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>.</p>
  </li>
</ul>

<p>Templates and inline variables can also use COMDAT. A downside is that we’d like to merge the
definitions of e.g. <code class="language-plaintext highlighter-rouge">f&lt;char*&gt;</code> and <code class="language-plaintext highlighter-rouge">f&lt;void*&gt;</code> when they’re identical (if nothing in the program compares their
addresses), but if dupe-ness is decided by symbol name and those two have different symbols, we’re out
of luck. MSVC has a thing called <a href="https://learn.microsoft.com/en-us/cpp/build/reference/opt-optimizations">“Identical Comdat Folding”</a>
that helps with that. (MSVC’s ICF is a non-conforming optimization, because it doesn’t check the parenthetical above.
This can <a href="https://developercommunity.visualstudio.com/t/Safe-Identical-COMDAT-folding-ICF/10888506">break code</a>:
<a href="https://godbolt.org/z/5TM15Wzcx">Godbolt</a>).</p>

<p>Initializer-list backing arrays can use <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> too. A downside is that we’d <em>really</em> like to
merge <code class="language-plaintext highlighter-rouge">{2,3,4}</code> into the tail of <code class="language-plaintext highlighter-rouge">{1,2,3,4}</code>, and the <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> algorithm will never do that.
At least the compiler can do that, if someone teaches it to. That compiler optimization “composes”
properly with <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>. The compiler could even merge <code class="language-plaintext highlighter-rouge">{1,2}</code>, <code class="language-plaintext highlighter-rouge">{3,4}</code>, and <code class="language-plaintext highlighter-rouge">{2,3}</code> into a single
16-byte element <code class="language-plaintext highlighter-rouge">{1,2,3,4}</code> with three embedded labels, which could then merge with <code class="language-plaintext highlighter-rouge">{1u,2u,3u,4u}</code>
at link time:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  .section .rodata.cst16,"aM",@progbits,16
.C.0.0:
  .long 1
.C.1.1:
  .long 2
.C.2.2:
  .long 3
  .long 4
</code></pre></div></div>

<p>Of course it would be better if the linker could work this out itself; the linker has more
information than the compiler and therefore can do a better job of merging elements. It would also
be nice if we could do something like <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> for literals larger than 32 bytes, and/or literals
of lengths that aren’t powers of two. <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119153">GCC bug #119153</a>
is related.</p>

<p>Contrariwise, C++26 <code class="language-plaintext highlighter-rouge">std::define_static_array</code> seemingly cannot use <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>, because it cannot tolerate
false negatives: C++26 at present <em>requires</em> us to merge all copies of <code class="language-plaintext highlighter-rouge">define_static_array(std::array{1,2,3})</code>
into the same place in memory. That’s too bad, because forcing it to use COMDAT (name-addressed)
instead of <code class="language-plaintext highlighter-rouge">SHF_MERGE</code> (data-addressed) means we cannot take advantage of the latitude C++26 gives us to
merge <code class="language-plaintext highlighter-rouge">define_static_array(array{1,2,3})</code> with <code class="language-plaintext highlighter-rouge">define_static_array(array{1u,2u,3u})</code>.</p>

<blockquote>
  <p>Well, we could merge <code class="language-plaintext highlighter-rouge">define_static_array(array{1,2,3})</code> with <code class="language-plaintext highlighter-rouge">define_static_array(array{1u,2u,3u})</code>
if we used group identifiers based on a hash of the data! When we tried that on string data
<a href="#you-might-imagine-abusing-comdat">above</a>
we paid a huge performance penalty as well as a correctness penalty. Here we’d pay only
the correctness penalty. It’s still not worth it, IMHO.</p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>Figuring out the best way to “compress” static data using only the tools we’ve got — single-TU ad-hoc compiler
smarts, data-addressed <code class="language-plaintext highlighter-rouge">SHF_MERGE</code>, and name-addressed COMDAT sections — is a very hard problem. The implementation
won’t be optimum in every case.</p>

<p>Maybe we could give future linkers even better tools. For example, now that
“potentially non-unique object” is a term of art in C++, maybe we could just dump all PNU objects’ initializers into
a single section (<code class="language-plaintext highlighter-rouge">.rodata.pnu</code>?) and in one more special section (<code class="language-plaintext highlighter-rouge">.pnu_symtab</code>, storing just a list of indices
into the real <code class="language-plaintext highlighter-rouge">.symtab</code>?) specify their starts and sizes — I think that’s all the information the linker needs
in order to overlap them any way it sees fit and repair <code class="language-plaintext highlighter-rouge">.symtab</code> accordingly.</p>

<p>Something like that might already exist. If it does, I’d certainly like to hear about it.
And if it doesn’t, I’d certainly like someone to build it!</p>]]></content><author><name></name></author><category term="abi" /><category term="elf" /><category term="inline-functions" /><category term="language-design" /><category term="proposal" /><category term="rant" /><category term="sufficiently-smart-compiler" /><category term="templates" /><summary type="html"><![CDATA[Previously [I wrote](/blog/2026/04/24/define-static-array/): > [Template parameter objects of array type] are permitted to overlap or be > coalesced, just like `initializer_list`s and string literals. Clang trunk > isn't smart enough to coalesce potentially non-unique objects [but] > GCC, once it implements `define_static_array`, will presumably make them the same. Well, GCC 16 has an experimental implementation of `define_static_array` (compile with `g++ -std=c++26 -freflection`), and it does _not_ coalesce template parameter objects of array type in the way I expected. Digging deeper into why not, I learned that there are at least three ways compilers and linkers (on ELF — that is, non-Windows — platforms) conspire to "merge" potentially non-unique objects: * Merging at the compiler level (for `initializer_list` backing arrays) * Sections with `SHF_MERGE` (for string literals and backing arrays) * Sections with `SHF_GROUP`, a.k.a. COMDAT sections (for inline variables)]]></summary></entry><entry><title type="html">_Adventure:_ Is there light in the cobble crawl?</title><link href="https://quuxplusone.github.io/blog/2026/04/30/cobble-crawl-truncation/" rel="alternate" type="text/html" title="_Adventure:_ Is there light in the cobble crawl?" /><published>2026-04-30T00:01:00+00:00</published><updated>2026-04-30T00:01:00+00:00</updated><id>https://quuxplusone.github.io/blog/2026/04/30/cobble-crawl-truncation</id><content type="html" xml:base="https://quuxplusone.github.io/blog/2026/04/30/cobble-crawl-truncation/"><![CDATA[<p>The original <em>Colossal Cave Adventure</em> consists basically of a
Fortran source file and a textual data file. These files would
often travel from one installation to another via paper printouts:
printed out at one site, typed in by hand at another.</p>

<p>The lines of WOOD0350’s Fortran source (intentionally or not)
never exceed 80 columns regardless of your tab stop:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ detab -4 advent.for | awk '{print length}' | sort -n | uniq -c | tail -4
  48 77
  52 78
  35 79
   3 80
$ detab -8 advent.for | awk '{print length}' | sort -n | uniq -c | tail -4
  58 77
  62 78
  39 79
   3 80
</code></pre></div></div>

<p>But the data file fits within 80 columns only with a tab stop of four.
With an eight-space tab stop, four lines of the data file exceed 80 columns:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ detab -4 advent.dat | awk '{print length}' | sort -n | uniq -c | tail -4
  55 71
  59 72
  67 73
  69 74
$ detab -8 advent.dat | awk '{print length}' | sort -n | uniq -c | tail -4
  59 76
  66 77
  69 78
   4 82
</code></pre></div></div>

<p>These are the four offending lines. The first comes from section 3 (travel table)
and the rest from section 9 (bit flags).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>108     95556   43      45      46      47      48      49      50      29      30
0       1       2       3       4       5       6       7       8       9       10
7       42      43      44      45      46      47      48      49      50      51
7       52      53      54      55      56      80      81      82      86      87
</code></pre></div></div>

<p>If your version of <em>Adventure</em> has suffered truncation at the 80th column
at any point in its pedigree, you’d expect to see
(1) going DOWN from Witt’s End is impossible; (2) the Cobble Crawl
does not have light; (3) entering maze room 51 or 87 resets the maze
hint counter.</p>

<p>I first became aware of this possibility in December 2025, when Mike Willegal
sent me a fan-fold printout of HORV0350 (a
<a href="https://en.wikipedia.org/wiki/Systems_Engineering_Laboratories">SEL32</a>/<a href="http://mnembler.com/ragooman/computers_mini_history.html">RTM</a>
port of WOOD0350 most recently touched by Ned Horvath) that he’d kept since March 1979.
In that fan-fold printout, all four of the lines above are truncated and
thus missing the last number.</p>

<p><img src="/blog/images/2026-04-30-horv0350.jpg" alt="Three of the truncated lines" /></p>

<p>Now, I have no evidence that HORV0350’s own data file was actually truncated.
But anyone retyping the game from that printout could easily have assumed that
the Cobble Crawl was meant to be dark, and that Witt’s End was meant
to have no DOWN exit.</p>

<p>I see evidence that such truncation really did happen between LONG0501 and
ANON0501/OSKA0501.</p>

<ul>
  <li>ANON0501 reflows line <a href="https://github.com/Quuxplusone/Advent/blob/master/ANON0501/adv.data.2#L353-L354">(1)</a>,
  preserving the exits from Witt’s End,
  but truncates <a href="https://github.com/Quuxplusone/Advent/blob/master/ANON0501/adv.data.3#L742">(2)</a>
  and <a href="https://github.com/Quuxplusone/Advent/blob/master/ANON0501/adv.data.3#L759-L760">(3)</a>.</li>
  <li>OSKA0501 reflows <a href="https://github.com/Quuxplusone/Advent/blob/master/OSKA0501/src/adventure.text#L1583-L1584">(1)</a>
  but truncates <a href="https://github.com/Quuxplusone/Advent/blob/master/OSKA0501/src/adventure.text#L3218">(2)</a>
  and <a href="https://github.com/Quuxplusone/Advent/blob/master/OSKA0501/src/adventure.text#L3235-L3236">(3)</a>.</li>
  <li>MCDO0551 reflows <a href="https://github.com/Quuxplusone/Advent/blob/master/MCDO0551/ADVDAT#L1606-L1607">(1)</a>
  but truncates <a href="https://github.com/Quuxplusone/Advent/blob/master/MCDO0551/ADVDAT#L3172">(2)</a>
  and <a href="https://github.com/Quuxplusone/Advent/blob/master/MCDO0551/ADVDAT#L3191-L3192">(3)</a>.</li>
  <li>ROBE0665 reflows all three.</li>
</ul>

<p>My decompilation of <a href="/blog/2025/12/29/long0751/">the recovered LONG0751’s</a> data file indicates
that it did not truncate any of these lines; and my playtesting of the recovered LONG0501
indicates that it did not truncate (1) or (2) either. (It’s inconvenient to test (3) by playtesting.)</p>

<p>These observations are consistent with the hypothesis that first LONG0501 reflowed (1) but left (2) and (3)
untouched; then ANON0501/OSKA0501 and MCDO0551 descended from truncated paper printouts of LONG0501
(or via a now-lost common ancestor that was itself descended from LONG0501 by truncation).
Meanwhile LONG0751 and ROBE0665 descended, independently, from non-truncated copies of LONG0501.</p>]]></content><author><name></name></author><category term="adventure" /><category term="digital-antiquaria" /><category term="war-stories" /><summary type="html"><![CDATA[The original _Colossal Cave Adventure_ consists basically of a Fortran source file and a textual data file. These files would often travel from one installation to another via paper printouts: printed out at one site, typed in by hand at another. The lines of WOOD0350's Fortran source (intentionally or not) never exceed 80 columns regardless of your tab stop. But the data file fits within 80 columns only with a tab stop of four. With an eight-space tab stop, four lines of the data file exceed 80 columns:]]></summary></entry></feed>