<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged cpp</title>
<link>https://coderlegion.com/tag/cpp</link>
<description>Powered by Question2Answer</description>
<item>
<title>Tuesday Coding Tip 27 — Aliasing and ‘using’ in C++</title>
<link>https://coderlegion.com/29762/tuesday-coding-tip-27-aliasing-and-using-in-c</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Every hello world tutorial starts with the command &lt;code&gt;using namespace std;&lt;/code&gt;. This command is dangerous as it allows you to bring all standard symbols into your namespace where they can collide with your own symbols. Depending on the kind of collision, the compiler might not tell you, nor can you be sure which version will be used.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdexcept&amp;gt;

int min(int a, int b)
{
    std::terminate();
}

#include &amp;lt;cmath&amp;gt;

int main() {
    using namespace std; // dangerous
    // better to import specific names like:
    using std::max;

    return min(10, 20); // which min will be called?
    // ::min would disambiguate to the local function
    // std::min would disambiguate to standard
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is better to use the &lt;code&gt;using&lt;/code&gt; keyword to selectively bring symbols into the scope or even alias them to be sure they’re unique (note that namespaces alias differently).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;filesystem&amp;gt;

// Aliasing namespace works differently
namespace fs = std::filesystem;

// from aliasing classes
using Path = fs::path;

bool fileExists(const Path&amp;amp; path)
{
    return fs::exists(path);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The keyword can also be used to expose class template parameters for common usage.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;class T&amp;gt;
class Foo {
public:
    // Exposing template type saves time
    // to those who need to get to it
    using DataType = T;

    T getItem() const { return T{}; }
};

int main() {
    Foo&amp;lt;int&amp;gt; foo;
    decltype(foo)::DataType item;
    item = foo.getItem();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And since C++20, you can bring &lt;code&gt;enum&lt;/code&gt; values (selectively or in group) for better ergonomics in enum-based branching.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;enum class EmployeeType {
    JuniorDev,
    MediorDev,
    SeniorDev,
    ScrumMaster,
    ProductManager
};

std::string to_string(EmployeeType et) {
    switch (et)
    {
        using enum EmployeeType; // Since C++20
        case JuniorDev: return &quot;junior&quot;;
        case MediorDev: return &quot;medior&quot;;
        // ...
    }
}

std::string to_string(EmployeeType et) {
    using EmployeeType::JuniorDev;

    if (et == JuniorDev) return &quot;junior&quot;;
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29762/tuesday-coding-tip-27-aliasing-and-using-in-c</guid>
<pubDate>Tue, 20 Oct 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>The missing break behind Digimon World’s evolution rules</title>
<link>https://coderlegion.com/31448/the-missing-break-behind-digimon-worlds-evolution-rules</link>
<description>&lt;hr&gt;
&lt;p&gt;title: The missing break behind Digimon World’s evolution rules&lt;br&gt;
published: true&lt;br&gt;
description: Digimon World’s decompiled evolution code reveals a four-point scoring system, a surprising tie-break rule, and a path toward a PC port.&lt;/p&gt;
&lt;h3 id=&quot;tags-reverseengineering-c-gamedev-cpp&quot;&gt;tags: reverseengineering, c, gamedev, cpp&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ldw0x3np6t314act44lq.gif&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;You fed it, trained it, cleaned up after it. You had a plan. Then it evolved into Numemon, a walking pile of sludge, and the game never told you why.&lt;/p&gt;
&lt;p&gt;No menu. No hint. No stat screen saying &lt;em&gt;here is what went wrong&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Players filled that silence with wikis, calculators and arguments about whether care mistakes helped or hurt. They inferred the rules from what happened on screen. Now we can read the code.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jype0/dw_decomp&quot;&gt;Digimon World decompilation project&lt;/a&gt; has reconstructed the evolution code. One rule comes from a loop with no &lt;code&gt;break&lt;/code&gt;: when several stats share the highest value, the game picks the last one in its array.&lt;/p&gt;
&lt;p&gt;Here is how the scoring works, what that tie means, and what I learned while testing a small PC port prototype.&lt;/p&gt;
&lt;h3 id=&quot;the-missing-break&quot;&gt;The missing break&lt;/h3&gt;
&lt;p&gt;Here is the real code that picks which of your stats the game examines. It's in &lt;code&gt;src/main/evolution.c&lt;/code&gt;, and it runs over the six stats &lt;code&gt;hp, mp, offense, defense, speed, brain&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;for (i = 0; i &amp;lt; 6; i++) {
  int8_t isHighestStat = 1;
  for (j = 0; j &amp;lt; 6; j++) {
    if (statsArray[i] &amp;lt; statsArray[j])
      isHighestStat = 0;
  }
  if (isHighestStat == 1)
    highestStat = i;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For six stats, the nested loop is cheap. But look at what is missing: there is no &lt;code&gt;break&lt;/code&gt; after the assignment. The loop keeps going after it finds a maximum, so a later tied stat overwrites an earlier one. &lt;strong&gt;When stats tie, the highest index wins.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I ran the same selection logic with a few inputs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;all six tied      -&amp;gt; brain
offense highest   -&amp;gt; offense
off+def tied high -&amp;gt; defense
brain highest     -&amp;gt; brain
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading the array order backwards gives the full tie-break priority: &lt;strong&gt;brain &amp;gt; speed &amp;gt; defense &amp;gt; offense &amp;gt; mp &amp;gt; hp&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With all six stats tied, the game selects brain as the highest stat. You could infer this from enough experiments, but the code makes the reason clear. Whether the missing &lt;code&gt;break&lt;/code&gt; was intentional is unknown; its effect is observable.&lt;/p&gt;
&lt;p&gt;{% card %}&lt;br&gt;
Worth being careful here: I first suspected &lt;code&gt;highestStat&lt;/code&gt; was read uninitialized, which would be a genuine bug worth reporting upstream. It can't be — a maximum always exists among six values, so the flag is always set at least once. Check before you claim.&lt;br&gt;
{% endcard %}&lt;/p&gt;
&lt;h3 id=&quot;the-scoring-system-four-checks-three-needed&quot;&gt;The scoring system: four checks, three needed&lt;/h3&gt;
&lt;p&gt;That tie-break feeds a larger system. Every Digimon has up to six possible evolution targets. For each candidate the game computes a score out of 4, and &lt;strong&gt;a candidate needs at least 3 points to be eligible at all.&lt;/strong&gt; Below 3 it isn't considered.&lt;/p&gt;
&lt;p&gt;Here are the four checks.&lt;/p&gt;
&lt;h4 id=&quot;care-mistakes-and-the-flag-that-inverts-them&quot;&gt;Care mistakes, and the flag that inverts them&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (isMaxCM == 0) {
  if (partner-&amp;gt;careMistakes &amp;gt;= reqs-&amp;gt;care)
    reqPoints += 1;
} else if (partner-&amp;gt;careMistakes &amp;lt;= reqs-&amp;gt;care) {
  reqPoints += 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the comparisons closely. There's a flag bit that &lt;strong&gt;reverses the test&lt;/strong&gt;, so some evolutions want your care mistakes &lt;em&gt;above&lt;/em&gt; a threshold.&lt;/p&gt;
&lt;p&gt;Neglect isn't a punishment in this game. For certain paths it's a requirement. Care mistakes can therefore help qualify a candidate. Whether that explains any particular Numemon depends on the other checks and available targets.&lt;/p&gt;
&lt;h4 id=&quot;weight-is-a-window-not-a-floor&quot;&gt;Weight is a window, not a floor&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (reqs-&amp;gt;weight - 5 &amp;lt;= partner-&amp;gt;weight &amp;amp;&amp;amp;
    partner-&amp;gt;weight &amp;lt;= reqs-&amp;gt;weight + 5)
  reqPoints += 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A band of &lt;strong&gt;±5&lt;/strong&gt; around a target. Overfeeding fails this check exactly as hard as underfeeding. A precise target weight matters because both sides of the window have a limit.&lt;/p&gt;
&lt;h4 id=&quot;stats-where-the-stage-changes-the-rule&quot;&gt;Stats, where the stage changes the rule&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (DIGIMON_DATA[target].level == 3U) {
  /* Champion: only your single HIGHEST stat is examined */
} else {
  /* everything else: ALL six stats must clear their thresholds */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most evolutions need every stat above its requirement. Champion-level targets check only your highest one — which is where the missing &lt;code&gt;break&lt;/code&gt; above actually bites. &lt;strong&gt;For Champions, one selected highest stat matters; other targets require all six thresholds.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-bonus-point-any-one-of-five&quot;&gt;The bonus point: any one of five&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (reqs-&amp;gt;digimon    != -1 &amp;amp;&amp;amp; current == reqs-&amp;gt;digimon) isBonusFulfilled = 1;
if (reqs-&amp;gt;discipline != -1 &amp;amp;&amp;amp; /* ... */)                isBonusFulfilled = 1;
if (reqs-&amp;gt;happiness  != -1 &amp;amp;&amp;amp; /* ... */)                isBonusFulfilled = 1;
if (reqs-&amp;gt;battles    != -1) { /* also invertible */ }
if (reqs-&amp;gt;techs      != -1) { /* mastered moves */ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any single one of these grants the fourth point. Battle count carries the same inversion trick as care mistakes: some evolutions want you to have fought &lt;em&gt;fewer&lt;/em&gt; than N battles.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-that-quietly-steers-your-collection&quot;&gt;The rule that quietly steers your collection&lt;/h3&gt;
&lt;p&gt;One more piece, and it's the one that made me sit back:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;if (reqPoints &amp;gt;= 3 &amp;amp;&amp;amp; currentBest != -1) {
  isTargetRaised = hasDigimonRaised(
      EVO_GAINS_DATA[target].targetDigimon);
  isCurrentBestRaised = hasDigimonRaised(
      EVO_GAINS_DATA[currentBest].targetDigimon);

  if (isTargetRaised == 1 &amp;amp;&amp;amp; isCurrentBestRaised == 0)
    reqPoints = 0;      /* you already have this one: disqualify it */

  if (isTargetRaised == 0 &amp;amp;&amp;amp; isCurrentBestRaised == 1)
    reqPoints++;        /* you don't have this one: push it ahead */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The game remembers which Digimon you have raised. When it compares two eligible candidates, this code favours the one you have not raised: it can zero the already-raised candidate’s score or add a point to the new one. The outcome still depends on candidate order and the surrounding selection logic.&lt;/p&gt;
&lt;p&gt;It is a small but deliberate way to steer players toward more of the roster.&lt;/p&gt;
&lt;h3 id=&quot;who-actually-did-this&quot;&gt;Who actually did this&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;I did not decompile the game.&lt;/strong&gt; I forked the repository, ran parts of its code on a PC, and measured a few pieces a port would need. The decompilation work belongs to its contributors.&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jype0/dw_decomp&quot;&gt;https://github.com/jype0/dw_decomp&lt;/a&gt; %}&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/jype0/dw_decomp&quot;&gt;jype0/dw_decomp&lt;/a&gt; started in March 2026 and is MIT licensed. It passed a milestone most decompilation projects never reach: &lt;strong&gt;zero &lt;code&gt;INCLUDE_ASM&lt;/code&gt; stubs left in &lt;code&gt;src/&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you haven't worked on a decomp, that needs explaining. &lt;code&gt;INCLUDE_ASM&lt;/code&gt; is the marker for &quot;we haven't figured this function out yet, here's the raw assembly instead.&quot; Most decomps carry hundreds of them for years. The source tree no longer relies on those assembly placeholders. That milestone alone does not establish that every function matches the retail binary.&lt;/p&gt;
&lt;p&gt;Credit isn't evenly spread, and shouldn't be reported as if it were. As of today the contributor counts are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Contributor &lt;/th&gt;&lt;th&gt;Contributions&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Pekka Jylhä-Ollila (jype0) &lt;/td&gt;&lt;td&gt;409 — maintainer, and the bulk of the project&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;juandav &lt;/td&gt;&lt;td&gt;37 — matching functions and data across overlays&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ChonkMode &lt;/td&gt;&lt;td&gt;22&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;kingzmanh &lt;/td&gt;&lt;td&gt;11&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;fmil95 &lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ThirstyWraith &lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SydMontague &lt;/td&gt;&lt;td&gt;5 — struct and naming reference work&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;paulohpfilho &lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Those are the repository’s contribution counts at the time I checked, not a measure of each person’s work or proof that every regional build is complete.&lt;/p&gt;
&lt;p&gt;I asked about the roadmap in an issue. juandav relayed jype0's four points: clean up the hacks and name things properly; match all 10 regional versions; maybe a PC port, blocked because &lt;em&gt;psyq and psycross don't currently implement libgs or libsnd&lt;/em&gt;; and then &lt;strong&gt;a DW1 mod of epic proportions&lt;/strong&gt; — new maps, story, Digimon.&lt;/p&gt;
&lt;p&gt;The ambitious mod is the long-term goal. The decompilation makes that work possible.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;running-their-code-somewhere-it-has-never-run&quot;&gt;Running their code somewhere it has never run&lt;/h3&gt;
&lt;p&gt;My question was narrower: can this code run off the PlayStation at all?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;evolution.c&lt;/code&gt; is 1,087 lines. Compiling it on x86-64 produced exactly one error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;libgte.h: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's Sony's geometry coprocessor header. So I grepped &lt;code&gt;evolution.c&lt;/code&gt; for &lt;code&gt;VECTOR&lt;/code&gt;, &lt;code&gt;MATRIX&lt;/code&gt;, &lt;code&gt;gte_&lt;/code&gt;, &lt;code&gt;RotTrans&lt;/code&gt; — nothing. The evolution logic doesn't touch the GTE at all. It only &lt;em&gt;inherits&lt;/em&gt; the header through &lt;code&gt;entity.h&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I wrote type-only stand-ins for the four PsyQ headers. It compiled clean, then ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EvoRequirements = 28 bytes (PS1 layout: 28)
fresh(1)       -&amp;gt; 2
in-training(3) -&amp;gt; 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That 28 matters more than it looks. The game reads its data tables as raw structs, so if the layout shifted by even one byte on a 64-bit host, every evolution requirement in the game would decode as noise. It doesn't, and I pinned it with &lt;code&gt;static_assert&lt;/code&gt; so nobody can break it quietly later.&lt;/p&gt;
&lt;h3 id=&quot;measuring-the-gte-instead-of-guessing-at-it&quot;&gt;Measuring the GTE instead of guessing at it&lt;/h3&gt;
&lt;p&gt;A major piece of a PS1 port is the GTE, the fixed-point geometry coprocessor.&lt;/p&gt;
&lt;p&gt;My first instinct was to size the problem with a grep. I got 181 and quoted that number publicly. &lt;strong&gt;It was wrong&lt;/strong&gt; — my pattern was matching a header that &lt;em&gt;defines&lt;/em&gt; every GTE macro whether the game calls it or not. Measured against &lt;code&gt;src/&lt;/code&gt; alone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;632 call sites, 29 distinct operations, 34 of 126 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The call sites are concentrated:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Operation &lt;/th&gt;&lt;th&gt;Sites &lt;/th&gt;&lt;th&gt;Cumulative&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ApplyMatrixSV&lt;/code&gt; &lt;/td&gt;&lt;td&gt;103 &lt;/td&gt;&lt;td&gt;16%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;RotMatrix&lt;/code&gt; &lt;/td&gt;&lt;td&gt;51 &lt;/td&gt;&lt;td&gt;24%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;RotMatrixZYX&lt;/code&gt; &lt;/td&gt;&lt;td&gt;44 &lt;/td&gt;&lt;td&gt;31%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;RotMatrixYXZ&lt;/code&gt; &lt;/td&gt;&lt;td&gt;44 &lt;/td&gt;&lt;td&gt;38%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ratan2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;43 &lt;/td&gt;&lt;td&gt;45%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;gte_rtps&lt;/code&gt; &lt;/td&gt;&lt;td&gt;42 &lt;/td&gt;&lt;td&gt;58%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ScaleMatrix&lt;/code&gt; &lt;/td&gt;&lt;td&gt;39 &lt;/td&gt;&lt;td&gt;71%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;TransMatrix&lt;/code&gt; &lt;/td&gt;&lt;td&gt;37 &lt;/td&gt;&lt;td&gt;77%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ApplyMatrixLV&lt;/code&gt; &lt;/td&gt;&lt;td&gt;23 &lt;/td&gt;&lt;td&gt;&lt;strong&gt;81%&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;The nine operations shown account for about 81% of these call sites.&lt;/strong&gt; That gives a useful starting point, though call-site count does not measure implementation difficulty.&lt;/p&gt;
&lt;p&gt;I implemented a software subset covering those common operations. The contract is strict: matrix entries are 1.3.12 fixed point (4096 = 1.0), angles run 4096 to a full turn, and results &lt;strong&gt;saturate rather than wrap&lt;/strong&gt;. That last one isn't optional — a wrapped coordinate throws a vertex to the far side of the screen, which is exactly the class of bug that ships unnoticed.&lt;/p&gt;
&lt;p&gt;Then I measured the number that actually decides anything. &quot;0.17% trigonometric error&quot; is a useless statistic; nobody can tell you whether that's visible. So I measured pixels — how far a projected vertex lands from where double-precision maths puts it, across 2,528 vertices and a full rotation sweep:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;mean error:             0.825 px
worst error:            1.896 px
vertices off by &amp;gt; 2px:  0.00%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that test, the error stayed under two pixels. This measures one projection sweep; it does not guarantee every game scene will look the same.&lt;/p&gt;
&lt;p&gt;And the honest half: &lt;strong&gt;it is not bit-exact.&lt;/strong&gt; The real GTE uses lookup tables; my polynomial approximation looks identical and is numerically different. That's fine for a &lt;em&gt;port&lt;/em&gt; and useless for &lt;em&gt;verifying a decompilation&lt;/em&gt;, whose entire premise is reproducing the original binary byte for byte. Confusing those two would be the expensive mistake, so I put the warning in the test output rather than in a comment nobody reads.&lt;/p&gt;
&lt;p&gt;{% details Drawing a frame: primitives and the ordering table %}&lt;/p&gt;
&lt;p&gt;The game's rendering is all PsyQ primitives, counted across the source:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;POLY_FT4  386    (textured quad)
POLY_F4    30
POLY_GT4   17
POLY_FT3    6    (textured triangle)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two textured primitive types in this excerpt account for 392 call sites, making them a useful first rendering test. I built them with the game's own &lt;code&gt;setXYWH&lt;/code&gt; and &lt;code&gt;setUVWH&lt;/code&gt; macros lifted from &lt;code&gt;src/main/utils.c&lt;/code&gt;, and rasterised them in software.&lt;/p&gt;
&lt;p&gt;One thing I deliberately did not fix: the texture mapping is affine, not perspective-correct. The PS1 had no perspective correction, and that wobble is part of how the console looks. Correcting it would make a port feel wrong.&lt;/p&gt;
&lt;p&gt;Then the ordering table, which is how the PS1 sorted by depth. The name misleads, so it's worth saying plainly: &lt;strong&gt;an ordering table is not a sorting algorithm.&lt;/strong&gt; It's a bucket array — one slot per depth value, each holding a linked list, with &lt;code&gt;AddPrim&lt;/code&gt; pushing to the front. O(1) insertion, zero comparisons. That's how a 33MHz console depth-sorted a scene every frame, and reimplementing it with &lt;code&gt;qsort&lt;/code&gt; would be slower &lt;em&gt;and&lt;/em&gt; wrong, because the order of equal-depth primitives depends on the original insertion and traversal rules.&lt;/p&gt;
&lt;p&gt;I built the occlusion test so it couldn't pass by luck: submit the &lt;strong&gt;near&lt;/strong&gt; quad first, then the far one.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;normal:          centre pixel r=16  g=104    (near/green occludes)
depths swapped:  centre pixel r=120 g=16     (far/red now occludes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same submission order, opposite result. The table is doing the work, not the sequence of my function calls.&lt;/p&gt;
&lt;p&gt;{% enddetails %}&lt;/p&gt;
&lt;h3 id=&quot;where-i-got-things-wrong&quot;&gt;Where I got things wrong&lt;/h3&gt;
&lt;p&gt;Two corrections, because they're more useful than a clean story.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The GTE count.&lt;/strong&gt; I said 181 in public. It's 632. I estimated with a grep and then quoted the estimate as a measurement, which is a bad habit and I did it anyway.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A test that failed for the right reason.&lt;/strong&gt; I asserted the far square would peek out around the near one. It doesn't: the near quad spans x=75..245 and the far one x=123..197, so full occlusion is correct physics and my sampling point was badly chosen. I checked the rendered image before touching any code, which is the only reason I didn't &quot;fix&quot; working code to match a broken expectation.&lt;/p&gt;
&lt;h3 id=&quot;what-this-does-not-prove&quot;&gt;What this does not prove&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One logic file out of 126.&lt;/strong&gt; I picked &lt;code&gt;evolution.c&lt;/code&gt; &lt;em&gt;because&lt;/em&gt; it looked dependency-free. Best case, not a representative sample.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The GTE is approximate.&lt;/strong&gt; 18 of 29 operations unimplemented, and the trig is polynomial rather than table-driven.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No model loading.&lt;/strong&gt; &lt;code&gt;GsSortObject4&lt;/code&gt; has 42 call sites and walks TMD models. The ordering table is ready for it; the format reader isn't written.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nothing validated against the retail binary.&lt;/strong&gt; That needs the MIPS toolchain and a disc image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The assets are still Bandai's.&lt;/strong&gt; Models, textures, music, text. A possible distribution model is an engine that requires players to supply their own game data; the legal details would need separate review.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where my work and jype0's roadmap touch: his stated blocker for a PC port is that psyq and psycross don't implement &lt;code&gt;libgs&lt;/code&gt; or &lt;code&gt;libsnd&lt;/code&gt;. &lt;code&gt;libgs&lt;/code&gt; is one of the four headers I stubbed. I'm not claiming that solves his problem — only that it's the same wall, and it's climbable from at least one side.&lt;/p&gt;
&lt;h3 id=&quot;what-we-finally-understand&quot;&gt;What we finally understand&lt;/h3&gt;
&lt;p&gt;Twenty-five years of wiki edits, spreadsheets and arguments, and the answer is a few hundred lines of C:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3 points of 4&lt;/strong&gt; to be eligible at all&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Care mistakes and battle counts are invertible&lt;/strong&gt; — some evolutions want neglect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight is a ±5 window&lt;/strong&gt; — overfeeding fails like starving&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Champions check your highest stat only&lt;/strong&gt;; everything else checks all six&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ties favour the last tied stat&lt;/strong&gt; in array order, with brain last&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Previously raised targets can lose a head-to-head comparison&lt;/strong&gt; against a new one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your Numemon had an explanation in the game’s rules. The exact reason in any given run depends on the candidate scores and selection path.&lt;/p&gt;
&lt;p&gt;That is what makes the decompilation exciting: the rules are readable and testable. It also gives modders a foundation to build on.&lt;/p&gt;
&lt;p&gt;My four experiments live on a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/krlz-dev/dw_decomp/tree/port-spike/port&quot;&gt;branch in my fork&lt;/a&gt;. Each runs with one command and needs only &lt;code&gt;gcc&lt;/code&gt;, plus &lt;code&gt;libsdl2-dev&lt;/code&gt; for the two that draw — no PlayStation toolchain, no disc image.&lt;/p&gt;
&lt;p&gt;If you've worked on a decomp: how do you decide when an approximation is good enough to ship in a port, given it can never be good enough to verify a match? The bit-exact line is the part I'm least sure I drew correctly.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31448/the-missing-break-behind-digimon-worlds-evolution-rules</guid>
<pubDate>Fri, 16 Oct 2026 19:39:38 +0000</pubDate>
</item>
<item>
<title>I Built a C++ CLI That Hunts Down Secrets Hiding in Your Git History</title>
<link>https://coderlegion.com/31117/i-built-a-c-cli-that-hunts-down-secrets-hiding-in-your-git-history</link>
<description>&lt;p&gt;We've all done it. You commit a &lt;code&gt;.env&lt;/code&gt; file by accident, catch it a few commits later, delete it, and breathe a sigh of relief. Except... it's not gone. It's still sitting in your Git history, one &lt;code&gt;git log -p&lt;/code&gt; away from anyone who clones the repo.&lt;/p&gt;
&lt;p&gt;That's the problem &lt;strong&gt;GitHub Secrets Watcher&lt;/strong&gt; is built to catch.&lt;/p&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;p&gt;GitHub Secrets Watcher is a command-line tool written in modern C++20 that scans your GitHub repositories - public or private (with a token) - and walks back through commit history looking for accidentally committed environment and config files: &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;config.js&lt;/code&gt;, and similar patterns that often carry API keys, database credentials, or other secrets.&lt;/p&gt;
&lt;p&gt;For every hit, it gives you a direct link to the exact commit where the file appears, so you're not stuck digging through history manually to remediate it.&lt;/p&gt;
&lt;p&gt;Importantly, it's strictly &lt;strong&gt;read-only&lt;/strong&gt;. It never modifies, deletes, or rewrites anything in your repositories - it just reports what it finds so you can decide what to do next (rotate the secret, scrub the history, etc.).&lt;/p&gt;
&lt;h3 id=&quot;key-features&quot;&gt;Key features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Scans all repos for a given GitHub username, public by default, private too if you pass a token&lt;/li&gt;
&lt;li&gt;Walks a configurable depth of commit history across branches&lt;/li&gt;
&lt;li&gt;Skips noisy directories like &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, and &lt;code&gt;dist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Validates commit hashes before generating links, so you never get a broken URL&lt;/li&gt;
&lt;li&gt;Multi-threaded scanning - set the thread count to match your hardware&lt;/li&gt;
&lt;li&gt;Three output formats: human-readable text (with color-coded terminal output), JSON, or CSV&lt;/li&gt;
&lt;li&gt;Stream-based processing to keep memory usage low even on large histories&lt;/li&gt;
&lt;li&gt;Modern, safe filesystem operations: Uses C++20 std::filesystem for all path handling, eliminating platform-specific path issues and improving security&lt;/li&gt;
&lt;li&gt;Direct Git integration: Leverages libgit2 for efficient, safe repository operations without spawning subprocesses&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-output&quot;&gt;Example output&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[1/7] Scanning: ccas-website
[INFO] Cloning repository (depth=100)...
[INFO] Scanning history...
[WARN] Found 5 potential environment/configuration files:
     - client/assets/js/config.js
       [LINK] https://github.com/.../blob/c7e0fba.../client/assets/js/config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each finding links straight to the offending commit, so remediation is one click away instead of a history-diving expedition.&lt;/p&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting started&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/gerald-mathew/github-secrets-watcher
cd github-secrets-watcher
mkdir build &amp;amp;&amp;amp; cd build
cmake ..
make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run a scan:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./github_secrets_watcher scan --username YOUR_USERNAME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a token for private repos and higher API rate limits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./github_secrets_watcher scan --username YOUR_USERNAME \
  --token YOUR_PERSONAL_ACCESS_TOKEN --include-private
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can tune the scan with &lt;code&gt;--depth&lt;/code&gt;, &lt;code&gt;--max-repos&lt;/code&gt;, &lt;code&gt;--threads&lt;/code&gt;, &lt;code&gt;--format&lt;/code&gt;, &lt;code&gt;--verbose&lt;/code&gt;, &lt;code&gt;repo(s) &amp;lt;repo name(s)&amp;gt;&lt;/code&gt; and &lt;code&gt;--output&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-c-20&quot;&gt;Why C++20&lt;/h3&gt;
&lt;p&gt;This isn't a wrapper around &lt;code&gt;git log&lt;/code&gt; piped through &lt;code&gt;grep&lt;/code&gt; — it uses libcurl for GitHub API calls, libgit2 for direct Git repository access, nlohmann/json for parsing, and C++17 std::filesystem for safe, portable path operations, with modern C++20 features throughout. It builds cleanly with CMake or a provided build script on Linux, macOS, and Windows (via MSYS2).&lt;/p&gt;
&lt;h3 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h3&gt;
&lt;p&gt;It's MIT-licensed and open source. If you've got repos going back a few years, there's a decent chance something's buried in there that shouldn't be — I'd genuinely be curious what people find.&lt;/p&gt;
&lt;p&gt;🔗 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/gerald-mathew/github-secrets-watcher&quot;&gt;github.com/gerald-mathew/github-secrets-watcher&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Feedback, issues, and PRs welcome.&lt;/p&gt;
&lt;h3 id=&quot;update---sep-3-&quot;&gt;Update - Sep 3:&lt;/h3&gt;
&lt;p&gt;Based on feedback from the community, I’ve replaced the filesystem handling with std::filesystem and am now using libgit2 for Git operations instead of popen. Thanks to @hailiang194  for the suggestions!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31117/i-built-a-c-cli-that-hunts-down-secrets-hiding-in-your-git-history</guid>
<pubDate>Wed, 14 Oct 2026 22:57:01 +0000</pubDate>
</item>
<item>
<title>I Tried to Build Persistent Compute on an Xbox SSD. The Failed Tests Changed the Architecture.</title>
<link>https://coderlegion.com/30863/tried-to-build-persistent-compute-on-an-xbox-ssd-the-failed-tests-changed-the-architecture</link>
<description>&lt;p&gt;The Xbox wrote 8 GiB of persistent data.&lt;/p&gt;
&lt;p&gt;I still couldn't call the experiment successful.&lt;/p&gt;
&lt;p&gt;The bytes were there, but the operation failed because a completely different part of the system had timed out. In another test, the worker correctly refused to allocate more storage even though its configuration technically allowed a much larger workload.&lt;/p&gt;
&lt;p&gt;Eventually, the experiment passed. But getting there required much more than writing files to an SSD.&lt;/p&gt;
&lt;p&gt;I had to investigate how Xbox processes share resources, build a separate coordination component, introduce a recoverable page store, survive real application restarts, and learn which failures were bugs and which were legitimate platform boundaries.&lt;/p&gt;
&lt;p&gt;This is one of the experiments behind XCP that I hadn't explored in the previous articles.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/danielecangi/series/44781&quot;&gt;five-part XCP series&lt;/a&gt; ended with the platform becoming open source. This bonus chapter goes back into the original Xbox research to explain how I approached persistent computation inside the Developer Mode sandbox.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-wasn-t-just-memory-capacity&quot;&gt;The problem wasn't just memory capacity&lt;/h3&gt;
&lt;p&gt;In the earlier experiments, XCP measured an application memory budget of 5 GiB in one Xbox Series X Developer Mode configuration.&lt;/p&gt;
&lt;p&gt;That was useful. It gave us an actual resource boundary for the worker rather than a number inferred from the console's hardware specifications.&lt;/p&gt;
&lt;p&gt;But it also raised another question.&lt;/p&gt;
&lt;p&gt;What happens when the work I want to execute is larger than the memory I can keep resident?&lt;/p&gt;
&lt;p&gt;I wasn't trying to bypass the sandbox or convince the Xbox that its SSD was additional physical RAM.&lt;/p&gt;
&lt;p&gt;I wanted to build something different: an execution system that could keep data and completed work in persistent storage, bring bounded pieces into memory, process them, and continue from a valid checkpoint after an interruption.&lt;/p&gt;
&lt;p&gt;Before building the storage layer, though, I had another hypothesis to investigate.&lt;/p&gt;
&lt;p&gt;Could a second process help?&lt;/p&gt;
&lt;h3 id=&quot;first-i-investigated-the-xbox-process-model&quot;&gt;First, I investigated the Xbox process model&lt;/h3&gt;
&lt;p&gt;XCP already had a foreground worker capable of running CPU and GPU workloads.&lt;/p&gt;
&lt;p&gt;I wanted to understand whether another process inside the same UWP package could perform useful background coordination without consuming the worker's entire resource budget.&lt;/p&gt;
&lt;p&gt;The candidate was an out-of-process App Service.&lt;/p&gt;
&lt;p&gt;On desktop Windows, creating another process might seem straightforward. On Xbox Developer Mode, I wasn't going to assume that the desktop behavior applied unchanged.&lt;/p&gt;
&lt;p&gt;So I started measuring.&lt;/p&gt;
&lt;p&gt;The first live probes established that the worker and an auxiliary App Service could run with distinct process and activation identities. They exchanged messages, maintained independent heartbeat activity and retained touched, verified memory during overlapping execution windows.&lt;/p&gt;
&lt;p&gt;That was already useful.&lt;/p&gt;
&lt;p&gt;But it did not prove that we had discovered a second independent 5 GiB memory budget.&lt;/p&gt;
&lt;p&gt;To investigate properly, I built an eleven-phase matrix covering CPU execution, verified GPU execution, simultaneous activity, progressive memory pressure, bidirectional storage visibility, suspension, termination, restart, redeployment and cleanup.&lt;/p&gt;
&lt;p&gt;And this is where the testing started getting complicated.&lt;/p&gt;
&lt;h4 id=&quot;the-first-failures-weren-t-even-storage-failures&quot;&gt;The first failures weren't even storage failures&lt;/h4&gt;
&lt;p&gt;Worker &lt;code&gt;.150&lt;/code&gt; reached the Xbox but exposed incomplete measurement data. It could report IPC latency summaries, yet the acceptance contract required the underlying distribution, including median, p95 and bidirectional throughput.&lt;/p&gt;
&lt;p&gt;There was no legitimate way to reconstruct missing measurements afterward.&lt;/p&gt;
&lt;p&gt;So that run remained diagnostic.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.151&lt;/code&gt; produced another interesting failure: &lt;strong&gt;79 of 81 preflight checks passed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The two failures came from heartbeat validation.&lt;/p&gt;
&lt;p&gt;The foreground heartbeat ran at 25 ms intervals. The auxiliary broker used a 100 ms timer. During an approximately 935 ms observation window, the broker produced nine ticks.&lt;/p&gt;
&lt;p&gt;The test incorrectly expected 32 because it had reused the foreground count.&lt;/p&gt;
&lt;p&gt;The broker wasn't failing to run. Our expectation was wrong.&lt;/p&gt;
&lt;p&gt;That candidate stayed failed, and the next version corrected the measurement contract.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.152&lt;/code&gt; passed the preflight, but memory-pressure testing exposed another gap. The Xbox refused a bounded allocation, yet the error didn't carry enough structured information to establish the responsible component, observed headroom, applied reserve and cleanup outcome.&lt;/p&gt;
&lt;p&gt;A text error saying that memory allocation failed wasn't sufficient to classify the platform behavior.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.153&lt;/code&gt; added the missing observations.&lt;/p&gt;
&lt;p&gt;The completed matrix passed all eleven phases, retaining fourteen actual attempts in its evidence history.&lt;/p&gt;
&lt;p&gt;The separate classification step selected &lt;strong&gt;Case B: a distinct but resource-limited useful process&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That result changed the design.&lt;/p&gt;
&lt;p&gt;I didn't have evidence for a second large compute process. But I had enough evidence for a small coordination service.&lt;/p&gt;
&lt;p&gt;That was what I built.&lt;/p&gt;
&lt;h3 id=&quot;the-second-process-became-a-coordinator&quot;&gt;The second process became a coordinator&lt;/h3&gt;
&lt;p&gt;Instead of creating another general-purpose worker, I gave the auxiliary process a narrower responsibility.&lt;/p&gt;
&lt;p&gt;It could participate in lifecycle observation, heartbeat management and storage reservations.&lt;/p&gt;
&lt;p&gt;The main worker still controlled execution admission, semantic verification, recovery authorization and final result publication.&lt;/p&gt;
&lt;p&gt;This separation mattered because the broker could disappear and restart independently.&lt;/p&gt;
&lt;p&gt;A reservation could not simply be assumed valid because some old record said it existed.&lt;/p&gt;
&lt;p&gt;The worker had to validate the reservation identity, generation and storage plan before accepting new work.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.154&lt;/code&gt; tested this arrangement on the physical Xbox.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;55 of 55 checks passed, with zero violations.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The useful result wasn't more RAM.&lt;/p&gt;
&lt;p&gt;It was an operational coordination mechanism on which persistent computation could be built.&lt;/p&gt;
&lt;h3 id=&quot;next-i-needed-storage-that-could-survive-failure&quot;&gt;Next, I needed storage that could survive failure&lt;/h3&gt;
&lt;p&gt;Writing a file is easy compared with deciding whether that file represents completed computation.&lt;/p&gt;
&lt;p&gt;Imagine a workload producing hundreds of output pages.&lt;/p&gt;
&lt;p&gt;The application writes a page, updates some metadata, and then disappears.&lt;/p&gt;
&lt;p&gt;After restarting, which pages can be trusted?&lt;/p&gt;
&lt;p&gt;Did a page finish writing? Does its recorded hash match the bytes? Was its journal entry committed? Can the computation resume from that point without silently skipping or duplicating work?&lt;/p&gt;
&lt;p&gt;For XCP, I built the storage system around content-addressed data and an append-only journal.&lt;/p&gt;
&lt;p&gt;Each committed page had an identity derived from its content. The journal recorded ordered transitions, with records linked through hashes.&lt;/p&gt;
&lt;p&gt;A page was not considered committed just because its file existed.&lt;/p&gt;
&lt;p&gt;The system had to verify the relevant content and metadata before including it in the committed sequence.&lt;/p&gt;
&lt;p&gt;Recovery worked from the longest valid committed prefix. Anything beyond that boundary could not silently become trusted state.&lt;/p&gt;
&lt;p&gt;That approach allowed the runtime to distinguish a completed checkpoint from bytes left behind by an interrupted operation.&lt;/p&gt;
&lt;h4 id=&quot;then-i-tried-breaking-it&quot;&gt;Then I tried breaking it&lt;/h4&gt;
&lt;p&gt;Worker &lt;code&gt;.155&lt;/code&gt; exercised the page store and recovery system on Xbox.&lt;/p&gt;
&lt;p&gt;The tests included truncated journal entries, corrupted hashes, missing pages, incomplete root manifests, conflicting page identities, exhausted quotas, stale reservations and repeated cleanup.&lt;/p&gt;
&lt;p&gt;The live campaign also tested recovery after worker restart, auxiliary restart and package redeployment.&lt;/p&gt;
&lt;p&gt;One deployment experiment uncovered an unexpected problem.&lt;/p&gt;
&lt;p&gt;An in-place installation using the same package version left the application running, but Xbox Network Isolation silently dropped the inbound worker connection.&lt;/p&gt;
&lt;p&gt;The correction was not to pretend the storage operation had succeeded.&lt;/p&gt;
&lt;p&gt;We used a clean uninstall with application data preserved, reinstalled the exact package, restored connectivity and verified that the committed storage root had survived.&lt;/p&gt;
&lt;p&gt;The complete &lt;code&gt;.155&lt;/code&gt; campaign eventually passed &lt;strong&gt;248 of 248 checks&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At this point I had a storage substrate capable of preserving committed data through the tested failure and lifecycle scenarios.&lt;/p&gt;
&lt;p&gt;But I still hadn't demonstrated useful persistent computation.&lt;/p&gt;
&lt;p&gt;That required the next experiment.&lt;/p&gt;
&lt;h3 id=&quot;running-computation-through-persistent-storage&quot;&gt;Running computation through persistent storage&lt;/h3&gt;
&lt;p&gt;For the first workload, I reused an existing XCP test called &lt;em&gt;Provable Worlds&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It produces a deterministic field of 1024 × 1024 unsigned 32-bit values.&lt;/p&gt;
&lt;p&gt;The complete result is only 4 MiB.&lt;/p&gt;
&lt;p&gt;That may sound small for an SSD experiment, but this test wasn't intended to prove beyond-RAM scale yet.&lt;/p&gt;
&lt;p&gt;I wanted to establish that computation could be divided into independently checked units, committed in order, interrupted and resumed correctly.&lt;/p&gt;
&lt;p&gt;The field was divided into &lt;strong&gt;256 tiles of 16 KiB each&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Each tile had a known global position in the output. The signed CPU Capsule could compute its bounded portion, and XCP compared that result against the CPU reference.&lt;/p&gt;
&lt;p&gt;The runtime used bounded output buffers rather than requiring the complete result to stay resident.&lt;/p&gt;
&lt;p&gt;The important condition was that the durable cursor advanced only after a page had been committed successfully.&lt;/p&gt;
&lt;h4 id=&quot;the-first-uninterrupted-runs-passed-restart-failed-&quot;&gt;The first uninterrupted runs passed. Restart failed.&lt;/h4&gt;
&lt;p&gt;Worker &lt;code&gt;.156&lt;/code&gt; completed two uninterrupted executions.&lt;/p&gt;
&lt;p&gt;Then I tested the restart path.&lt;/p&gt;
&lt;p&gt;Both real-restart resume attempts failed.&lt;/p&gt;
&lt;p&gt;The problem was the reservation lifecycle.&lt;/p&gt;
&lt;p&gt;After the foreground interruption, the system attempted to continue using the old coordinator lease. But that lease belonged to the previous activation.&lt;/p&gt;
&lt;p&gt;This contradicted what the earlier process-topology experiments had already shown.&lt;/p&gt;
&lt;p&gt;Persistent data could survive the interruption.&lt;/p&gt;
&lt;p&gt;A live process reservation could not be treated the same way.&lt;/p&gt;
&lt;p&gt;So I separated those two concepts.&lt;/p&gt;
&lt;p&gt;The committed journal retained its original storage identity and generation. A resumed worker had to obtain a &lt;strong&gt;fresh live reservation&lt;/strong&gt;, with a newer generation, and reauthorize the persisted work before continuing.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.157&lt;/code&gt; implemented that correction.&lt;/p&gt;
&lt;p&gt;The test deliberately committed the first 128 tiles, computed the next tile without committing it, and interrupted execution.&lt;/p&gt;
&lt;p&gt;After the real restart, the worker verified the committed prefix, skipped those first 128 tiles and computed the remaining 128.&lt;/p&gt;
&lt;p&gt;It reproduced the expected final field hash.&lt;/p&gt;
&lt;p&gt;The uninterrupted tests, restart/resume path, cleanup and same-package regression checks passed.&lt;/p&gt;
&lt;p&gt;XCP admitted that specific bounded persistent-compute capability.&lt;/p&gt;
&lt;p&gt;One distinction is worth keeping: the tiled execution used the signed CPU Capsule, not a newly implemented GPU-tiled engine. The existing GPU path was checked separately. Nor did this test demonstrate performance gains from asynchronous I/O overlap.&lt;/p&gt;
&lt;p&gt;It demonstrated correct bounded computation and recovery.&lt;/p&gt;
&lt;p&gt;Now I wanted to investigate the storage scale.&lt;/p&gt;
&lt;h3 id=&quot;eight-gigabytes-and-another-failed-experiment&quot;&gt;Eight gigabytes, and another failed experiment&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qnntqurx0dxo2jvtwvkc.png&quot; alt=&quot;Conceptual illustration of XCP persistent storage research on Xbox Series X, showing an 8 GiB SSD experiment, verified data pages, recovery tests and a successful result.&quot;&gt;&lt;/p&gt;
&lt;p&gt;For the scale test, I moved to a different page geometry.&lt;/p&gt;
&lt;p&gt;Instead of the 16 KiB computation tiles, the storage characterization used &lt;strong&gt;4 MiB pages&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The target was 8 GiB: exactly 2,048 pages.&lt;/p&gt;
&lt;p&gt;I also defined a progression through 256 MiB, 512 MiB, 1 GiB, 2 GiB, 4 GiB and finally 8 GiB.&lt;/p&gt;
&lt;p&gt;The data was generated deterministically, with page-specific content, so the test could verify distinct pages without treating a single repeated blob as proof of physical growth.&lt;/p&gt;
&lt;p&gt;And there was a safety condition: the operation had to preserve &lt;strong&gt;16 GiB of reported available storage&lt;/strong&gt; as a reserve.&lt;/p&gt;
&lt;p&gt;Then came worker &lt;code&gt;.158&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The worker materialized the full 8 GiB.&lt;/p&gt;
&lt;p&gt;But the operation failed.&lt;/p&gt;
&lt;p&gt;After more than 163 seconds of storage work, the coordinator attempted to release its reservation using an IPC deadline that had been established at the beginning of the operation.&lt;/p&gt;
&lt;p&gt;That deadline was only 30 seconds.&lt;/p&gt;
&lt;p&gt;The data had been written, but the protocol could no longer complete the operation correctly.&lt;/p&gt;
&lt;p&gt;This was not an accepted result.&lt;/p&gt;
&lt;p&gt;I corrected the lease-release timeout handling, added an explicit cleanup path for the failed run and tested again.&lt;/p&gt;
&lt;p&gt;Worker &lt;code&gt;.159&lt;/code&gt; successfully committed all 2,048 pages.&lt;/p&gt;
&lt;p&gt;The measured seed operation took approximately 164 seconds.&lt;/p&gt;
&lt;p&gt;That is an end-to-end operation time, including page generation, hashing, storage and journal work. It isn't a raw SSD throughput benchmark.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;.159&lt;/code&gt; was not the final success either.&lt;/p&gt;
&lt;h4 id=&quot;the-64-gib-attempt-was-refused&quot;&gt;The 64 GiB attempt was refused&lt;/h4&gt;
&lt;p&gt;The broader storage policy allowed much larger declared reservations, so I also tested what happened when the system attempted to continue beyond the 8 GiB seed.&lt;/p&gt;
&lt;p&gt;The remaining planned growth was 56 GiB.&lt;/p&gt;
&lt;p&gt;The platform-reported filesystem space was nowhere near sufficient for that growth plus the required 16 GiB safety reserve.&lt;/p&gt;
&lt;p&gt;The worker returned:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;STORAGE_SCALE_PLATFORM_HEADROOM_REFUSED&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And crucially, it refused &lt;strong&gt;before writing additional data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The existing workspace remained unchanged. Cleanup subsequently removed the 8 GiB seed and a second cleanup confirmed that the namespace was already absent.&lt;/p&gt;
&lt;p&gt;This result did not establish that the Xbox SSD had a 64 GiB limit, or that the entire physical SSD was visible to the application.&lt;/p&gt;
&lt;p&gt;The application had observed a much smaller storage filesystem, approximately 32.2 billion bytes of reported capacity in that test.&lt;/p&gt;
&lt;p&gt;The configured storage quota was not the same as actual platform capacity.&lt;/p&gt;
&lt;p&gt;The refusal was correct.&lt;/p&gt;
&lt;p&gt;Instead of trying to force a larger allocation, I froze the demonstrated 8 GiB size as the target for the final characterization.&lt;/p&gt;
&lt;h3 id=&quot;the-result-worker-0-1-160-0&quot;&gt;The result: worker 0.1.160.0&lt;/h3&gt;
&lt;p&gt;Worker &lt;code&gt;.160&lt;/code&gt; was built and signed as a new exact package and tested on the physical Xbox.&lt;/p&gt;
&lt;p&gt;This time, the process completed the full storage lifecycle.&lt;/p&gt;
&lt;p&gt;It committed 2,048 distinct 4 MiB pages, totaling 8 GiB, then released the reservation.&lt;/p&gt;
&lt;p&gt;I returned to the Xbox Home screen and resumed the worker without power-cycling the console.&lt;/p&gt;
&lt;p&gt;The resumed worker obtained a fresh reservation generation, recovered the persisted namespace, reread and rehashed the complete 8 GiB, and committed the ordered root without allocating another storage rung.&lt;/p&gt;
&lt;p&gt;A separate verification operation then read the complete data again under another fresh lease and reproduced the same root identity.&lt;/p&gt;
&lt;p&gt;Finally, cleanup removed the 2,048 unreferenced blobs and all 8 GiB of their content.&lt;/p&gt;
&lt;p&gt;A second cleanup found nothing left to remove.&lt;/p&gt;
&lt;p&gt;The complete &lt;code&gt;.160&lt;/code&gt; lifecycle passed its checks.&lt;/p&gt;
&lt;p&gt;That was the accepted storage-scale result.&lt;/p&gt;
&lt;p&gt;Not 64 GiB. Not the console's full SSD. Not virtual RAM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eight GiB of bounded, recoverable, content-verified persistent storage, measured through the actual Xbox worker lifecycle.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-this-proves-and-what-it-doesn-t&quot;&gt;What this proves, and what it doesn't&lt;/h3&gt;
&lt;p&gt;The completed experiments establish two related capabilities.&lt;/p&gt;
&lt;p&gt;First, XCP demonstrated a persistent-compute workflow in which deterministic tiled execution could survive a real worker interruption, verify previous committed work and resume without recomputing the valid prefix.&lt;/p&gt;
&lt;p&gt;Second, it demonstrated an 8 GiB persistent storage namespace with recovery, full-root verification and cleanup on the physical Xbox.&lt;/p&gt;
&lt;p&gt;These were separate tests.&lt;/p&gt;
&lt;p&gt;The 4 MiB computation experiment did not prove that an arbitrary dataset larger than RAM could be processed end to end.&lt;/p&gt;
&lt;p&gt;The 8 GiB storage experiment did not prove an 8 GiB GPU or CPU workload, transparent paging, unrestricted SSD access or a performance-optimized streaming engine.&lt;/p&gt;
&lt;p&gt;In fact, the recorded streaming runtime exposed no measured prefetch/compute overlap advantage. The bounded buffers, ordered commits and recovery path were the capabilities being established.&lt;/p&gt;
&lt;p&gt;Combining those pieces into larger real workloads is further engineering work, not something I can claim was already completed.&lt;/p&gt;
&lt;p&gt;But the distinction between demonstrated components and future integration doesn't make the result less interesting.&lt;/p&gt;
&lt;p&gt;It tells me which parts of the problem are actually solved.&lt;/p&gt;
&lt;h3 id=&quot;why-the-failed-tests-mattered-more-than-the-number&quot;&gt;Why the failed tests mattered more than the number&lt;/h3&gt;
&lt;p&gt;Looking back, there wasn't one dramatic failure.&lt;/p&gt;
&lt;p&gt;There were several small, independent ones.&lt;/p&gt;
&lt;p&gt;The auxiliary process existed, but didn't prove an independent memory budget.&lt;/p&gt;
&lt;p&gt;The heartbeat worked, but the test expected the wrong number of ticks.&lt;/p&gt;
&lt;p&gt;Memory pressure produced a refusal, but not enough structured evidence to classify it.&lt;/p&gt;
&lt;p&gt;The storage layer survived, but an installation sequence disrupted the worker connection.&lt;/p&gt;
&lt;p&gt;Computation succeeded uninterrupted, but couldn't resume under the old lease.&lt;/p&gt;
&lt;p&gt;Eight gigabytes were written, but the IPC timeout invalidated the operation.&lt;/p&gt;
&lt;p&gt;And a larger storage request was refused because the available space could not satisfy the reserve.&lt;/p&gt;
&lt;p&gt;Each result forced a different correction.&lt;/p&gt;
&lt;p&gt;Some changed the implementation. Some changed the measurement contract. One changed the resource-topology decision. Another changed the scale we were prepared to claim.&lt;/p&gt;
&lt;p&gt;I think this is one of the most interesting aspects of XCP.&lt;/p&gt;
&lt;p&gt;It didn't become more capable by assuming the Xbox restrictions were irrelevant.&lt;/p&gt;
&lt;p&gt;It became more capable by discovering which capabilities could be combined reliably inside those restrictions.&lt;/p&gt;
&lt;p&gt;The result was not a magic way to make SSD behave like RAM.&lt;/p&gt;
&lt;p&gt;It was a storage and recovery architecture that allows computation to have a durable history independent of the process that produced it.&lt;/p&gt;
&lt;h3 id=&quot;the-code-is-now-open-source&quot;&gt;The code is now open source&lt;/h3&gt;
&lt;p&gt;The original Xbox experiments were performed in the historical XCP research environment.&lt;/p&gt;
&lt;p&gt;The public repository contains reconstructed reference implementations and the source needed to inspect the design, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Daniele-Cangi/xcp-xbox/blob/main/reference/xcompute-probe/src/XComputeProbe/runtime/WorkerContentAddressedStoreRecovery.cpp&quot;&gt;Content-addressed storage and recovery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Daniele-Cangi/xcp-xbox/blob/main/reference/xcompute-probe/src/XComputeProbe/runtime/WorkerProvableWorldsStreamingRuntime.cpp&quot;&gt;Persistent tiled compute runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Daniele-Cangi/xcp-xbox/blob/main/reference/xcompute-probe/src/XComputeProbe/runtime/WorkerStorageScaleCharacterizationRuntime.cpp&quot;&gt;8 GiB storage characterization runtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The distinction between the original historical results and the newly reconstructed development packages remains important.&lt;/p&gt;
&lt;p&gt;The public source lets developers inspect and rebuild the relevant components. The historical hardware results belong to their original package versions and evidence records.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Newly reconstructed packages do not automatically inherit those physical-Xbox results.&lt;/strong&gt; They require their own device validation.&lt;/p&gt;
&lt;p&gt;That boundary is explained in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Daniele-Cangi/xcp-xbox/blob/main/docs/RECONSTRUCTION_AUDIT.md&quot;&gt;public reconstruction audit&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;beyond-the-ssd-experiment&quot;&gt;Beyond the SSD experiment&lt;/h3&gt;
&lt;p&gt;XCP started as an attempt to make an Xbox Series X a programmable execution target.&lt;/p&gt;
&lt;p&gt;The project eventually expanded into a virtual machine, CPU/GPU execution, agent-driven development, a desktop studio and tools for adapting and building software.&lt;/p&gt;
&lt;p&gt;The SSD experiments explored another necessary part of that picture.&lt;/p&gt;
&lt;p&gt;A useful execution target needs more than the ability to run a program once.&lt;/p&gt;
&lt;p&gt;It needs a way to preserve work, recover from interruption and continue under a new execution context without pretending that the old one still exists.&lt;/p&gt;
&lt;p&gt;The most valuable result of this experiment wasn't the eight-gigabyte figure by itself.&lt;/p&gt;
&lt;p&gt;It was the realization that &lt;strong&gt;the lifetime of a computation does not have to be identical to the lifetime of the process executing it&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On this particular Xbox, we demonstrated some concrete parts of that idea.&lt;/p&gt;
&lt;p&gt;And as with the rest of XCP, the next meaningful step is to take those capabilities into a more demanding workload and find out where they stop being sufficient.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;XCP is open source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Daniele-Cangi/xcp-xbox&quot;&gt;https://github.com/Daniele-Cangi/xcp-xbox&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;XCP Studio: &lt;a rel=&quot;nofollow&quot; href=&quot;https://xcpstudio.com/&quot;&gt;https://xcpstudio.com/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30863/tried-to-build-persistent-compute-on-an-xbox-ssd-the-failed-tests-changed-the-architecture</guid>
<pubDate>Mon, 12 Oct 2026 21:54:26 +0000</pubDate>
</item>
<item>
<title>How a single 1-bit shift bug stayed hidden in NASA's communication protocol for years.</title>
<link>https://coderlegion.com/30570/how-a-single-1-bit-shift-bug-stayed-hidden-in-nasas-communication-protocol-for-years</link>
<description>&lt;p&gt;When developing flight software for spacecraft and rovers, standards are everything.&lt;/p&gt;
&lt;p&gt;NASA’s F Prime framework powers autonomous flight control and deep-space telemetry. While analyzing the CFDP (CCSDS File Delivery Protocol) implementation inside F Prime, something in the metadata serialization caught my eye.&lt;/p&gt;
&lt;p&gt;According to the official CCSDS 727.0-B-5 standard (Table 5-9): The closure_requested flag is specified at bit position 6 (mask 0x40).&lt;/p&gt;
&lt;p&gt;However, inside MetadataPdu.cpp, the serialization code was doing: closure_requested &amp;lt;&amp;lt; 7 (masking at bit 7, which is 0x80).&lt;/p&gt;
&lt;p&gt;A single-bit discrepancy. In deep-space operations, this meant:&lt;/p&gt;
&lt;p&gt;The closure request bit was never set where the CCSDS standard expected it.&lt;br&gt;
If the peer receiver relied strictly on the international specification, transaction closure signals would simply be misinterpreted or dropped.&lt;/p&gt;
&lt;p&gt;When I submitted the fix to NASA's repository, Core Maintainer Thomas Boyer-Chammard reviewed it and confirmed: &quot;This seems like a bug that was inherited from the cFS implementation.&quot;&lt;/p&gt;
&lt;p&gt;The PR (#6143) is now officially tagged as a bug fix in C++/CFDP, passed all automated unit tests and CI suites, and is queued for integration.&lt;/p&gt;
&lt;p&gt;The biggest takeaway for me as a systems engineer: Never take legacy codebases for granted—even space-grade frameworks. Always verify against first principles and the raw specification.&lt;/p&gt;
&lt;p&gt;PR details: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nasa/fprime/pull/6143&quot;&gt;https://github.com/nasa/fprime/pull/6143&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30570/how-a-single-1-bit-shift-bug-stayed-hidden-in-nasas-communication-protocol-for-years</guid>
<pubDate>Sat, 10 Oct 2026 09:03:21 +0000</pubDate>
</item>
<item>
<title>Write C++ like it's SQL</title>
<link>https://coderlegion.com/30002/write-c-like-its-sql</link>
<description>&lt;p&gt;A lot of C/C++ developers build complex data structures as if database theory never happened. And then they spend months maintaining pointer contracts that a schema could have made obvious.&lt;/p&gt;
&lt;p&gt;Let’s take a graph structure as an example.&lt;/p&gt;
&lt;p&gt;A common pattern looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Node;
struct Edge;

struct Node {
    uint32_t id;
    std::vector&amp;lt;Edge*&amp;gt; out_edges;
    void* payload;     // depends on node type
    uint32_t type;
};

struct Edge {
    Node* from;
    Node* to;
    void* payload;     // depends on edge type
    uint32_t type;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks flexible. In reality, it is a tiny undocumented database with manual referential integrity.Everyone must remember things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which Node::type maps to which payload struct&lt;/li&gt;
&lt;li&gt;which Edge::type maps to which payload struct&lt;/li&gt;
&lt;li&gt;whether payload == nullptr is allowed&lt;/li&gt;
&lt;li&gt;who owns node payloads&lt;/li&gt;
&lt;li&gt;who owns edge payloads&lt;/li&gt;
&lt;li&gt;whether deleting a node invalidates edge payloads&lt;/li&gt;
&lt;li&gt;how to serialize pointer graphs without losing meaning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And when requirements change, the whole thing turns into archaeology.  Now let’s model the same problem like people who know schemas exist.&lt;/p&gt;
&lt;p&gt;Start with identity:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using NodeId = uint32_t;
using EdgeId = uint32_t; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the core tables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct NodeRow {
    NodeId id;
};

struct EdgeRow {
    EdgeId id;
    NodeId from;
    NodeId to;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That alone already gives you something huge:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;edges are explicitly relations between nodes&lt;/li&gt;
&lt;li&gt;serialization is trivial&lt;/li&gt;
&lt;li&gt;deletion rules can be formalized&lt;/li&gt;
&lt;li&gt;validation is possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now add typed data associated with nodes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct TransformNode {
    NodeId node;
    float x, y, z;
};

struct LabelNode {
    NodeId node;
    std::string text;
};

struct ColorNode {
    NodeId node;
    uint8_t r, g, b, a;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And typed data associated with edges:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct WeightEdge {
    EdgeId edge;
    float weight;
};

struct CapacityEdge {
    EdgeId edge;
    uint32_t max_flow;
};

struct StyleEdge {
    EdgeId edge;
    uint32_t line_style;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the model tells the truth.&lt;/p&gt;
&lt;p&gt;A node may have a transform, or a label, or both, or neither. An edge may have a weight, a capacity, or some other attribute. No void*. No “if type == 3 cast to Foo”. No comment-based schema.&lt;/p&gt;
&lt;p&gt;Need multiplicity rules?&lt;/p&gt;
&lt;p&gt;Make them explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one TransformNode row per node&lt;/li&gt;
&lt;li&gt;zero or one WeightEdge row per edge&lt;/li&gt;
&lt;li&gt;many LabelNode rows if your model allows multilingual labels&lt;/li&gt;
&lt;li&gt;no EdgeRow may reference missing nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is database thinking.&lt;/p&gt;
&lt;p&gt;And it buys you real benefits even in plain C++:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;strong typing instead of tribal knowledge&lt;/li&gt;
&lt;li&gt;explicit foreign keys instead of raw pointers&lt;/li&gt;
&lt;li&gt;easy serialization&lt;/li&gt;
&lt;li&gt;easy validation&lt;/li&gt;
&lt;li&gt;better data locality with arrays of records&lt;/li&gt;
&lt;li&gt;queryable structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples of useful queries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find all edges whose to node is missing a transform&lt;/li&gt;
&lt;li&gt;find all nodes with outgoing edges but no label&lt;/li&gt;
&lt;li&gt;find all weighted edges crossing between two node groups&lt;/li&gt;
&lt;li&gt;find dangling relations after a delete&lt;/li&gt;
&lt;li&gt;find all data attached to a given node or edge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try doing that cleanly in a pointer soup.&lt;/p&gt;
&lt;p&gt;This is why I keep saying:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Many C/C++ codebases are just badly normalized databases with manual&lt;br&gt;
memory management.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The irony is that game developers already stumbled into one good answer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ECS.&lt;/strong&gt; (or Entity-Component-System)&lt;/p&gt;
&lt;p&gt;Entities are IDs. Components are typed tables. Systems are queries.&lt;/p&gt;
&lt;p&gt;That idea is not limited to games. Graphs, CAD tools, compilers, pipelines, simulation systems, scene graphs, workflow engines — all of them can benefit from the same data-first thinking.&lt;/p&gt;
&lt;p&gt;The real lesson is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Before you design your structs like a C programmer, design your data&lt;br&gt;
like a database engineer. Then make it fast like a systems programmer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“Nice theory… but won’t this be slow?”&lt;/p&gt;
&lt;p&gt;That’s the first reaction when you suggest modeling C++ data like a database. Fair question. Also mostly wrong.&lt;/p&gt;
&lt;p&gt;Let’s take the graph model from the previous post and talk about how you actually make it fast.&lt;/p&gt;
&lt;p&gt;First mistake people imagine:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Tables + IDs = like SQL = slow joins”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No. You are not building a database engine. You are building arrays with structure.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Storage: SoA beats pointer soup&lt;br&gt;
Instead of scattered heap objects:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Node* -&amp;gt; Edge* -&amp;gt; Node* -&amp;gt; payload*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You store dense arrays:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::vector&amp;lt;NodeRow&amp;gt; nodes;
std::vector&amp;lt;EdgeRow&amp;gt; edges;
std::vector&amp;lt;TransformNode&amp;gt; transforms;
std::vector&amp;lt;WeightEdge&amp;gt; weights;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sequential memory&lt;/li&gt;
&lt;li&gt;predictable access&lt;/li&gt;
&lt;li&gt;prefetch-friendly&lt;/li&gt;
&lt;li&gt;no pointer chasing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This alone often wins before you optimize anything else.&lt;/p&gt;
&lt;p&gt;Aside of pure performance the inspection and debugging of such data organization is way simpler than chasing void* pointer soup.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;IDs are cheaper than pointers (in practice)&lt;br&gt;
A pointer graph:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;random memory access&lt;/li&gt;
&lt;li&gt;poor cache locality&lt;/li&gt;
&lt;li&gt;hard to batch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An ID-based graph:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NodeId -&amp;gt; index lookup -&amp;gt; contiguous data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, you pay an indirection. But you remove cache misses. On modern CPUs, that’s usually a net win.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“Joins” are just loops&lt;br&gt;
Example:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;find all edges with weights where from node has a transform&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You don’t need SQL.&lt;/p&gt;
&lt;p&gt;You do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (auto&amp;amp; w : weights) {
    auto&amp;amp; edge = edges[w.edge];
    if (has_transform[edge.from]) {
        // process
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no virtual dispatch&lt;/li&gt;
&lt;li&gt;no pointer casting&lt;/li&gt;
&lt;li&gt;no branching on “type”&lt;/li&gt;
&lt;li&gt;no cache-miss lottery&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just tight loops over arrays. Eventually you may even come up with an idea of implementing a language integrated query (LINQ) framework.                                         &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build indices where needed&lt;br&gt;
If something is frequent, index it:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;std::vector&amp;lt;std::vector&amp;lt;EdgeId&amp;gt;&amp;gt; edges_from_node;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;std::unordered_map&amp;lt;NodeId, TransformNode&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or even better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sorted vectors + binary search&lt;/li&gt;
&lt;li&gt;dense bitsets&lt;/li&gt;
&lt;li&gt;sparse sets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You choose the structure per access pattern. Not everything needs to be O(1). Everything needs to be &lt;em&gt;predictable&lt;/em&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optional data without branching hell&lt;br&gt;
Instead of:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;if (node-&amp;gt;payload &amp;amp;&amp;amp; node-&amp;gt;type == TRANSFORM)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;presence in table&lt;/li&gt;
&lt;li&gt;or a bitset mask&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;if (has_transform[node_id]) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Branch becomes predictable. Data becomes separate. Code becomes honest.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Memory management becomes trivial&lt;br&gt;
Pointer model:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;who owns what?&lt;/li&gt;
&lt;li&gt;who deletes what?&lt;/li&gt;
&lt;li&gt;what invalidates what?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Table model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vectors own memory&lt;/li&gt;
&lt;li&gt;IDs stay stable (or use handles/generations)&lt;/li&gt;
&lt;li&gt;bulk free is trivial                                                                                                                                                                                                                             - serialization is dump-and-load, computing diffs is trivial (multiplayer game state synchronization anyone?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You stop fighting lifetime bugs.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You can still go “full performance mode”&lt;br&gt;
Nothing stops you from:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;struct-of-arrays layouts&lt;/li&gt;
&lt;li&gt;SIMD over columns&lt;/li&gt;
&lt;li&gt;chunking (archetypes like ECS)&lt;/li&gt;
&lt;li&gt;custom allocators / arenas&lt;/li&gt;
&lt;li&gt;memory mapping&lt;/li&gt;
&lt;li&gt;compression of columns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In fact, this model makes those optimizations easier, not harder.&lt;/p&gt;
&lt;p&gt;The uncomfortable truth&lt;br&gt;
Pointer-heavy designs feel “low level”. But they often produce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;worse cache behavior&lt;/li&gt;
&lt;li&gt;harder reasoning&lt;/li&gt;
&lt;li&gt;fragile invariants&lt;/li&gt;
&lt;li&gt;slower real-world performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because modern performance is not about pointer flexibility. It’s about &lt;strong&gt;data layout and predictability&lt;/strong&gt;.                                                                                &lt;/p&gt;
&lt;p&gt;The mental shift&lt;br&gt;
Stop thinking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“What does this object point to?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start thinking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Where does this fact live, and how do I iterate it efficiently?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Database-style modeling is not about SQL.&lt;/p&gt;
&lt;p&gt;It’s about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;explicit structure&lt;/li&gt;
&lt;li&gt;normalized data&lt;/li&gt;
&lt;li&gt;predictable access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that’s exactly what high-performance C++ needs. Next time you reach for *&lt;em&gt;void&lt;/em&gt; + enum** …ask yourself: Am I designing a data structure?&lt;br&gt;
Or quietly building a database without admitting it?&lt;/p&gt;
&lt;p&gt;Database-style modeling is powerful in C++. But it is not always the right tool. This matters, because every good idea becomes harmful when people turn it into a religion.&lt;br&gt;
So when should you not model everything as tables + IDs?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When behavior matters more than data&lt;br&gt;
Sometimes the central thing is not “what facts exist?”&lt;br&gt;
Sometimes the central thing is:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;“What operation should this object perform?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Shape {
    virtual float area() const = 0;
    virtual void draw(Renderer&amp;amp;) const = 0;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a small, stable set of objects with rich behavior, polymorphism may be cleaner than forcing everything into component tables.&lt;/p&gt;
&lt;p&gt;A table model is great for &lt;strong&gt;querying data&lt;/strong&gt;. An object model is often better for &lt;strong&gt;encapsulating behavior&lt;/strong&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the hierarchy is real and stable&lt;br&gt;
If your domain naturally has a closed hierarchy:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Circle
Rectangle
Triangle 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;em&gt;std::variant&lt;/em&gt; can be excellent:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Shape = std::variant&amp;lt;Circle, Rectangle, Triangle&amp;gt;; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;strong&gt;void&lt;/strong&gt;*. No fake database. No dynamic allocation required. Just a type-safe tagged union.&lt;br&gt;
This is much better than pretending every tiny hierarchy needs an ID table.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When object identity is not important&lt;br&gt;
Tables shine when identity and relationships matter. But not everything has identity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A Vec3, Color, Matrix, Rect, Range, or BoundingBox is usually just a value. Do not give every value object an ID. That way lies madness.&lt;/p&gt;
&lt;p&gt;Sometimes the right model is simply:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Color {
    uint8_t r, g, b, a;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not every struct is secretly a table row.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the data is tiny and local&lt;br&gt;
If a structure is:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;small&lt;/li&gt;
&lt;li&gt;short-lived&lt;/li&gt;
&lt;li&gt;private to one algorithm&lt;/li&gt;
&lt;li&gt;not serialized&lt;/li&gt;
&lt;li&gt;not shared across systems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then simple direct structs are often best.&lt;br&gt;
Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Candidate {
    float score;
    uint32_t index;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No schema needed. No registry. No component store.&lt;br&gt;
Just code.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When traversal is the whole point&lt;br&gt;
Some structures are naturally pointer-shaped.&lt;br&gt;
Examples:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;intrusive linked lists&lt;/li&gt;
&lt;li&gt;trees with parent pointers&lt;/li&gt;
&lt;li&gt;arena-allocated AST nodes&lt;/li&gt;
&lt;li&gt;graph algorithms optimized around adjacency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A table representation can still work, but it may not be worth the extra ceremony.&lt;br&gt;
If the dominant operation is “walk this exact shape,” direct references can be perfectly reasonable.&lt;br&gt;
The mistake is not using pointers. The mistake is using pointers as an undocumented schema.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When lifetime is simple and ownership is obvious&lt;br&gt;
If ownership is clear:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;std::unique_ptr&amp;lt;Node&amp;gt; root; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the whole structure dies together, then a pointer graph may be fine.&lt;br&gt;
Especially with arenas.&lt;br&gt;
A compiler AST is a good example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build it&lt;/li&gt;
&lt;li&gt;analyze it&lt;/li&gt;
&lt;li&gt;maybe transform it&lt;/li&gt;
&lt;li&gt;destroy it all at once&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That can be simple and fast.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the abstraction boundary is behavior-based&lt;br&gt;
Sometimes the whole point is to hide the implementation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct StorageBackend {
    virtual void write(std::span&amp;lt;const std::byte&amp;gt;) = 0;
    virtual std::vector&amp;lt;std::byte&amp;gt; read(Key) = 0;
}; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You do not want callers reaching into tables here.&lt;br&gt;
You want an interface.&lt;br&gt;
Data-first design is not an excuse to destroy encapsulation.&lt;/p&gt;
&lt;p&gt;So what is the rule?&lt;br&gt;
Use table/ID/database-style modeling when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;identity matters&lt;/li&gt;
&lt;li&gt;relationships matter&lt;/li&gt;
&lt;li&gt;serialization matters&lt;/li&gt;
&lt;li&gt;validation matters&lt;/li&gt;
&lt;li&gt;many systems touch the same data&lt;br&gt;
the model evolves over time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use object/value/polymorphic design when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;behavior matters more than relationships&lt;/li&gt;
&lt;li&gt;data is local&lt;/li&gt;
&lt;li&gt;hierarchy is small and stable&lt;/li&gt;
&lt;li&gt;ownership is simple&lt;/li&gt;
&lt;li&gt;encapsulation matters more than introspection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The real enemy is not OOP. The real enemy is informal contracts.&lt;br&gt;
*&lt;em&gt;void&lt;/em&gt; + enum + comment** is not a design pattern.&lt;br&gt;
It is a schema that gave up.&lt;/p&gt;
&lt;p&gt;Good C++ is not “always objects.” Good C++ is not “always tables.”&lt;br&gt;
Good C++ is choosing the representation that makes illegal states hard, common operations fast, and future maintenance survivable.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30002/write-c-like-its-sql</guid>
<pubDate>Tue, 06 Oct 2026 12:59:20 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 25 — User-defined literals</title>
<link>https://coderlegion.com/29758/tuesday-coding-tip-25-user-defined-literals</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;C++ has many built-in literals for disambiguating types of constants, such as &lt;code&gt;u&lt;/code&gt; for unsigned, &lt;code&gt;f&lt;/code&gt; for float, or time-related literals from the chrono header (like &lt;code&gt;ms&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt;, or &lt;code&gt;min&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The really cool thing is that you can define your own literals (since C++11) to make your domain-specific constants more legible. All these literals overload the &lt;code&gt;operator“”&lt;/code&gt; with your custom &lt;code&gt;_suffix&lt;/code&gt;. The data type that is supposed to be before &lt;code&gt;_suffix&lt;/code&gt; is then determined by the function parameter type (consult the documentation for allowed parameter types: &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/language/user_literal&quot;&gt;https://en.cppreference.com/w/cpp/language/user_literal&lt;/a&gt; ).&lt;/p&gt;
&lt;p&gt;Thanks to compile-time computing, these literals don’t have to imply any runtime overhead for your code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Language-provided literals
const auto foo = 100u; // implies unsigned int
const auto bar = 10.f; // implies float instead of double

// Library-provided literals
#include &amp;lt;chrono&amp;gt;
using namespace std::literals;

const auto baz = &quot;hello&quot;s; // implies std::string instead of const char*
constexpr auto day = 14d; // If the value &amp;gt; 255, then the stored value is unspecified. 
constexpr auto twoDays = 48h;
constexpr auto week = 24h* 7;
constexpr auto my_duration_unit = 108ms;
constexpr auto theYear = 2021y;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An interesting use-case that I found is that literals can be leveraged to make described bool constants without having to wrap them in a temporary.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Literal that describes a constant in pixel units
[[nodiscard]] consteval unsigned long long operator&quot;&quot;_px(unsigned long long value) {
    return value;
}

const auto SCREEN_WIDTH = 1280_px;

#include &amp;lt;cstddef&amp;gt;

[[nodiscard]] consteval bool operator&quot;&quot;_false(const char*, std::size_t) {
    return false;
}

[[nodiscard]] consteval bool operator&quot;&quot;_true(const char*, std::size_t) {
    return true;
}

foo(&quot;isWindowOpen&quot;_true, &quot;isFullscreen&quot;_false);
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29758/tuesday-coding-tip-25-user-defined-literals</guid>
<pubDate>Tue, 06 Oct 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Semantic Search in C++ without Python, libtorch or ONNX Runtime</title>
<link>https://coderlegion.com/29317/semantic-search-in-c-without-python-libtorch-or-onnx-runtime</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Semantic Search in C++ without Python, libtorch or ONNX Runtime&lt;br&gt;
published: true&lt;br&gt;
description: Run a transformer model from C++ in four commands. No libtorch, no ONNX Runtime, no Python, no model conversion. Works from C++11 upward.&lt;br&gt;
tags: cpp, machinelearning, rust, opensource&lt;/p&gt;
&lt;h3 id=&quot;canonical-url-https-kjarni-ai-blog-cppinference-&quot;&gt;canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/cppinference/&quot;&gt;https://kjarni.ai/blog/cppinference/&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ask how to run a transformer model from C++ and you get two answers: link libtorch, or convert the model and link ONNX Runtime. Both work. Both are large, both want a toolchain of their own, and both put a second inference engine inside your process.&lt;/p&gt;
&lt;p&gt;There is a third answer, and it takes four commands.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir kjarni-quickstart &amp;amp;&amp;amp; cd kjarni-quickstart
curl -sL https://github.com/olafurjohannsson/kjarni/releases/latest/download/kjarni-x86_64-linux.tar.gz | tar xz
curl -sO https://raw.githubusercontent.com/olafurjohannsson/kjarni/main/crates/kjarni-ffi/examples/cpp/hello.cpp
g++ -std=c++23 hello.cpp -I. -L. -lkjarni_ffi -Wl,-rpath,'$ORIGIN' -o hello &amp;amp;&amp;amp; ./hello
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;related:   0.5510
unrelated: -0.0630
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a transformer model, downloaded, loaded and run, from an empty directory. No package manager, no Python, no model conversion step. The archive holds the shared library, &lt;code&gt;kjarni.h&lt;/code&gt; (the C ABI) and &lt;code&gt;kjarni.hpp&lt;/code&gt; (a header-only C++23 wrapper). macOS and Windows&lt;br&gt;
builds are on the same [releases page(&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni/releases&quot;&gt;https://github.com/olafurjohannsson/kjarni/releases&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Here is what &lt;code&gt;hello.cpp&lt;/code&gt; contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.hpp&quot;
#include &amp;lt;print&amp;gt;

int main() {
    // Downloaded once and cached under ~/.cache/kjarni, then loaded from disk.
    auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
    if (!embedder) {
        std::println(&quot;{}&quot;, embedder.error().message());
        return 1;
    }

    auto question = embedder-&amp;gt;encode(&quot;How do I get my money back?&quot;);
    auto related = embedder-&amp;gt;encode(&quot;What is your refund policy?&quot;);
    auto unrelated = embedder-&amp;gt;encode(&quot;The weather in Reykjavik is unpredictable.&quot;);

    // No shared words with the question, but the same meaning.
    std::println(&quot;related:   {:.4f}&quot;, kjarni::cosine(*question, *related));
    std::println(&quot;unrelated: {:.4f}&quot;, kjarni::cosine(*question, *unrelated));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&quot;How do I get my money back?&quot; and &quot;What is your refund policy?&quot; share no words at all, and score 0.55. The sentence about the weather scores below zero. That gap is the entire idea behind semantic search.&lt;/p&gt;
&lt;h3 id=&quot;how-semantic-search-works&quot;&gt;How semantic search works&lt;/h3&gt;
&lt;p&gt;An embedding model reads text and returns a vector, an array of floats, 384 numbers for the model above. Text with similar meaning produces vectors that point in similar directions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;refund policy&quot;  -&amp;gt; [0.12, -0.34, 0.56, ...]  (384 numbers)
&quot;get money back&quot; -&amp;gt; [0.11, -0.33, 0.55, ...]  (384 numbers)  &amp;lt;- close
&quot;weather today&quot;  -&amp;gt; [-0.45, 0.23, -0.12, ...] (384 numbers)  &amp;lt;- far
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You compare two vectors with cosine similarity, which measures the angle between them and ignores their length. It runs from 1 for identical direction to -1 for opposite.&lt;/p&gt;
&lt;h3 id=&quot;what-you-actually-link-against&quot;&gt;What you actually link against&lt;/h3&gt;
&lt;p&gt;This is the part that decides whether the approach is worth anything, so it is worth checking rather than believing. Ask the linker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ ldd hello
    linux-vdso.so.1
    libkjarni_ffi.so =&amp;gt; /home/you/kjarni-quickstart/libkjarni_ffi.so
    libstdc++.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libstdc++.so.6
    libm.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libm.so.6
    libgcc_s.so.1 =&amp;gt; /lib/x86_64-linux-gnu/libgcc_s.so.1
    libc.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libc.so.6
    /lib64/ld-linux-x86-64.so.2
    libpthread.so.0 =&amp;gt; /lib/x86_64-linux-gnu/libpthread.so.0
    libdl.so.2 =&amp;gt; /lib/x86_64-linux-gnu/libdl.so.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kjarni, the C++ runtime, and the parts of glibc every program already uses: libc, libm, libgcc, libpthread and libdl. That is the whole list. No libtorch, no &lt;code&gt;onnxruntime&lt;/code&gt;, no Python, no CUDA runtime. The binary is 283 KB and the library is 19.4 MB, which includes the tokenizer, the model loaders and every kernel.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-Wl,-rpath,'$ORIGIN'&lt;/code&gt; in the build line is what makes that first entry resolve to the library sitting next to your binary rather than something in &lt;code&gt;/usr/local/lib&lt;/code&gt;. Keep it, and&lt;br&gt;
the directory you built in is a directory you can copy somewhere else and run.&lt;/p&gt;
&lt;p&gt;A dependency you cannot see in &lt;code&gt;ldd&lt;/code&gt; is a dependency that cannot break you on a machine that is not yours.&lt;/p&gt;
&lt;h3 id=&quot;errors-are-values&quot;&gt;Errors are values&lt;/h3&gt;
&lt;p&gt;Every fallible call returns &lt;code&gt;std::expected&amp;lt;T, kjarni::Error&amp;gt;&lt;/code&gt;. Nothing in the header throws except &lt;code&gt;std::bad_alloc&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
if (!embedder) {
    std::println(stderr, &quot;could not load model: {}&quot;, embedder.error().message());
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether a missing model file is exceptional depends on the program. A batch job should die; a desktop application should show a message and carry on. Returning the failure lets the caller decide, and puts it in the signature where it cannot be missed.&lt;/p&gt;
&lt;p&gt;The options are a designated-initialiser aggregate, so a call names only what it changes:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Embedder::create({.model = &quot;mpnet-base-v2&quot;, .gpu = true})&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;searching-a-corpus&quot;&gt;Searching a corpus&lt;/h3&gt;
&lt;p&gt;Encode the documents once, encode the query at search time, sort by similarity.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.hpp&quot;

#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;print&amp;gt;
#include &amp;lt;ranges&amp;gt;
#include &amp;lt;string_view&amp;gt;
#include &amp;lt;vector&amp;gt;

int main() {
    auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
    if (!embedder) {
        std::println(stderr, &quot;could not load model: {}&quot;, embedder.error().message());
        return 1;
    }

    constexpr std::array docs = {
        std::string_view{&quot;How do I reset my password?&quot;},
        std::string_view{&quot;What is your refund policy?&quot;},
        std::string_view{&quot;Do you ship internationally?&quot;},
        std::string_view{&quot;How do I update my billing address?&quot;},
        std::string_view{&quot;Where can I track my order?&quot;},
    };

    std::vector&amp;lt;std::vector&amp;lt;float&amp;gt;&amp;gt; corpus;
    corpus.reserve(docs.size());
    for (std::string_view d : docs) {
        auto v = embedder-&amp;gt;encode(d);
        if (!v) {
            std::println(stderr, &quot;encode failed: {}&quot;, v.error().message());
            return 1;
        }
        corpus.push_back(v-&amp;gt;to_vector());
    }

    constexpr std::string_view query = &quot;I need to change my login credentials&quot;;
    auto q = embedder-&amp;gt;encode(query);
    if (!q) {
        std::println(stderr, &quot;encode failed: {}&quot;, q.error().message());
        return 1;
    }

    std::vector&amp;lt;std::pair&amp;lt;float, std::string_view&amp;gt;&amp;gt; scored;
    for (auto [i, doc] : std::views::enumerate(docs))
        scored.emplace_back(kjarni::cosine(q-&amp;gt;values(), corpus[i]), doc);

    std::ranges::sort(scored, std::ranges::greater{},
                      &amp;amp;std::pair&amp;lt;float, std::string_view&amp;gt;::first);

    std::println(&quot;query: \&quot;{}\&quot;&quot;, query);
    for (auto [score, doc] : scored)
        std::println(&quot;  {:.4f}  {}&quot;, score, doc);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;query: &quot;I need to change my login credentials&quot;
  0.5981  How do I reset my password?
  0.4067  How do I update my billing address?
  0.0767  Where can I track my order?
  -0.0027  What is your refund policy?
  -0.0451  Do you ship internationally?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&quot;Change my login credentials&quot; matches &quot;reset my password&quot; at 0.60 while sharing no words with it, and &quot;update my billing address&quot; comes second because changing account details is a&lt;br&gt;
related idea. That is what you cannot get from keyword matching.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;encode&lt;/code&gt; returns a &lt;code&gt;kjarni::Embedding&lt;/code&gt;, which owns the array the C API returned and frees it in its destructor. It hands out a &lt;code&gt;std::span&amp;lt;const float&amp;gt;&lt;/code&gt; through &lt;code&gt;values()&lt;/code&gt;, so it drops&lt;br&gt;
straight into ranges, and &lt;code&gt;to_vector()&lt;/code&gt; copies when the data has to outlive the object. There is no raw pointer to forget.&lt;/p&gt;
&lt;h3 id=&quot;classification-and-reranking&quot;&gt;Classification and reranking&lt;/h3&gt;
&lt;p&gt;The other models follow the same shape.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto clf = kjarni::Classifier::create({.model = &quot;roberta-sentiment&quot;});
if (!clf) { std::println(stderr, &quot;{}&quot;, clf.error().message()); return 1; }

for (std::string_view t : {&quot;I love this product!&quot;,
                           &quot;Terrible quality, broke after one day.&quot;}) {
    auto top = clf-&amp;gt;top(t);
    if (!top) { std::println(stderr, &quot;{}&quot;, top.error().message()); return 1; }
    if (*top) std::println(&quot;  {:&amp;lt;9} {:5.1f}%  \&quot;{}\&quot;&quot;, (*top)-&amp;gt;name, (*top)-&amp;gt;score * 100, t);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;  positive   98.5%  &quot;I love this product!&quot;
  negative   94.1%  &quot;Terrible quality, broke after one day.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;top()&lt;/code&gt; returns &lt;code&gt;Result&amp;lt;std::optional&amp;lt;Label&amp;gt;&amp;gt;&lt;/code&gt;, which puts two separate questions in the type: did the call work, and did it produce a label. A model with nothing above threshold is&lt;br&gt;
not a failure.&lt;/p&gt;
&lt;p&gt;Reranking uses a cross-encoder, which reads the query and each document together instead of comparing two independently produced vectors. It is slower and much more precise, so it runs&lt;br&gt;
as a second pass over whatever the embeddings retrieved:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto rr = kjarni::Reranker::create();
if (!rr) { std::println(stderr, &quot;{}&quot;, rr.error().message()); return 1; }

const std::vector&amp;lt;std::string&amp;gt; docs = {
    &quot;Machine learning is a subset of artificial intelligence.&quot;,
    &quot;Deep learning uses neural networks with many layers.&quot;,
    &quot;The weather today is sunny.&quot;,
};

auto ranked = rr-&amp;gt;rerank(&quot;What is machine learning?&quot;, docs);
if (!ranked) { std::println(stderr, &quot;{}&quot;, ranked.error().message()); return 1; }

for (const auto&amp;amp; r : *ranked)
    std::println(&quot;  {:&amp;gt;9.4f}  {}&quot;, r.score, docs[r.index]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;    10.5139  Machine learning is a subset of artificial intelligence.
    -5.5301  Deep learning uses neural networks with many layers.
   -11.1001  The weather today is sunny.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scores are logits, not probabilities. What matters is the ordering and the size of the gap between one document and the next. &lt;code&gt;Ranked&lt;/code&gt; gives back an index into your input rather than a copy of the text, so whatever IDs, URLs and permissions came with your documents stay attached to them.&lt;/p&gt;
&lt;p&gt;One signature detail: &lt;code&gt;rerank&lt;/code&gt; takes &lt;code&gt;std::span&amp;lt;const std::string&amp;gt;&lt;/code&gt; rather than &lt;code&gt;string_view&lt;/code&gt;, because the C call underneath needs an array of null terminated pointers and a &lt;code&gt;string_view&lt;/code&gt; does not promise one.&lt;/p&gt;
&lt;h3 id=&quot;the-same-numbers-in-every-language&quot;&gt;The same numbers in every language&lt;/h3&gt;
&lt;p&gt;Those figures are not specific to C++. The reranker scores 10.5139, -5.5301 and -11.1001 are&lt;br&gt;
the same values the &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/documentsearchengine/&quot;&gt;C# document search post&lt;/a&gt; prints, and the&lt;br&gt;
similarity scores are the ones in &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/semanticsearch/&quot;&gt;Semantic Search in C#&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That is the reason a C ABI is the right shape here. There is one engine and one set of&lt;br&gt;
kernels, and the C++ header is a wrapper over the same entry points the C#, Go and Python&lt;br&gt;
packages call. There is no second implementation to drift from the first.&lt;/p&gt;
&lt;h3 id=&quot;choosing-a-model&quot;&gt;Choosing a model&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Model &lt;/th&gt;&lt;th&gt;Dimensions &lt;/th&gt;&lt;th&gt;Input limit &lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;minilm-l6-v2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;384 &lt;/td&gt;&lt;td&gt;256 tokens &lt;/td&gt;&lt;td&gt;Default. Fast, good quality per byte&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;mpnet-base-v2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;768 &lt;/td&gt;&lt;td&gt;384 tokens &lt;/td&gt;&lt;td&gt;Higher quality, slower&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nomic-embed-text&lt;/code&gt; &lt;/td&gt;&lt;td&gt;768 &lt;/td&gt;&lt;td&gt;8192 tokens &lt;/td&gt;&lt;td&gt;Long documents, though trained at 2048&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;bge-m3&lt;/code&gt; &lt;/td&gt;&lt;td&gt;1024 &lt;/td&gt;&lt;td&gt;8192 tokens &lt;/td&gt;&lt;td&gt;Large, multilingual&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mind the input limit column. &lt;code&gt;minilm-l6-v2&lt;/code&gt; reads 256 tokens, roughly 900 characters, and silently drops the rest: no error, no warning, just a vector computed from the part it saw.&lt;br&gt;
The cross-encoder has its own limit, reading query and document as one sequence capped at 512 tokens. If your documents are longer than that, chunk them. There is a measurement of&lt;br&gt;
what the truncation costs in &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/embedding-truncation/&quot;&gt;Your MiniLM Embeddings Are Probably Truncating at 256 Tokens&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;practical-notes&quot;&gt;Practical notes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Threading.&lt;/strong&gt; The handles are not individually thread safe. Give each thread its own, or serialise calls. The engine already parallelises across cores inside a single call, so one embedder will use the machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C++23 is only needed for &lt;code&gt;std::expected&lt;/code&gt;.&lt;/strong&gt; Everything else in &lt;code&gt;kjarni.hpp&lt;/code&gt; is C++20, and GCC 13 or Clang 17 and newer will build it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There is no package manager integration yet.&lt;/strong&gt; No Conan recipe, no vcpkg port. The four commands above are the install story on Linux, and the equivalent archives for macOS and&lt;br&gt;
Windows are on the releases page.&lt;/p&gt;
&lt;h3 id=&quot;if-you-are-on-c-11-or-c-17&quot;&gt;If you are on C++11 or C++17&lt;/h3&gt;
&lt;p&gt;Plenty of codebases are, and a header that demands C++23 is not much use to them. It is worth being clear about what the requirement actually covers: &lt;code&gt;kjarni.hpp&lt;/code&gt; needs C++23 for&lt;br&gt;
&lt;code&gt;std::expected&lt;/code&gt;, and nothing else does. &lt;code&gt;kjarni.h&lt;/code&gt; is plain C, it is the interface every other language binding is built on, and it works from C++11 upward.&lt;/p&gt;
&lt;p&gt;The whole of the C++23 convenience is one RAII wrapper and a copy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.h&quot;

#include &amp;lt;cstdio&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

namespace {

struct EmbedderDeleter {
    void operator()(KjarniEmbedder* p) const { kjarni_embedder_free(p); }
};
using EmbedderPtr = std::unique_ptr&amp;lt;KjarniEmbedder, EmbedderDeleter&amp;gt;;

std::vector&amp;lt;float&amp;gt; encode(KjarniEmbedder* emb, const char* text) {
    KjarniFloatArray arr{};
    if (kjarni_embedder_encode(emb, text, &amp;amp;arr) != KJARNI_ERROR_CODE_OK) {
        std::fprintf(stderr, &quot;encode failed: %s\n&quot;, kjarni_last_error_message());
        return {};
    }
    std::vector&amp;lt;float&amp;gt; out(arr.data, arr.data + arr.len);
    kjarni_float_array_free(arr);   // copied out, so release the engine's buffer
    return out;
}

} // namespace

int main() {
    KjarniEmbedderConfig cfg = kjarni_embedder_config_default();
    cfg.model_name = &quot;minilm-l6-v2&quot;;
    cfg.quiet = 1;

    KjarniEmbedder* raw = nullptr;
    if (kjarni_embedder_new(&amp;amp;cfg, &amp;amp;raw) != KJARNI_ERROR_CODE_OK) {
        std::fprintf(stderr, &quot;could not load model: %s\n&quot;, kjarni_last_error_message());
        return 1;
    }
    EmbedderPtr embedder(raw);

    const std::vector&amp;lt;float&amp;gt; question  = encode(embedder.get(), &quot;How do I get my money back?&quot;);
    const std::vector&amp;lt;float&amp;gt; related   = encode(embedder.get(), &quot;What is your refund policy?&quot;);
    const std::vector&amp;lt;float&amp;gt; unrelated = encode(embedder.get(), &quot;The weather in Reykjavik is unpredictable.&quot;);
    if (question.empty() || related.empty() || unrelated.empty()) return 1;

    std::printf(&quot;related:   %.4f\n&quot;,
                kjarni_cosine_similarity(question.data(), related.data(), question.size()));
    std::printf(&quot;unrelated: %.4f\n&quot;,
                kjarni_cosine_similarity(question.data(), unrelated.data(), question.size()));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;g++ -std=c++17 hello17.cpp -I. -L. -lkjarni_ffi -Wl,-rpath,'$ORIGIN' -o hello17
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;related:   0.5510
unrelated: -0.0630
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same numbers as the C++23 version, because it is the same engine underneath. That compiles unchanged under &lt;code&gt;-std=c++11&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;Two rules cover the manual memory. Any &lt;code&gt;KjarniFloatArray&lt;/code&gt; you receive is freed with &lt;code&gt;kjarni_float_array_free&lt;/code&gt; once you have copied what you need out of it, and any handle is freed with its matching &lt;code&gt;_free&lt;/code&gt; function. Wrapping the handle in a &lt;code&gt;unique_ptr&lt;/code&gt; with a&lt;br&gt;
custom deleter, as above, means the second rule takes care of itself on every return path.&lt;/p&gt;
&lt;p&gt;Error text comes from &lt;code&gt;kjarni_last_error_message()&lt;/code&gt;, and it reports the most recent failure process wide, so read it immediately after the call that failed rather than saving it up.&lt;/p&gt;
&lt;h3 id=&quot;compared-to-the-alternatives&quot;&gt;Compared to the alternatives&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;libtorch &lt;/th&gt;&lt;th&gt;ONNX Runtime &lt;/th&gt;&lt;th&gt;Kjarni&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Install &lt;/td&gt;&lt;td&gt;download SDK, match ABI &lt;/td&gt;&lt;td&gt;package plus model conversion &lt;/td&gt;&lt;td&gt;one archive&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Model format &lt;/td&gt;&lt;td&gt;TorchScript &lt;/td&gt;&lt;td&gt;.onnx, converted &lt;/td&gt;&lt;td&gt;HuggingFace safetensors and GGUF directly&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Extra entries in &lt;code&gt;ldd&lt;/code&gt; &lt;/td&gt;&lt;td&gt;many &lt;/td&gt;&lt;td&gt;its own stack &lt;/td&gt;&lt;td&gt;none beyond libc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tokenizer &lt;/td&gt;&lt;td&gt;bring your own &lt;/td&gt;&lt;td&gt;bring your own &lt;/td&gt;&lt;td&gt;included&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPU &lt;/td&gt;&lt;td&gt;CUDA toolkit &lt;/td&gt;&lt;td&gt;CUDA or DirectML &lt;/td&gt;&lt;td&gt;WebGPU, no toolkit&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The trade is scope. libtorch runs anything expressible in TorchScript. Kjarni runs the model families it implements: BERT-style encoders, cross-encoders, Llama-family decoders, T5, BART and Whisper. For an arbitrary research model, convert it and use ONNX Runtime. For embeddings, classification, reranking or chat inside a C++ program that has to ship somewhere, one archive with no toolchain is a smaller problem than either.&lt;/p&gt;
&lt;h3 id=&quot;getting-it&quot;&gt;Getting it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai&quot;&gt;kjarni.ai&lt;/a&gt; - documentation and the rest of these posts&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni/releases&quot;&gt;Releases&lt;/a&gt; - the archive used above, for Linux, macOS and Windows&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni&quot;&gt;GitHub&lt;/a&gt; - source, including &lt;code&gt;kjarni.hpp&lt;/code&gt; and the C++ examples&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nuget.org/packages/Kjarni&quot;&gt;NuGet&lt;/a&gt; - the same engine from C#&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/kjarni-wasm&quot;&gt;npm&lt;/a&gt; - the same engine in the browser, via WebAssembly&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pkg.go.dev/github.com/olafurjohannsson/kjarni-go&quot;&gt;Go module&lt;/a&gt; - the same engine from Go&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/kjarni&quot;&gt;crates.io&lt;/a&gt; - the Rust engine itself&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;other-resources&quot;&gt;Other Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/semanticsearch/&quot;&gt;Semantic Search in C#&lt;/a&gt; - The same engine and the same vectors, from .NET&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/documentsearchengine/&quot;&gt;Build a Document Search Engine in C#&lt;/a&gt; - Keyword and semantic retrieval combined, with reranking&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/nativeinference/&quot;&gt;Why I Built a Native ML Inference Engine in Rust&lt;/a&gt; - What is underneath all of this&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/cli/&quot;&gt;ML from the Command Line&lt;/a&gt; - The same models as a UNIX tool&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/embedding-truncation/&quot;&gt;Your MiniLM Embeddings Are Probably Truncating at 256 Tokens&lt;/a&gt; - Measured, with the cost&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29317/semantic-search-in-c-without-python-libtorch-or-onnx-runtime</guid>
<pubDate>Wed, 30 Sep 2026 21:39:52 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 24 — Getting template type from an existing object</title>
<link>https://coderlegion.com/26343/tuesday-coding-tip-24-getting-template-type-from-an-existing-object</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Nice APIs expose their template types as member aliases so that anybody can get them easily. For other types, you might be able to deduce the type by calling &lt;code&gt;decltype&lt;/code&gt; on a return value of some of their functions.&lt;/p&gt;
&lt;p&gt;Sometimes, none of that is possible. But there is still a way to get to it. You need to write a template structure that only matches on the type you care about and alias the template parameters so you can access them later, a technique known as ‘type trait’.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;vector&amp;gt;

// Base template, only for the compiler
template&amp;lt;class T&amp;gt;
struct UnderlyingVectorType {};

// Overload for vector, note that templates don't have to match
template&amp;lt;class T, class A&amp;gt;
struct UnderlyingVectorType&amp;lt;std::vector&amp;lt;T, A&amp;gt;&amp;gt; {
    using StorageType = T;
    using AllocatorType = A;
};

// Nice types already expose their template types
template&amp;lt;class T&amp;gt;
class NiceType {
public:
    using StorageType = T;
};

int main() {
    int a;
    
    // error: no type named 'type' in 'UnderlyingVectorType&amp;lt;int&amp;gt;'
    // because it resolves to the base template
    // UnderlyingVectorType&amp;lt;decltype(a)&amp;gt;::storageType b;

    std::vector&amp;lt;float&amp;gt; vec;
    using VecStorageType = UnderlyingVectorType&amp;lt;decltype(vec)&amp;gt;::StorageType;
    VecStorageType vec2;
    static_assert(std::is_same_v&amp;lt;VecStorageType, float&amp;gt;);

    NiceType&amp;lt;double&amp;gt; nice;
    decltype(nice)::StorageType myDouble = 0.0;
    static_assert(std::is_same_v&amp;lt;decltype(nice)::StorageType, double&amp;gt;);
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26343/tuesday-coding-tip-24-getting-template-type-from-an-existing-object</guid>
<pubDate>Tue, 29 Sep 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 23 — CMake configure_file</title>
<link>https://coderlegion.com/26342/tuesday-coding-tip-23-cmake-configurefile</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In unit tests, I often don’t want to bother with the working folder of the test binary. But if those tests need physical files from the disc, I need to know their absolute paths (which are different on each machine). Luckily, CMake has a function &lt;code&gt;configure_file&lt;/code&gt; that can write CMake variables to a file template.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmake&quot;&gt;cmake_minimum_required ( VERSION 3.10 )

set ( USE_MSVC_FEATURES (&quot;${CMAKE_GENERATOR}&quot; MATCHES &quot;Visual Studio 17 2022&quot;) )

project ( DEMO CXX )

# CMakeLists.txt
configure_file(
    &quot;${CMAKE_SOURCE_DIR}/TemplateHeader.hpp.in&quot; # input
    &quot;${CMAKE_SOURCE_DIR}/FinalHeader.hpp&quot; # output
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, I can prepare a template for a header that will hold an absolute path to a folder with helper files, and during configuration, I can produce a valid C++ header with the path filled out. CMake will replace variables escaped as &lt;code&gt;@VAR@&lt;/code&gt;, &lt;code&gt;${VAR}&lt;/code&gt;, &lt;code&gt;$CACHE{VAR}&lt;/code&gt; or &lt;code&gt;$ENV{VAR}&lt;/code&gt; and furthermore it can do things like generating &lt;code&gt;#define&lt;/code&gt; directives.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// TemplateHeader.hpp.in
#pragma once

#include &amp;lt;filesystem&amp;gt;

#cmakedefine01 USE_MSVC_FEATURES

const std::filesystem::path TESTDATA_FOLDER = &quot;@CMAKE_CURRENT_SOURCE_DIR@/TestFiles&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Generated FinalHeader.hpp
#pragma once

#include &amp;lt;filesystem&amp;gt;

#define USE_MSVC_FEATURES 1

const std::filesystem::path TESTDATA_FOLDER = &quot;C:/Work/Project/TestFiles&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://cmake.org/cmake/help/latest/command/configure_file.html&quot;&gt;https://cmake.org/cmake/help/latest/command/configure_file.html&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26342/tuesday-coding-tip-23-cmake-configurefile</guid>
<pubDate>Tue, 22 Sep 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Embedding a web UI into a native desktop application comes with a price</title>
<link>https://coderlegion.com/27838/embedding-a-web-ui-into-a-native-desktop-application-comes-with-a-price</link>
<description>&lt;p&gt;I thought embedding a web UI into a native desktop application would be the easy part.&lt;br&gt;
After all... macOS has WebKit. Linux has GTK WebKit. Windows has WebView2.&lt;/p&gt;
&lt;p&gt;One API per platform, smaller installers, native look &amp;amp; feel. Sounds perfect.&lt;br&gt;
Then reality arrived.&lt;/p&gt;
&lt;h3 id=&quot;macos-&quot;&gt;macOS 🍎&lt;/h3&gt;
&lt;p&gt;Honestly, this was the easiest platform.&lt;br&gt;
System WebKit is there.&lt;br&gt;
It behaves consistently.&lt;br&gt;
No additional runtime.&lt;br&gt;
No installer surprises.&lt;br&gt;
Exactly what you'd expect from a platform component.&lt;br&gt;
10/10&lt;/p&gt;
&lt;h3 id=&quot;linux-&quot;&gt;Linux 🐧&lt;/h3&gt;
&lt;p&gt;Things became... more interesting.&lt;br&gt;
GTK WebKit works, but suddenly packaging starts to matter.&lt;br&gt;
An AppImage built on one distribution may refuse to start on another because some required WebKitGTK library isn't available.&lt;br&gt;
Your application itself is perfectly fine.&lt;br&gt;
The user's system just doesn't happen to provide exactly the version your build expects.&lt;br&gt;
You quickly discover that &quot;works on my machine&quot; has many regional dialects.&lt;br&gt;
7/10&lt;/p&gt;
&lt;h3 id=&quot;windows-&quot;&gt;Windows 🪟&lt;/h3&gt;
&lt;p&gt;This one surprised me the most.&lt;br&gt;
Unlike macOS, the web view isn't really just &quot;there.&quot;&lt;br&gt;
Using WebView2 means depending on the Edge WebView runtime.&lt;br&gt;
If the runtime isn't installed, congratulations—you now need another installer.&lt;br&gt;
So your installer may install something whose purpose is to allow your application to display HTML.&lt;br&gt;
Not exactly the dependency story I was hoping for.&lt;br&gt;
2/10&lt;/p&gt;
&lt;h3 id=&quot;meeting-in-the-middle&quot;&gt;Meeting in the middle&lt;/h3&gt;
&lt;p&gt;At some point I asked myself:&lt;br&gt;
Why am I spending time debugging operating-system packaging instead of building my application?&lt;/p&gt;
&lt;p&gt;So I tried CEF (Chromium Embedded Framework).&lt;br&gt;
Yes...&lt;br&gt;
The application becomes larger.&lt;br&gt;
Quite a bit larger.&lt;/p&gt;
&lt;p&gt;But in exchange:&lt;br&gt;
• Same rendering engine everywhere.&lt;br&gt;
• Same JavaScript engine everywhere.&lt;br&gt;
• Same debugging experience.&lt;br&gt;
• Same HTML/CSS behavior.&lt;br&gt;
• No Linux WebKit dependency lottery.&lt;br&gt;
• No separate WebView runtime installation on Windows.&lt;br&gt;
• One code path across all desktop platforms.&lt;br&gt;
Ironically, shipping your own browser turned out to be simpler than relying on the browser already &quot;provided&quot; by the operating system.&lt;br&gt;
It's one of those engineering decisions that looks wasteful on paper but ends up reducing complexity everywhere else.&lt;br&gt;
Sometimes carrying the extra megabytes is cheaper than carrying platform-specific surprises.&lt;/p&gt;
&lt;p&gt;Curious what others have settled on.&lt;/p&gt;
&lt;p&gt;Are you using native web views, CEF, or something else for cross-platform desktop applications?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27838/embedding-a-web-ui-into-a-native-desktop-application-comes-with-a-price</guid>
<pubDate>Sun, 20 Sep 2026 14:12:47 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 22 — Compile-time computing</title>
<link>https://coderlegion.com/26341/tuesday-coding-tip-22-compile-time-computing</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As you might know, templates in C++ are turing-complete and you can use them for compile-time computing. But they are unwieldy. So the &lt;code&gt;constexpr&lt;/code&gt;/&lt;code&gt;consteval&lt;/code&gt; keywords were created, so you can write a normal code and mark it as viable for compile-time execution. &lt;code&gt;consteval&lt;/code&gt; means that the function has to be executed during compilation (and then optimized away) while &lt;code&gt;constexpr&lt;/code&gt; gives the compiler the opportunity to do so.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;array&amp;gt;

template&amp;lt;unsigned limit&amp;gt;
consteval auto ComputeFibonacciLookup()
{
    std::array&amp;lt;int, limit&amp;gt; result = { 0 };

    unsigned n_1 = 0;
    unsigned n_2 = 1;
    for (unsigned i = 0; i &amp;lt; limit; i++)
    {
        result[i] = (n_1 + n_2);
        n_2 = n_1;
        n_1 = result.at(i);
    }

    return result;
}

constexpr const auto FIB_LOOKUP_TABLE = ComputeFibonacciLookup&amp;lt;10&amp;gt;();
static_assert(FIB_LOOKUP_TABLE.size() == 10u);
static_assert(FIB_LOOKUP_TABLE[0] == 1);
static_assert(FIB_LOOKUP_TABLE[1] == 1);
static_assert(FIB_LOOKUP_TABLE[2] == 2);
static_assert(FIB_LOOKUP_TABLE[3] == 3);
static_assert(FIB_LOOKUP_TABLE[4] == 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why is this useful? For example, you can generate a lookup table to optimize runtime speed while maintaining the table itself via an algorithm, not hard-coded values. Or you can write compile-time unit tests for your code. These are handy the compilation will error out if you have any undefined behavior in your code (that does not happen for regular compilation).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;constexpr bool divide(int a, int b)
{
    return (a / b) != 0;
}

// Static assert guaratees compile-time execution
static_assert(divide(10, 5)); // ok

// compilation error, division by zero is UB
// error: static assertion expression is not an integral constant expression
static_assert(divide(10, 0));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Extra thought:&lt;/strong&gt; This tip was originally written three years ago. Since then, many more &lt;code&gt;constexpr&lt;/code&gt; variants of standard containers and language constructs were added to the language. Meaning you can do more advanced stuff in the compile time. With C++26, you can even allocate memory and throw exceptions. The compiler will also check that you properly deallocate memory, which is very clever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit 15.9.2026&lt;/strong&gt;: Fixed the first code example as per Mike Dabydeen comment. Kudos for the correction!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26341/tuesday-coding-tip-22-compile-time-computing</guid>
<pubDate>Tue, 15 Sep 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>ForgeZero: What Happens When Someone Finally Stops Forgiving Your Build System</title>
<link>https://coderlegion.com/27194/forgezero-what-happens-when-someone-finally-stops-forgiving-your-build-system</link>
<description>&lt;p&gt;In 1976, a Bell Labs researcher named Stuart Feldman was annoyed at a coworker. The story, as it's been retold for half a century now, goes something like this: a colleague kept shipping broken binaries because he'd edit a source file and forget to recompile the parts of the program that depended on it. Feldman's fix wasn't a lecture. It was &lt;code&gt;make&lt;/code&gt; — a tool whose entire reason for existing was to answer one question correctly, automatically, every single time: &lt;em&gt;what, exactly, needs to be rebuilt right now, and what doesn't?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That question turned out to be one of the hardest, most quietly load-bearing problems in all of software engineering. Fifty years later, we're still answering it badly, most of the time, and most engineers have simply stopped noticing.&lt;/p&gt;
&lt;p&gt;Make aged the way anything does when it becomes foundational infrastructure nobody's allowed to break: badly, slowly, and in public. Its dependency model — timestamps, phony targets, recursive invocation — leaked complexity into every project that grew past a few dozen files, and autotools showed up to paper over that with an entire generation-of-generators architecture that, to this day, remains one of the most feared corners of the open-source toolchain. CMake arrived at the turn of the millennium out of Kitware's need to build ITK and VTK across radically different platforms without hand-maintaining a Makefile per OS — a meta-build system that generates &lt;em&gt;other&lt;/em&gt; build systems, which is either a brilliant abstraction or a confession that the underlying problem was never actually solved, depending on which decade of CMake you personally suffered through.&lt;/p&gt;
&lt;p&gt;And then there's Ninja. Evan Martin built it at Google specifically because Chromium's build had grown so large that Make's dependency-graph evaluation had become a measurable, painful part of every engineer's day — and his answer was almost insultingly minimal: a build file format so sparse that the project's own documentation tells you, directly, that you are not meant to write Ninja files by hand. Ninja doesn't try to be pleasant. It tries to be a fast, dumb, extremely well-behaved execution target for something smarter sitting above it, generating its files for it. That single design decision — &lt;em&gt;radical minimalism as a feature, not a limitation&lt;/em&gt; — is one of the most underrated ideas in build tooling history, and it's the closest ancestor to what comes next in this story.&lt;/p&gt;
&lt;p&gt;Because none of Make, Autotools, CMake, or Ninja were ever built for the workload I actually live in: assembly-heavy code, small C translation units by the hundreds, embedded and bare-metal targets, mixed-language repositories where the &lt;em&gt;coordination&lt;/em&gt; between compiler invocations costs more than the invocations themselves. I started building ForgeZero to solve that gap about a year and a half ago, quietly, on my own time, the way most infrastructure worth trusting actually gets built. It went public in May 2026. This is the deep engineering account of what it actually is.&lt;/p&gt;
&lt;h3 id=&quot;the-thesis-stated-plainly&quot;&gt;The Thesis, Stated Plainly&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;A build system should handle planning, invalidation, and routing. It should not try to reinvent the compiler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That single sentence is the entire architectural spine of ForgeZero, and everything below is just the consequence of taking it seriously. ForgeZero is not GCC, Clang, Zig, NASM, FASM, GAS, or LD, and it has no interest in becoming any of them. It's the thin, transparent coordination layer that decides &lt;em&gt;what&lt;/em&gt; those tools need to do, &lt;em&gt;when&lt;/em&gt;, and &lt;em&gt;whether they need to do it again at all&lt;/em&gt; — and then gets entirely out of the way.&lt;/p&gt;
&lt;p&gt;The value proposition compresses into one relationship:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Build Throughput ≈ Useful Compiler Work
                    ─────────────────────────────────────────
                    Discovery + Dispatch + Repeated Work + Invalidation Cost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every serious build-system optimization in ForgeZero attacks the denominator. None of it touches the numerator, because the numerator — actual code generation correctness — belongs to GCC, Clang, Zig, and the assemblers, permanently, and ForgeZero has no ambition to argue with that division of labor.&lt;/p&gt;
&lt;h3 id=&quot;the-pipeline-as-a-chain-of-owned-transformations&quot;&gt;The Pipeline as a Chain of Owned Transformations&lt;/h3&gt;
&lt;p&gt;Filesystem state becomes a source set. The source set becomes a dependency closure. That closure becomes a preprocessing state, then a cache identity, then a set of compiler and assembler actions, then an object graph, then a linker invocation, then a verified artifact.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;filesystem state
    -&amp;gt; source set
    -&amp;gt; dependency closure
    -&amp;gt; preprocessing state
    -&amp;gt; cache identity
    -&amp;gt; compiler/assembler actions
    -&amp;gt; object graph
    -&amp;gt; linker invocation
    -&amp;gt; verified artifact
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What makes this more than a diagram is that &lt;strong&gt;each arrow is an owned boundary with its own failure class.&lt;/strong&gt; A discovery failure is not a cache failure. A cache failure is not a linker failure. When something goes wrong, an engineer using ForgeZero is diagnosing one specific, narrow transformation — not reverse-engineering an opaque shell invocation that happened to fail somewhere in the middle of a black box.&lt;/p&gt;
&lt;p&gt;Four properties fall directly out of respecting that boundary discipline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observability&lt;/strong&gt; — the selected backend, target, flags, object paths, cache decisions, and exact linker command are all visible, not inferred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controllability&lt;/strong&gt; — ABI, compiler, linker, optimization level, sysroot, and output format are never hidden behind a &quot;just trust the abstraction&quot; layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Amortization&lt;/strong&gt; — unchanged units don't get recompiled, and metadata checks run &lt;em&gt;before&lt;/em&gt; the expensive stages, not after.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; — assembly, C-family code, Zig, Gloria, custom preprocessing, and security tooling all live inside one coordinated pipeline instead of five disconnected ones duct-taped together in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;who-this-was-actually-built-for&quot;&gt;Who This Was Actually Built For&lt;/h3&gt;
&lt;p&gt;This isn't a general-purpose build tool wearing a performance costume. It has specific, named audiences, and it's honest about who they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assembly engineers&lt;/strong&gt; — boot code, runtime glue, kernel-adjacent components, flat binaries, hand-managed ELF/PE/Mach-O pipelines, where NASM/FASM/GAS selection and the resulting object format can't be papered over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C/C++/Objective-C engineers&lt;/strong&gt; who need direct, unfiltered access to GCC, Clang, or Zig — their own &lt;code&gt;-I&lt;/code&gt;, &lt;code&gt;-D&lt;/code&gt;, &lt;code&gt;-O&lt;/code&gt;, sanitizer, and linker flags surviving intact into the actual command line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded and cross-compilation engineers&lt;/strong&gt; targeting ARM, RISC-V, and bare-metal ABIs, where the target triple has to participate directly in backend selection, not get normalized away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release and supply-chain engineers&lt;/strong&gt; who need reproducible builds, source-tree verification, SBOM generation, SAST-style auditing, and filesystem isolation — as genuinely separate concerns from the build cache, never conflated with it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build infrastructure authors&lt;/strong&gt; looking to replace a Make/Ninja/shell-script Frankenstein with one coordinator that has a predictable cache model and room for hooks, watchers, and compile databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cache-identity-the-part-everyone-gets-wrong&quot;&gt;Cache Identity: The Part Everyone Gets Wrong&lt;/h3&gt;
&lt;p&gt;Here's the mistake that shows up in almost every homegrown build cache: treating &quot;the source bytes didn't change&quot; as sufficient justification for reusing a compiled object. It isn't, and pretending it is produces silent, maddening correctness bugs. The same source file produces a &lt;em&gt;different&lt;/em&gt; correct object under a different compiler, a different compiler version, a different target triple, a different sysroot, different optimization flags, different sanitizer policy, or different linker configuration.&lt;/p&gt;
&lt;p&gt;ForgeZero's action-level cache identity is built from the sorted inputs, their digests, the action name, and the full environment and toolchain parameters — not the source hash alone. Two layers do the heavy lifting:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metadata-first invalidation&lt;/strong&gt; checks file size and modification time before touching the file's bytes at all. If both match a cache entry, the expensive digest step is skipped entirely. Only a mismatch triggers a full keyed digest computation. This is explicitly a performance optimization, not a cryptographic guarantee — and ForgeZero doesn't pretend otherwise. Security-sensitive verification lives in its own, separately-specified &lt;code&gt;verify&lt;/code&gt;/SBOM contract, deliberately kept apart from the invalidation fast path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The disk hash cache format, &lt;code&gt;FZHC3&lt;/code&gt;,&lt;/strong&gt; stores path length, metadata, a context fingerprint, and a 32-byte digest, written via a temp-file-then-rename sequence specifically to avoid leaving a half-written cache entry behind on a crash. Small detail. The kind of detail that separates a cache you can trust from one you quietly stop trusting after the third mysterious corruption.&lt;/p&gt;
&lt;h3 id=&quot;two-hashes-one-honest-boundary&quot;&gt;Two Hashes, One Honest Boundary&lt;/h3&gt;
&lt;p&gt;This is where ForgeZero's engineering has genuinely moved since it first went public, and it's worth explaining precisely, because the project draws a hard, deliberate line between two hashing systems that solve &lt;em&gt;related but different&lt;/em&gt; problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BoomBoom Hash&lt;/strong&gt; is the original incremental-invalidation engine, built as a specialized layer over keyed BLAKE3. Build context — compiler identity, version, target triple, ordered flags — gets domain-separated and hashed into a derived key first, so the same source bytes produce different digests under different build contexts without any cross-contamination between cache domains. Source files are broken into logical chunks — directive regions, brace-delimited blocks, top-level statements, residual spans — each independently digested and placed as a leaf in a flat, heap-layout Merkle tree. Change one chunk, and only the path from that leaf to the root gets recomputed: &lt;code&gt;O(log n)&lt;/code&gt; instead of a full &lt;code&gt;O(n)&lt;/code&gt; re-aggregation. Past four changed chunks, a persistent worker pool picks up jobs through atomic fetch-and-add over a flat array, each worker holding its own independent keyed BLAKE3 state, with zero shared mutable state at the hasher level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BB64&lt;/strong&gt; is newer, and it exists precisely &lt;em&gt;because&lt;/em&gt; BoomBoom Hash, built on a general-purpose cryptographic primitive, is doing more work than some invalidation paths actually need. BB64 is a purpose-built, non-cryptographic 64-bit hash with a hand-written AVX2 Plan 9 assembly hot path, a scalar fallback verified for bit-exact equivalence against the SIMD path, explicit tail handling for inputs of arbitrary length, and SIMD-accelerated byte comparison and delimiter scanning. On an AMD Ryzen 7 PRO 4750U, it measured roughly &lt;strong&gt;16.87 GB/s at 0 B/op and 0 allocs/op&lt;/strong&gt; — against roughly 0.42 GB/s for FNV-1a and roughly 1.71 GB/s for BLAKE3 on the same benchmark input. That's about &lt;strong&gt;40x FNV-1a and nearly 10x BLAKE3&lt;/strong&gt;, for a task — PCH cache-key generation, keyed off header content, compiler, target, flags, and positions — that never needed cryptographic guarantees in the first place.&lt;/p&gt;
&lt;p&gt;And here's the part that should matter most to anyone evaluating this as infrastructure, not just admiring the throughput number: &lt;strong&gt;BB64 is explicitly, deliberately never used for security-sensitive file integrity, package verification, or any cryptographic surface.&lt;/strong&gt; That boundary isn't an oversight waiting to be &quot;optimized away&quot; later. It's the whole point. A fast non-cryptographic hash and a keyed cryptographic hash are different tools solving different problems, and collapsing that distinction to save one dependency is exactly the kind of shortcut that turns into a CVE two years later. ForgeZero doesn't take it.&lt;/p&gt;
&lt;h3 id=&quot;below-the-go-standard-library-the-discovery-layer&quot;&gt;Below the Go Standard Library: The Discovery Layer&lt;/h3&gt;
&lt;p&gt;This is the part that will make a systems engineer sit up, because it's the difference between &quot;a build tool written in Go&quot; and &quot;a build tool written by someone who's actually spent time fighting &lt;code&gt;syscall&lt;/code&gt; on Linux.&lt;/p&gt;
&lt;p&gt;Source discovery on Linux bypasses the convenient, portable &lt;code&gt;filepath.WalkDir&lt;/code&gt; entirely in favor of &lt;strong&gt;raw &lt;code&gt;getdents64&lt;/code&gt; traversal via &lt;code&gt;unix.Getdents&lt;/code&gt;&lt;/strong&gt;, paired with &lt;strong&gt;&lt;code&gt;openat&lt;/code&gt; using &lt;code&gt;O_NOFOLLOW&lt;/code&gt;&lt;/strong&gt; to keep directory traversal from silently following a symlink somewhere it shouldn't. Metadata collection goes through &lt;strong&gt;Linux &lt;code&gt;statx&lt;/code&gt;&lt;/strong&gt; for type, inode, size, and modification time in a single call, falling back to &lt;code&gt;Fstatat&lt;/code&gt; on kernels where &lt;code&gt;statx&lt;/code&gt; isn't reliably available. Directory reads retry on &lt;code&gt;EINTR&lt;/code&gt; instead of surfacing a spurious failure, malformed dirents get validated rather than trusted, and path ordering is made deterministic so two runs on the same tree produce the same discovery order — a small property that matters enormously the first time you're trying to reproduce a flaky build. Non-Linux platforms fall back cleanly to &lt;code&gt;filepath.WalkDir&lt;/code&gt;, because the portable API contract stays identical even when the backend implementation doesn't.&lt;/p&gt;
&lt;p&gt;The payoff isn't hypothetical: manual dirent parsing measurably cut Linux discovery allocations on a 1,024-file benchmark from roughly &lt;strong&gt;3,182 down to 2,088 allocations per operation&lt;/strong&gt; — about a third fewer allocations, on a code path that runs at the very start of every single build.&lt;/p&gt;
&lt;h3 id=&quot;io-uring-used-honestly&quot;&gt;io_uring, Used Honestly&lt;/h3&gt;
&lt;p&gt;ForgeZero's io_uring integration batches file reads into a single submission cycle instead of one syscall per file, drains completion queue entries fully with explicit user-data bounds checking, handles zero-length files without generating an invalid empty READ request, and supports &lt;code&gt;IORING_OP_STATX&lt;/code&gt; for metadata collection through the same ring. It enables &lt;code&gt;IORING_SETUP_COOP_TASKRUN&lt;/code&gt; where the kernel supports it, with a clean setup fallback for older kernels that don't.&lt;/p&gt;
&lt;p&gt;And then there's the detail that actually tells you how this team thinks: &lt;strong&gt;&lt;code&gt;IORING_OP_GETDENTS&lt;/code&gt; was never wired up, because the target kernel ABI doesn't expose a usable io_uring GETDENTS opcode&lt;/strong&gt; — so rather than fabricate support for an operation that doesn't reliably exist, directory listing stays on the syscall path where it actually works. That's a small, unglamorous decision. It's also exactly the kind of decision that separates infrastructure you can trust from infrastructure that looks impressive in a README until someone runs it on the wrong kernel version.&lt;/p&gt;
&lt;h3 id=&quot;linking-scheduling-and-the-false-sharing-detail-most-projects-never-bother-with&quot;&gt;Linking, Scheduling, and the False-Sharing Detail Most Projects Never Bother With&lt;/h3&gt;
&lt;p&gt;Symbol cache decoding is strict — malformed records are rejected outright rather than partially trusted, triggering a fresh extraction through &lt;code&gt;nm&lt;/code&gt;, &lt;code&gt;objdump&lt;/code&gt;, or &lt;code&gt;readelf&lt;/code&gt; instead of silently propagating a corrupted cache entry into a link step. Symbol results move through &lt;strong&gt;indexed, preallocated result slots&lt;/strong&gt; instead of a per-object queue payload, cutting allocation churn on the hot link path. The scheduler supports &lt;strong&gt;idempotent shutdown&lt;/strong&gt; and explicit stopped-state handling, so a second shutdown call doesn't do anything surprising.&lt;/p&gt;
&lt;p&gt;And then there's this: &lt;strong&gt;worker and scheduler queue producer/consumer counters are deliberately separated across 64-byte cache lines&lt;/strong&gt;, specifically to avoid false sharing — the scenario where two unrelated atomic counters happen to land on the same CPU cache line and start silently fighting each other for cache coherency traffic under concurrent load, tanking throughput for no reason visible anywhere in the code. This is genuinely advanced systems-engineering territory, the kind of optimization that only shows up in codebases that have actually been profiled under real concurrent load rather than just &quot;made to compile and pass CI.&quot;&lt;/p&gt;
&lt;h3 id=&quot;gloria-the-language-that-refuses-to-hide-anything&quot;&gt;Gloria: The Language That Refuses to Hide Anything&lt;/h3&gt;
&lt;p&gt;Sitting inside ForgeZero is &lt;strong&gt;Gloria&lt;/strong&gt;, an experimental language that isn't trying to compete with C, Rust, or a full assembler frontend. It occupies a specific, narrow niche: a minimal language for producing a small, fully-understood fragment of machine code in contexts where a libc, a runtime, and a general-purpose compiler pipeline would be overkill.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source -&amp;gt; lexer -&amp;gt; tokens -&amp;gt; codegen -&amp;gt; machine bytes -&amp;gt; relocation -&amp;gt; raw binary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's no mandatory intermediate object file, no libc startup sequence, no standard linker script. The emitter writes bytes directly, and ForgeZero writes them out as a raw binary. Functions land in a contiguous output buffer; calls are written first with relocation placeholders, and once the function table is complete, relative displacements get patched directly into the buffer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;disp32 = offset(target) - (offset(call) + 5)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the entire mechanism that lets Gloria skip a full object-file linker for small raw images. Compile this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gloria&quot;&gt;fn main() {
    let a = 10;
    let b = 20;

    if b &amp;gt; a {
        print(&quot;hello world lol&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and disassembling the resulting &lt;code&gt;gloria.bin&lt;/code&gt; shows exactly what you'd hope for from a language that refuses to hide anything: an entry jump and stack-frame prologue, 64-bit immediate loads materializing the literals, a &lt;code&gt;cmp&lt;/code&gt;/&lt;code&gt;jle&lt;/code&gt; pair lowering the comparison, and — because the observed output loop writes directly to &lt;code&gt;0xb8000&lt;/code&gt;, the classic x86 VGA text-memory address — a byte-by-byte character-and-attribute write loop instead of a hidden &lt;code&gt;printf&lt;/code&gt; call. Every single opcode in that disassembly traces back to a specific, readable decision the emitter made. That transparency, not competing with production languages, is the entire point of Gloria existing at all.&lt;/p&gt;
&lt;h3 id=&quot;security-as-a-neighbor-not-a-costume&quot;&gt;Security as a Neighbor, Not a Costume&lt;/h3&gt;
&lt;p&gt;ForgeZero keeps its security surfaces &lt;strong&gt;deliberately separate&lt;/strong&gt; from its performance surfaces, because conflating &quot;this build was fast&quot; with &quot;this build can be trusted&quot; is exactly how supply-chain incidents happen. Path validation constrains CLI paths, symlink boundaries, and FZP include resolution to explicitly allowed roots. A dedicated seal/integrity subsystem tracks trust state for executables and toolchains — a fundamentally different question from &quot;do I need to recompile this file,&quot; and the codebase treats it as one. SBOM generation produces a CycloneDX-style component inventory with hashes; a separate audit subsystem runs SAST-style checks for secrets, vendor-specific risk patterns, and configuration violations; manifest-based source-tree verification is kept algorithmically stable specifically so release verification stays interoperable across versions instead of silently drifting.&lt;/p&gt;
&lt;p&gt;Cache hits are fast. Cache hits are not proof of integrity. ForgeZero's architecture never lets those two facts get confused with each other.&lt;/p&gt;
&lt;h3 id=&quot;the-redis-case-study-read-correctly&quot;&gt;The Redis Case Study, Read Correctly&lt;/h3&gt;
&lt;p&gt;The number worth reporting is this one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fz -p performance -dir redis/src -mode c -out redis-server -j 16

user       27.66 s
system     11.68 s
CPU        1155%
elapsed     3.404 s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it precisely, because precision is the entire point of this project. &lt;strong&gt;1155% CPU utilization&lt;/strong&gt; means the work was genuinely spread across multiple hardware threads, not disguised behind a fast single-thread path. &lt;strong&gt;3.404 seconds elapsed&lt;/strong&gt; against nearly 40 seconds of combined CPU time shows the bottleneck sitting in parallel compile-and-link work, not in sequential orchestration overhead — which is exactly where you want a build's time to be spent. The dependency closure for that run wasn't trivial: &lt;code&gt;fpconv&lt;/code&gt;, &lt;code&gt;hdr_histogram&lt;/code&gt;, &lt;code&gt;hiredis&lt;/code&gt;, &lt;code&gt;libxxhash&lt;/code&gt;, &lt;code&gt;linenoise&lt;/code&gt;, &lt;code&gt;lua&lt;/code&gt;, and &lt;code&gt;tre&lt;/code&gt; were each independently compiled into static archives, then handed to the linker alongside Redis's own translation units — proving the pipeline handles real, dependency-aware, multi-ABI native builds, not just a flat folder of standalone &lt;code&gt;.c&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;The honest framing matters more than the headline number: this is &lt;strong&gt;end-to-end orchestration latency on one specific host, toolchain, and cache state&lt;/strong&gt; — not a claim about compiler speed, and not a number that means anything without the CPU model, governor, storage, toolchain versions, cache warmth, and parallelism level that produced it. ForgeZero's own documentation says exactly that, unprompted, which is a more convincing performance claim than any raw benchmark number could be on its own.&lt;/p&gt;
&lt;h3 id=&quot;cross-platform-without-pretending-every-platform-is-the-same&quot;&gt;Cross-Platform Without Pretending Every Platform Is the Same&lt;/h3&gt;
&lt;p&gt;The Go core stays portable while low-level operations are split cleanly across build-tagged files: Linux gets native &lt;code&gt;getdents64&lt;/code&gt;/&lt;code&gt;statx&lt;/code&gt;/io_uring fast paths, other Unix-like targets get &lt;code&gt;mmap&lt;/code&gt;/descriptor-based paths, Windows gets its own executable, security, and filesystem branches entirely. Cross-platform, here, doesn't mean identical capability everywhere — it means an &lt;strong&gt;identical semantic contract&lt;/strong&gt; at the API boundary regardless of which backend is actually doing the work underneath it. Linux can have the fastest path available to it without Windows being forced into an emulation layer pretending to be a kernel it isn't.&lt;/p&gt;
&lt;h3 id=&quot;the-installer-nobody-notices-until-it-breaks&quot;&gt;The Installer Nobody Notices Until It Breaks&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;install.sh&lt;/code&gt; got rewritten as a strict POSIX &lt;code&gt;sh&lt;/code&gt; script with explicit platform and architecture validation, &lt;code&gt;--help&lt;/code&gt; and missing-option handling, retry-and-timeout logic on both &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;wget&lt;/code&gt;, and remote &lt;em&gt;and&lt;/em&gt; local checksum verification before anything gets executed. The insecure &lt;code&gt;mktemp -u&lt;/code&gt; pattern — which creates a predictable race window between naming a temp path and actually using it — was replaced with a properly private temporary directory. Writable installs land through a temp-file-then-atomic-rename sequence; privileged installs go through &lt;code&gt;sudo install&lt;/code&gt; rather than a raw copy; a full dry-run mode exists that downloads and modifies nothing; cleanup traps make sure temp files don't survive a failed run. It's the least glamorous file in the entire repository, and it's exactly the file that determines whether someone's first five minutes with the project go smoothly or end with a half-installed binary and a GitHub issue.&lt;/p&gt;
&lt;h3 id=&quot;what-forgezero-deliberately-doesn-t-do-and-why-that-s-worth-trusting&quot;&gt;What ForgeZero Deliberately Doesn't Do — And Why That's Worth Trusting&lt;/h3&gt;
&lt;p&gt;The most senior-engineer-coded section of this entire project's documentation isn't a feature list. It's a short list of things the team explicitly &lt;em&gt;chose not to build&lt;/em&gt;, and it's worth reading as a statement of engineering values as much as a technical note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BB64 stays out of every security-sensitive path&lt;/strong&gt; — integrity checks, package verification, and cryptographic APIs all keep using the keyed BLAKE3-based BoomBoom Hash, on purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IORING_OP_GETDENTS&lt;/code&gt; was never faked into existence&lt;/strong&gt; to check a feature-completeness box, because the kernel ABI in question genuinely doesn't expose a usable opcode for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;clone3&lt;/code&gt;/&lt;code&gt;execveat&lt;/code&gt; weren't forced into the process launcher&lt;/strong&gt; without a proper lifecycle design, because Go's context cancellation, descriptor ownership, and cross-platform launcher contracts all deserve a real design pass, not a shortcut.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A shared-memory persistent cache wasn't shipped&lt;/strong&gt; without a versioned format, crash recovery, and an ownership protocol behind it — because a cache that can silently corrupt itself is worse than no cache at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No thousand-fold speedup claims got made&lt;/strong&gt; without workload-specific measurements standing behind them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any team can list what they built. Far fewer are willing to publish, in plain language, what they refused to ship half-finished — and that restraint is a far stronger signal of engineering maturity than another benchmark chart would ever be.&lt;/p&gt;
&lt;h3 id=&quot;where-this-actually-leaves-you&quot;&gt;Where This Actually Leaves You&lt;/h3&gt;
&lt;p&gt;ForgeZero isn't trying to convince you that &lt;code&gt;gcc -O3&lt;/code&gt; needs replacing. It's making a much narrower, much more defensible claim: that the &lt;em&gt;coordination&lt;/em&gt; surrounding native compilation — discovery, invalidation, scheduling, dispatch, caching, linking — has its own real, measurable cost, and that cost has been quietly tolerated by an entire industry for decades because Make, Autotools, CMake, and Ninja were each solving the build problem of their own era, not this one. Assembly-heavy, embedded, mixed-language, cache-identity-sensitive native builds were never really the target audience for any of them.&lt;/p&gt;
&lt;p&gt;A year and a half of quiet, unglamorous systems work — cache-line-aware schedulers, kernel-ABI-honest io_uring integration, a hash function that knows exactly which problems it's allowed to touch and which ones it isn't — is what it actually takes to make that narrower claim true instead of just plausible. That's the whole pitch. Not magic. Not a thousand-fold speedup. Just a build system that finally stops forgiving itself for not knowing precisely why it did what it just did.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/27194/forgezero-what-happens-when-someone-finally-stops-forgiving-your-build-system</guid>
<pubDate>Mon, 14 Sep 2026 21:33:21 +0000</pubDate>
</item>
<item>
<title>From Arduino to ESP-IDF: The architecture behind my digital &quot;Swiss Army Knife&quot;</title>
<link>https://coderlegion.com/26982/from-arduino-to-esp-idf-the-architecture-behind-my-digital-swiss-army-knife</link>
<description>&lt;h3 id=&quot;1-why-build-another-multi-tool-&quot;&gt;1. Why build another multi-tool?&lt;/h3&gt;
&lt;p&gt;How many of you have often found yourselves wanting to buy a Flipper Zero? I thought about it many times, but there were always problems holding me back: stock is often limited, the price tag is quite high, and above all, you miss out on the thrill of building such a powerful tool literally from scratch.&lt;/p&gt;
&lt;p&gt;From these observations, my project was born: designing and developing a low-level &quot;Swiss Army Knife&quot;.&lt;/p&gt;
&lt;p&gt;It all started a few months ago. I was thinking about buying an M5Stick S3 after watching some videos online where people spoke very highly of it, especially for one major detail: unlike the Flipper, it has Wi-Fi and Bluetooth modules already built-in.&lt;/p&gt;
&lt;p&gt;Digging deeper, I quickly realized the advantages of the ESP32-S3 over the classic Arduino. The key features that convinced me were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dual-core processor: It opens the door to serious features, like managing firmware tasks separately.&lt;/li&gt;
&lt;li&gt;More RAM: It allows integrating very complex external libraries (like heavy graphical interfaces) without killing performance.&lt;/li&gt;
&lt;li&gt;Native USB HID: It allows emulating peripherals like keyboards or mice natively and quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, the hardware was decided. But why build a multi-tool?&lt;/p&gt;
&lt;p&gt;The main reason is to explore and understand the technical background of as many tools as possible. Lately, I feel there is a tendency to overlook the ingenuity of the mechanisms operating right in front of our eyes. We prefer having a ready-made tool, usable perhaps without even knowing the basics of computer science. I wanted to go in the opposite direction and understand exactly how these things work at the code level.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-fluid-graphics-and-multitasking-how-not-to-blow-up-an-esp32&quot;&gt;2. Fluid Graphics and Multitasking: How not to blow up an ESP32&lt;/h3&gt;
&lt;p&gt;A major problem when rendering a graphical interface on a microcontroller is that the CPU has to calculate and send every single pixel. Since this is a time-consuming operation, the entire device gets blocked until the whole interface is completely redrawn. In a multi-tool, if the ESP32 is stuck drawing buttons, it cannot sniff packets or execute attacks.&lt;/p&gt;
&lt;p&gt;To solve this problem and make the device truly responsive, I decided to use DMA (Direct Memory Access). DMA is a dedicated hardware component that allows peripherals (in this case the SPI display) to read data directly from RAM without involving the CPU, generating a single interrupt only when the transfer is complete.&lt;/p&gt;
&lt;p&gt;I initialized the SPI bus asking ESP-IDF to automatically assign a DMA channel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;spi_bus_initialize(SPI2_HOST, &amp;amp;buscfg, SPI_DMA_CH_AUTO);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-ram-problem-and-chunk-rendering&quot;&gt;The RAM problem and Chunk Rendering&lt;/h4&gt;
&lt;p&gt;However, implementing DMA without the right precautions wouldn't have been enough. The device's screen resolution is 135x240 at 16 bits, which requires a total of 64.8 KB to be rendered. This leads to two major problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wasting so many resources just for the interface is not sustainable for a microcontroller.&lt;/li&gt;
&lt;li&gt;It is virtually impossible to find 64.8 KB of contiguous memory inside the RAM once the system is running.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To bypass this issue, I decided to divide the screen into horizontal &quot;slices&quot; of 20 lines each (about 5.4 KB total). Using the advanced ESP-IDF APIs, I allocated this buffer in a memory area compatible with DMA:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;lv_color_t *buf1 = heap_caps_malloc(LCD_H_RES * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we optimize RAM usage while still maintaining super fluid rendering.&lt;/p&gt;
&lt;p&gt;At this point, a question arises: how does LVGL (the library I use to manage the interface) know when the DMA has finished sending the bits for those 20 lines? Simply, with a Hardware Interrupt system that defines this flow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LVGL fills the buffer and calls the driver.&lt;/li&gt;
&lt;li&gt;The driver passes the buffer to the DMA controller.&lt;/li&gt;
&lt;li&gt;When the transmission process ends, the interrupt is triggered.&lt;/li&gt;
&lt;li&gt;The interrupt immediately calls a callback (&lt;code&gt;notify_lvgl_flush_ready&lt;/code&gt;) which orders LVGL to calculate the next slice of the interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) {
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;semaphores-for-safety-thread-safety-&quot;&gt;Semaphores for Safety (Thread-Safety)&lt;/h4&gt;
&lt;p&gt;Since FreeRTOS is a system based on multi-threading, it is important to implement proper task management. A fundamental detail to know is that LVGL is not thread-safe by design. This means that the library has no internal mechanisms to protect itself: if two different tasks try to access or modify the graphical interface at the exact same moment, the system will crash.&lt;/p&gt;
&lt;p&gt;To prevent these race-conditions, which can lead to data inconsistency and deadlocks, I implemented a Semaphore (Mutex) system: every time an external task (other than the graphical one) wants to modify a GUI element, it must first &quot;take&quot; the semaphore.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;lvgl_mux = xSemaphoreCreateMutex();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the graphical task is already working, the network task (for example, the WiFi sniffer) waits in queue for a few milliseconds until the Mutex is released. This guarantees that LVGL's memory is read or written by only one thread at a time, making the firmware solid and crash-proof.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-data-security-and-crash-prevention&quot;&gt;3. Data Security and Crash Prevention&lt;/h3&gt;
&lt;p&gt;Having a responsive interface and fast loading times is not enough: we also must try to make the firmware as robust as possible against hardware failures.&lt;/p&gt;
&lt;p&gt;This justifies the choice of the file system: LittleFS. Unlike the classic FAT32 or SPIFFS (which would corrupt the disk leading to the loss of all data in case of sudden shutdown), LittleFS was specifically designed for battery-powered microcontrollers and can maintain its integrity even if the power is suddenly cut off.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;esp_vfs_littlefs_conf_t conf = {
    .base_path=&quot;/memoria&quot;,
    .partition_label=&quot;storage&quot;,
    .format_if_mount_failed = true, 
    .dont_mount = false,
};
esp_err_t ret = esp_vfs_littlefs_register(&amp;amp;conf);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A great engineering practice is using the &lt;code&gt;.format_if_mount_failed = true&lt;/code&gt; parameter, which allows handling the very first boot of the firmware on an empty memory by simply formatting it entirely on its own, without the user having to do anything.&lt;/p&gt;
&lt;h4 id=&quot;how-to-read-giant-payloads-without-running-out-of-ram&quot;&gt;How to read Giant Payloads without running out of RAM&lt;/h4&gt;
&lt;p&gt;The second major risk for stability is reading files. Many microcontrollers have very limited RAM that fills up in an instant if you try to open a file of several Megabytes all at once.&lt;/p&gt;
&lt;p&gt;To prevent the BadUSB tool from going into Out of Memory when executing complex payloads, I decided to implement a &quot;line-by-line&quot; reading. Think of it a bit like an e-reader: it doesn't load the whole book into memory, but only shows the small part of text you are reading, allowing you to scroll through the pages interacting with the interface.&lt;/p&gt;
&lt;p&gt;My parser dynamically loads only 256 bytes at a time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;bool esegui_duckyscript(const char *path) {
    FILE *f = fopen(path, &quot;r&quot;);
    if (!f) return false;

    char line[256];

    // RAM consumption is capped at 256 bytes, regardless 
    // of the size of the file loaded by the user!
    while (fgets(line, sizeof(line), f)) {
        trim_trailing(line);
        if (strlen(line) == 0) continue;

        char *cmd = strtok(line, &quot; &quot;);
        // parsing and execution of the single command...
    }
    fclose(f);
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This is how I structured my project to guarantee its stability, responsiveness, and scalability, especially considering the large number of tools I am going to implement.&lt;/p&gt;
&lt;p&gt;In the next episode, we will get to the heart of the action: I will show you how I used the USB stack to make the computer believe that the ESP32 is a keyboard, in order to execute DuckyScript scripts (BadUSB).&lt;/p&gt;
&lt;p&gt;In the meantime, which pentesting tool would you like to see added to the project? Let me know in the comments!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26982/from-arduino-to-esp-idf-the-architecture-behind-my-digital-swiss-army-knife</guid>
<pubDate>Sat, 12 Sep 2026 14:58:13 +0000</pubDate>
</item>
<item>
<title>C++ Customization Points: Why tag_invoke Was Brilliant—and Why P2300 Moved Away From It</title>
<link>https://coderlegion.com/26950/c-customization-points-why-taginvoke-was-brilliant-and-why-p2300-moved-away-from-it</link>
<description>&lt;p&gt;Generic C++ code needs customization points whenever a library wants to operate on types it doesn't own. The problem is deceptively simple: how can a library let users customize an operation without requiring inheritance, runtime dispatch, or modification of the library's source?&lt;/p&gt;
&lt;p&gt;C++ has accumulated several answers over the decades—class template specialization, argument-dependent lookup (ADL), and Customization Point Objects (CPOs)—but each leaves important gaps. In 2019, Lewis Baker, Eric Niebler, and Kirk Shoop proposed &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf&quot;&gt;P1895R0: tag_invoke — A general pattern for supporting customisable functions&lt;/a&gt;. The core idea was simple: &lt;strong&gt;use one ADL customization name (&lt;code&gt;tag_invoke&lt;/code&gt;) and encode the operation in the first argument's type&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This pattern made several architectural techniques considerably easier to express—most notably generic forwarding through wrapper types—and saw adoption in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.boost.org/doc/libs/1_86_0/libs/json/doc/html/json/conversion.html&quot;&gt;Boost.JSON&lt;/a&gt; and early sender/receiver execution frameworks. But it also ignited vigorous debate, captured in Barry Revzin's critique, &lt;a rel=&quot;nofollow&quot; href=&quot;https://brevzin.github.io/c++/2020/12/01/tag-invoke/&quot;&gt;&quot;Why tag_invoke is not the solution I want&quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The central lesson is that &lt;strong&gt;CPOs and &lt;code&gt;tag_invoke&lt;/code&gt; solve different layers of the customization problem&lt;/strong&gt;. A Customization Point Object (CPO) is the user-facing function object that encapsulates an operation's invocation and dispatch policy. &lt;code&gt;tag_invoke&lt;/code&gt; is a customization protocol that can be used to implement a CPO, or used directly by a library's customization API.&lt;/p&gt;
&lt;p&gt;Neither, however, gives C++ a language-level way to declare and verify customization interfaces. Tracing why &lt;code&gt;tag_invoke&lt;/code&gt; appeared, where it succeeded, and why the Sender/Receiver model eventually moved away from it tells one of the most instructive architectural stories in modern C++.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;crucial-distinction-cpo-tag-invoke&quot;&gt;Crucial Distinction: CPO ≠ tag_invoke&lt;/h3&gt;
&lt;p&gt;Before digging into mechanics, we should clarify the single most common source of confusion in this area: &lt;strong&gt;a Customization Point Object is not the same thing as &lt;code&gt;tag_invoke&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A Customization Point Object (CPO) is the &lt;strong&gt;user-facing function object&lt;/strong&gt; that encapsulates an operation's invocation and dispatch policy. &lt;code&gt;tag_invoke&lt;/code&gt; is one specific &lt;strong&gt;customization protocol&lt;/strong&gt; that a CPO can choose to use under the hood:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Customization Point Object (CPO) [User-facing interface &amp;amp; invocation policy]
        │
        ├── can dispatch via raw ADL (e.g. std::ranges::begin)
        ├── can dispatch via member functions (.begin())
        ├── can dispatch via class template specialization
        └── can dispatch via a tag protocol (e.g. tag_invoke)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tag_invoke&lt;/code&gt; does not replace CPOs; it is a customization protocol that can be used to implement a CPO, or used directly by a library's customization API (as Boost.JSON does).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-historical-timeline&quot;&gt;The Historical Timeline&lt;/h3&gt;
&lt;p&gt;To follow how modern C++ arrived at its current customization landscape, it helps to keep the timeline in view:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;2015   N4381 — Customization Point Objects formalized (Niebler)
   ↓
2019   P1895R0 — tag_invoke proposed (Baker, Niebler, Shoop)
   ↓
2020   Barry Revzin — &quot;Why tag_invoke is not the solution I want&quot;
   ↓
2020   Boost 1.75 — Boost.JSON adopts tag_invoke for conversions
   ↓
2021   P2279R0 — &quot;We need a language mechanism for customization points&quot; (Revzin)
   ↓
2021–23 P2300 — tag_invoke used extensively across early Sender/Receiver (libunifex)
   ↓
2024   P2300R9/R10 — std::execution replaces tag_invoke with member customization + domain dispatch
   ↓
2026   C++26 standard finalized — std::execution standardized as the core asynchronous framework
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-is-tag-invoke-a-good-fit-&quot;&gt;When Is &lt;code&gt;tag_invoke&lt;/code&gt; a Good Fit?&lt;/h3&gt;
&lt;p&gt;If you are evaluating customization strategies for your own libraries or application architectures, here is a practical decision guide:&lt;/p&gt;
&lt;h4 id=&quot;prefer-tag-invoke-when-&quot;&gt;Prefer &lt;code&gt;tag_invoke&lt;/code&gt; when:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Your ecosystem or library already uses it (e.g., writing custom serializers for Boost.JSON).&lt;/li&gt;
&lt;li&gt;You need generic wrappers or adapters that forward unknown customization points to wrapped objects.&lt;/li&gt;
&lt;li&gt;You need a uniform, non-intrusive customization protocol across a large set of operations.&lt;/li&gt;
&lt;li&gt;You want operation identity encoded in a type rather than risking collisions across shared ADL function names.&lt;/li&gt;
&lt;li&gt;You are implementing a type-erased facility around a known set of CPO tags.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;prefer-another-mechanism-when-&quot;&gt;Prefer another mechanism when:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;A named member function (&lt;code&gt;obj.operation()&lt;/code&gt;) is sufficient and you control or can constrain the participating types.&lt;/li&gt;
&lt;li&gt;The interface should be highly visible and self-documenting in class definitions.&lt;/li&gt;
&lt;li&gt;You want straightforward, localized compiler diagnostics without deep template instantiation cascades.&lt;/li&gt;
&lt;li&gt;There are only one or two simple customization points.&lt;/li&gt;
&lt;li&gt;Your ecosystem does not otherwise use &lt;code&gt;tag_invoke&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-customization-problem-amp-barry-s-criteria&quot;&gt;The Customization Problem &amp;amp; Barry's Criteria&lt;/h3&gt;
&lt;p&gt;In his 2020 analysis, Barry Revzin laid out six criteria for what constitutes a proper customization mechanism:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clear interface definition&lt;/strong&gt;: You can look at code and immediately see what operations can or must be customized, along with expected signatures and semantics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overridable default implementations&lt;/strong&gt;: A library can supply a sensible fallback that types can selectively override for efficiency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit opt-in&lt;/strong&gt;: Types intentionally declare that they satisfy the interface rather than accidentally matching loose signatures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Early failure on incorrect opt-in&lt;/strong&gt;: If a type tries to implement an interface with the wrong signature, the compiler rejects it at the point of definition, not miles away at a call site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safe and intuitive invocation&lt;/strong&gt;: Calling the customized function is simple, and callers cannot accidentally bypass user customizations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programmatic interface verification&lt;/strong&gt;: Generic code can easily check whether a type satisfies the customization requirements (e.g., via a Concept).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;(Note: While Barry's initial 2020 post framed the discussion around these six points, his subsequent paper &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2279&quot;&gt;P2279R0&lt;/a&gt; expanded the analysis to additional architectural dimensions, including atomic grouping of operations, associated types, non-intrusive customization, and generic forwarding.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In C++, &lt;strong&gt;virtual member functions&lt;/strong&gt; satisfy all six criteria cleanly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Printable {
    virtual void print() const = 0;              // 1. Clear interface
    virtual void describe() const { print(); }   // 2. Overridable default
};

struct Widget : Printable {
    void print() const override;                 // 3. Explicit opt-in
    // void print(int) override;                 // 4. Compile error at definition!
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calling &lt;code&gt;ptr-&amp;gt;describe()&lt;/code&gt; dispatches cleanly (Criterion 5), and checking &lt;code&gt;std::derived_from&amp;lt;T, Printable&amp;gt;&lt;/code&gt; verifies it (Criterion 6).&lt;/p&gt;
&lt;p&gt;However, virtual functions are unsuitable for generic programming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Intrusive&lt;/strong&gt;: You cannot add a virtual member function to &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;std::string&lt;/code&gt;, or a type defined in a third-party library without wrapping it in an adapter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime overhead&lt;/strong&gt;: Virtual dispatch introduces an indirect call through a vtable and may inhibit compiler inlining unless the dynamic type can be devirtualized. Polymorphic objects also carry a vptr. (Importantly, virtual dispatch does &lt;em&gt;not&lt;/em&gt; inherently require heap allocation—polymorphic objects can live on the stack or in static storage—but the indirection remains).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incompatible with generic value semantics&lt;/strong&gt;: Virtual methods cannot be function templates, nor can they easily return varying, dependent types tailored to each concrete implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hence the quest for static customization.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;three-generations-of-static-customization&quot;&gt;Three Generations of Static Customization&lt;/h3&gt;
&lt;h4 id=&quot;generation-1-class-template-specialization-std-hash-lt-t-gt-&quot;&gt;Generation 1: Class Template Specialization (&lt;code&gt;std::hash&amp;lt;T&amp;gt;&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;The oldest static technique requires users to specialize a class template owned by the library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;namespace std {
    template &amp;lt;&amp;gt;
    struct hash&amp;lt;MyType&amp;gt; {
        size_t operator()(const MyType&amp;amp; x) const noexcept {
            return std::hash&amp;lt;int&amp;gt;{}(x.id);
        }
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This provides explicit opt-in and prevents accidental signature matches. However:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The customization has to be expressed through a library-owned class template specialization, which makes conditional and composable customization awkward.&lt;/li&gt;
&lt;li&gt;Function-template specializations are not a substitute for overload-based customization because they do not participate in overload resolution in the same way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;generation-2-raw-adl-amp-the-two-step-dance-swap-&quot;&gt;Generation 2: Raw ADL &amp;amp; The &quot;Two-Step Dance&quot; (&lt;code&gt;swap&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;The classic C++98/11 idiom for customizable algorithms relied on Argument-Dependent Lookup (ADL):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template &amp;lt;typename T&amp;gt;
void process(T&amp;amp; a, T&amp;amp; b) {
    // The infamous two-step dance:
    using std::swap;
    swap(a, b); // Finds custom swap via ADL if present, otherwise std::swap
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Types customize &lt;code&gt;swap&lt;/code&gt; by providing a non-member function in their own namespace (typically as a hidden friend):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Buffer {
    friend void swap(Buffer&amp;amp; a, Buffer&amp;amp; b) noexcept {
        // optimized pointer swap...
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is non-intrusive and supports fallbacks, but introduces two notable failure modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Forgot the &lt;code&gt;using&lt;/code&gt;&quot; Bug&lt;/strong&gt;: Writing &lt;code&gt;std::swap(a, b)&lt;/code&gt; instead of &lt;code&gt;using std::swap; swap(a, b);&lt;/code&gt; silently bypasses the user's custom implementation, invoking the generic copy/move fallback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The ADL Namespace Problem&lt;/strong&gt;: ADL searches all namespaces associated with a function's arguments. When multiple independent libraries pick the same common function name—such as &lt;code&gt;size(x)&lt;/code&gt;—a call on a type that brings both namespaces into scope can result in ambiguous overloads or silent dispatch to an unintended function.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;generation-3-adl-based-customization-point-objects-n4381-ranges-&quot;&gt;Generation 3: ADL-based Customization Point Objects (N4381 / Ranges)&lt;/h4&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/n4381&quot;&gt;N4381: Suggested Design for Customization Points&lt;/a&gt;, Eric Niebler formalized Customization Point Objects (CPOs), which were standardized in C++20 Ranges (&lt;code&gt;std::ranges::begin&lt;/code&gt;, &lt;code&gt;std::ranges::swap&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;A CPO is an ordinary function-object value placed in a specific namespace. Callers invoke the object directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;std::ranges::swap(a, b);
auto it = std::ranges::begin(container);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The caller no longer performs ADL directly.&lt;/strong&gt; The CPO owns the dispatch policy, encapsulating internal ADL lookups, member-function checks, and default fallbacks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;namespace mylib {
    namespace _detail {
        void foo() = delete; // Poison pill to avoid finding enclosing foo()

        struct foo_fn {
            template &amp;lt;typename T&amp;gt;
                requires requires(T&amp;amp;&amp;amp; x) { foo(std::forward&amp;lt;T&amp;gt;(x)); }
            constexpr auto operator()(T&amp;amp;&amp;amp; x) const
                noexcept(noexcept(foo(std::forward&amp;lt;T&amp;gt;(x))))
                -&amp;gt; decltype(foo(std::forward&amp;lt;T&amp;gt;(x))) {
                return foo(std::forward&amp;lt;T&amp;gt;(x));
            }
        };
    }

    inline namespace _cpos {
        inline constexpr _detail::foo_fn foo{};
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the CPO is a first-class function-object value, it can be stored, passed to other algorithms, and composed without losing customization behavior.&lt;/p&gt;
&lt;h5 id=&quot;what-adl-based-cpos-still-struggle-with&quot;&gt;What ADL-based CPOs Still Struggle With&lt;/h5&gt;
&lt;p&gt;While CPOs solved the call-site invocation problem, ADL-based CPO conventions still face two architectural friction points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The ADL Namespace Problem&lt;/strong&gt;: An ADL-based CPO convention typically requires a corresponding ADL customization name (e.g., &lt;code&gt;std::ranges::begin&lt;/code&gt; dispatches to an unqualified ADL &lt;code&gt;begin(x)&lt;/code&gt;). If every library creates CPOs for dozens of domain algorithms, independent libraries still risk claiming and colliding on the same ADL identifiers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Generic Wrapper Problem&lt;/strong&gt;: A wrapper cannot generically forward arbitrary ADL customization points because C++ has no mechanism for saying &lt;em&gt;&quot;for any future function name found by ADL, forward the call to the wrapped object.&quot;&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-tag-invoke-protocol-p1895r0-&quot;&gt;The &lt;code&gt;tag_invoke&lt;/code&gt; Protocol (P1895R0)&lt;/h3&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf&quot;&gt;P1895R0&lt;/a&gt;, Lewis Baker, Eric Niebler, and Kirk Shoop proposed a uniform protocol:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Reserve &lt;strong&gt;exactly ONE&lt;/strong&gt; global ADL customization name: &lt;code&gt;tag_invoke&lt;/code&gt;. To specify which operation is being performed, &lt;strong&gt;pass the CPO itself as the first argument (the tag)&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[Caller: mylib::foo(x)]
         │
         ▼
[CPO: foo_fn::operator()(x)]
         │
         ▼
[tag_invoke(*this, x)] ──(ADL Dispatch)────────┐
                                                ▼
                         ┌──────────────────────────────────────────┐
                         │ tag_invoke(tag_t&amp;lt;foo&amp;gt;, MyType) defined?  │
                         └──────────────────┬───────────────────────┘
                                            │
                           ┌────────────────┴────────────────┐
                           ▼ YES                             ▼ NO
               [Custom Hidden Friend]             [Default Fallback in CPO]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;encoding-operations-in-tag-types&quot;&gt;Encoding Operations in Tag Types&lt;/h4&gt;
&lt;p&gt;Instead of dispatching to an ADL &lt;code&gt;foo(x)&lt;/code&gt; or &lt;code&gt;bar(x)&lt;/code&gt;, the CPO dispatches to &lt;code&gt;tag_invoke(foo, x)&lt;/code&gt; or &lt;code&gt;tag_invoke(bar, x)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because the operation identity is encoded in the first argument's type, collision risks between independent libraries are greatly reduced:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;namespace geom {
    struct area_fn { /* ... */ };
    inline constexpr area_fn area{};
}

namespace screen {
    struct area_fn { /* ... */ };
    inline constexpr area_fn area{};
}

class ScreenWindow {
    // Customization for geom::area (tag is geom::area_fn)
    friend double tag_invoke(geom::area_fn, const ScreenWindow&amp;amp; w) {
        return w.physical_width * w.physical_height;
    }

    // Customization for screen::area (tag is screen::area_fn)
    friend int tag_invoke(screen::area_fn, const ScreenWindow&amp;amp; w) {
        return w.pixel_width * w.pixel_height;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though both operations conceptually represent &quot;area&quot;, their tag types (&lt;code&gt;geom::area_fn&lt;/code&gt; and &lt;code&gt;screen::area_fn&lt;/code&gt;) are distinct C++ types. Both customizations live side-by-side as hidden friends without ambiguity.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tag_invoke&lt;/code&gt; is still an ADL customization mechanism—it does not eliminate all ADL-related complexity—but it channels lookups through a single identifier differentiated by the tag type.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-pedagogical-tag-invoke-dispatcher&quot;&gt;A Pedagogical &lt;code&gt;tag_invoke&lt;/code&gt; Dispatcher&lt;/h3&gt;
&lt;p&gt;Let's build a deliberately simplified &lt;code&gt;tag_invoke&lt;/code&gt; dispatcher for exposition. Production implementations (such as in &lt;code&gt;libunifex&lt;/code&gt;, &lt;code&gt;stdexec&lt;/code&gt;, or Boost) add substantial machinery for return type deduction, noexcept propagation, constraint checking, and error diagnostics.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;concepts&amp;gt;
#include &amp;lt;type_traits&amp;gt;
#include &amp;lt;utility&amp;gt;

namespace core {

namespace _tag_invoke_detail {
    // Poison pill declaration to ensure ADL occurs cleanly
    void tag_invoke();

    struct _fn {
        template &amp;lt;typename Tag, typename... Args&amp;gt;
            requires requires(Tag&amp;amp;&amp;amp; tag, Args&amp;amp;&amp;amp;... args) {
                tag_invoke(std::forward&amp;lt;Tag&amp;gt;(tag), std::forward&amp;lt;Args&amp;gt;(args)...);
            }
        constexpr decltype(auto) operator()(Tag&amp;amp;&amp;amp; tag, Args&amp;amp;&amp;amp;... args) const
            noexcept(noexcept(tag_invoke(std::forward&amp;lt;Tag&amp;gt;(tag), std::forward&amp;lt;Args&amp;gt;(args)...))) {
            return tag_invoke(std::forward&amp;lt;Tag&amp;gt;(tag), std::forward&amp;lt;Args&amp;gt;(args)...);
        }
    };
}

// The caller-facing dispatcher object
inline namespace _cpos {
    inline constexpr _tag_invoke_detail::_fn tag_invoke{};
}

// Convenience helper to extract the tag type from a CPO variable reference
template &amp;lt;auto&amp;amp; Tag&amp;gt;
using tag_t = std::decay_t&amp;lt;decltype(Tag)&amp;gt;;

// Concepts checking whether a tag can be invoked with given arguments
template &amp;lt;typename Tag, typename... Args&amp;gt;
concept tag_invocable = std::invocable&amp;lt;decltype(tag_invoke), Tag, Args...&amp;gt;;

template &amp;lt;typename Tag, typename... Args&amp;gt;
concept nothrow_tag_invocable =
    tag_invocable&amp;lt;Tag, Args...&amp;gt; &amp;amp;&amp;amp;
    std::is_nothrow_invocable_v&amp;lt;decltype(tag_invoke), Tag, Args...&amp;gt;;

template &amp;lt;typename Tag, typename... Args&amp;gt;
using tag_invoke_result_t = std::invoke_result_t&amp;lt;decltype(tag_invoke), Tag, Args...&amp;gt;;

} // namespace core
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;defining-a-cpo-with-tag-invoke&quot;&gt;Defining a CPO with &lt;code&gt;tag_invoke&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Writing a CPO in a &lt;code&gt;tag_invoke&lt;/code&gt;-based ecosystem becomes straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;namespace graphics {

inline constexpr struct draw_fn {
    template &amp;lt;typename T&amp;gt;
        requires core::tag_invocable&amp;lt;draw_fn, const T&amp;amp;&amp;gt;
    void operator()(const T&amp;amp; item) const
        noexcept(core::nothrow_tag_invocable&amp;lt;draw_fn, const T&amp;amp;&amp;gt;) {
        core::tag_invoke(*this, item);
    }
} draw{};

} // namespace graphics
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And user types customize it via a hidden friend:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;

struct Circle {
    double radius;

    friend void tag_invoke(core::tag_t&amp;lt;graphics::draw&amp;gt;, const Circle&amp;amp; c) noexcept {
        std::cout &amp;lt;&amp;lt; &quot;Drawing a circle with radius &quot; &amp;lt;&amp;lt; c.radius &amp;lt;&amp;lt; &quot;\n&quot;;
    }
};

int main() {
    Circle c{4.5};
    graphics::draw(c); // Prints: Drawing a circle with radius 4.5
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-killer-feature-generic-forwarding-through-wrappers&quot;&gt;The Killer Feature: Generic Forwarding Through Wrappers&lt;/h3&gt;
&lt;p&gt;This is the central architectural reason &lt;code&gt;tag_invoke&lt;/code&gt; gained widespread attention.&lt;/p&gt;
&lt;p&gt;Consider an adapter or decorator type—a logging wrapper, a thread-safe synchronized adapter, or a mock wrapper. You want the wrapper to forward customizations to the underlying object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template &amp;lt;typename T&amp;gt;
struct logged {
    T value;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In an ADL-based CPO world, the wrapper cannot forward unknown operations because C++ has no syntax for: &lt;em&gt;&quot;for any future function name found by ADL, forward to &lt;code&gt;value&lt;/code&gt;&quot;&lt;/em&gt;. The wrapper author must manually write a forwarding friend for every single CPO by name.&lt;/p&gt;
&lt;p&gt;Because all CPOs in a &lt;code&gt;tag_invoke&lt;/code&gt;-based ecosystem share the single customization function name &lt;code&gt;tag_invoke&lt;/code&gt;, the wrapper needs &lt;strong&gt;exactly one templated friend function&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template &amp;lt;typename T&amp;gt;
struct logged {
    T value;

    template &amp;lt;typename Tag, typename... Args&amp;gt;
        requires requires(Tag tag, const T&amp;amp; val, Args&amp;amp;&amp;amp;... args) {
            tag(val, std::forward&amp;lt;Args&amp;gt;(args)...);
        }
    friend decltype(auto) tag_invoke(Tag tag, const logged&amp;amp; w, Args&amp;amp;&amp;amp;... args) {
        std::cout &amp;lt;&amp;lt; &quot;[LOG] Intercepted CPO call!\n&quot;;
        return tag(w.value, std::forward&amp;lt;Args&amp;gt;(args)...);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing this with our &lt;code&gt;Circle&lt;/code&gt; and &lt;code&gt;graphics::draw&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int main() {
    logged&amp;lt;Circle&amp;gt; monitored{Circle{10.0}};

    // graphics::draw does not know about logged&amp;lt;Circle&amp;gt;.
    // logged&amp;lt;Circle&amp;gt; does not hardcode graphics::draw.
    // Yet forwarding works naturally:
    graphics::draw(monitored);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[LOG] Intercepted CPO call!
Drawing a circle with radius 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-caveat-generic-forwarding-can-be-dangerous&quot;&gt;The Caveat: Generic Forwarding Can Be Dangerous&lt;/h4&gt;
&lt;p&gt;While generic forwarding is powerful, it grants the wrapper broad authority over the customization surface:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CV/Ref Qualification&lt;/strong&gt;: The simplified example above takes &lt;code&gt;const logged&amp;amp;&lt;/code&gt; and forwards to &lt;code&gt;const T&amp;amp;&lt;/code&gt;. A truly transparent wrapper must preserve the value category and constness (&lt;code&gt;logged&amp;amp;&lt;/code&gt;, &lt;code&gt;const logged&amp;amp;&lt;/code&gt;, &lt;code&gt;logged&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;const logged&amp;amp;&amp;amp;&lt;/code&gt;), forwarding mutable and move-only operations correctly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Greedy Overloads&lt;/strong&gt;: A catch-all &lt;code&gt;tag_invoke&lt;/code&gt; overload is easily matched. If it is not constrained carefully, it can accidentally intercept operations intended specifically for the wrapper itself, introduce unintended recursion, alter exception guarantees (&lt;code&gt;noexcept&lt;/code&gt;), or silently skew overload resolution.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As Barry Revzin discussed in &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2547&quot;&gt;P2547R1&lt;/a&gt;, generic forwarding is one of &lt;code&gt;tag_invoke&lt;/code&gt;'s greatest architectural strengths, but it requires rigorous constraints to avoid hijacking operations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-world-adoption-boost-json&quot;&gt;Real-World Adoption: Boost.JSON&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.boost.org/doc/libs/1_86_0/libs/json/doc/html/json/conversion.html&quot;&gt;Boost.JSON&lt;/a&gt; (introduced in Boost 1.75) is one of the most prominent production examples of the &lt;code&gt;tag_invoke&lt;/code&gt; pattern.&lt;/p&gt;
&lt;p&gt;Importantly, Boost.JSON uses &lt;code&gt;tag_invoke&lt;/code&gt; as its customization protocol without requiring &lt;code&gt;value_from&lt;/code&gt; and &lt;code&gt;value_to&lt;/code&gt; themselves to be CPO objects. Boost.JSON documents &lt;code&gt;value_from&lt;/code&gt; and &lt;code&gt;value_to&lt;/code&gt; as &lt;strong&gt;function templates&lt;/strong&gt; whose customization is performed through ADL-discovered &lt;code&gt;tag_invoke&lt;/code&gt; overloads.&lt;/p&gt;
&lt;p&gt;Boost.JSON defines two primary tags in &lt;code&gt;boost/json/conversion.hpp&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boost::json::value_from_tag&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boost::json::value_to_tag&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;value_to_tag&amp;lt;T&amp;gt;&lt;/code&gt; tag is especially clever: &lt;strong&gt;the target type &lt;code&gt;T&lt;/code&gt; is encoded as a template parameter of the tag type itself&lt;/strong&gt;. This ensures that the user's namespace is automatically included in the associated namespaces for ADL lookup, even when converting from a standard JSON container.&lt;/p&gt;
&lt;h4 id=&quot;practical-boost-json-walkthrough&quot;&gt;Practical Boost.JSON Walkthrough&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;boost/json.hpp&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

struct User {
    int id;
    std::string name;

    // 1. Serialize: User -&amp;gt; boost::json::value
    friend void tag_invoke(boost::json::value_from_tag, boost::json::value&amp;amp; jv, const User&amp;amp; u) {
        jv = {
            {&quot;id&quot;, u.id},
            {&quot;name&quot;, u.name}
        };
    }

    // 2. Deserialize: boost::json::value -&amp;gt; User
    friend User tag_invoke(boost::json::value_to_tag&amp;lt;User&amp;gt;, const boost::json::value&amp;amp; jv) {
        const auto&amp;amp; obj = jv.as_object();
        return User{
            boost::json::value_to&amp;lt;int&amp;gt;(obj.at(&quot;id&quot;)),
            boost::json::value_to&amp;lt;std::string&amp;gt;(obj.at(&quot;name&quot;))
        };
    }
};

int main() {
    User alice{42, &quot;Alice&quot;};

    // Value -&amp;gt; JSON
    boost::json::value jv = boost::json::value_from(alice);
    std::cout &amp;lt;&amp;lt; &quot;Serialized: &quot; &amp;lt;&amp;lt; boost::json::serialize(jv) &amp;lt;&amp;lt; &quot;\n&quot;;

    // JSON -&amp;gt; Value
    User restored = boost::json::value_to&amp;lt;User&amp;gt;(jv);
    std::cout &amp;lt;&amp;lt; &quot;Restored: &quot; &amp;lt;&amp;lt; restored.id &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; restored.name &amp;lt;&amp;lt; &quot;\n&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the engineering benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The customization functions are hidden friends inside &lt;code&gt;User&lt;/code&gt;, so they do not pollute the enclosing namespace.&lt;/li&gt;
&lt;li&gt;The user never reopens &lt;code&gt;namespace boost::json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The user-facing API functions &lt;code&gt;value_from&lt;/code&gt; and &lt;code&gt;value_to&lt;/code&gt; provide a clean, type-safe entry point.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;sender-receiver-why-tag-invoke-was-attractive-and-why-p2300-moved-away&quot;&gt;Sender/Receiver: Why &lt;code&gt;tag_invoke&lt;/code&gt; Was Attractive—and Why P2300 Moved Away&lt;/h3&gt;
&lt;p&gt;Historically, the primary driver for &lt;code&gt;tag_invoke&lt;/code&gt; was the Sender/Receiver model for asynchronous execution, developed in Meta's &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/facebookexperimental/libunifex&quot;&gt;&lt;code&gt;libunifex&lt;/code&gt;&lt;/a&gt; and early revisions of &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2300&quot;&gt;P2300&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An asynchronous runtime involves two layers of operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Basis operations&lt;/strong&gt;: Primitives that execution contexts must implement (&lt;code&gt;schedule&lt;/code&gt;, &lt;code&gt;connect&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;set_value&lt;/code&gt;, &lt;code&gt;set_error&lt;/code&gt;, &lt;code&gt;set_stopped&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable algorithms&lt;/strong&gt;: Generic algorithms with default implementations that can be overridden for specific execution domains (such as &lt;code&gt;sync_wait&lt;/code&gt;, &lt;code&gt;then&lt;/code&gt;, or &lt;code&gt;transfer&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;the-original-appeal&quot;&gt;The Original Appeal&lt;/h4&gt;
&lt;p&gt;Early Sender/Receiver designs used &lt;code&gt;tag_invoke&lt;/code&gt; universally. It solved the namespace pollution problem and allowed platform authors to specialize algorithms:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Illustrative concept in early Sender/Receiver:
friend auto tag_invoke(sync_wait_tag, const cuda_sender&amp;amp; s) {
    // Specialized synchronization tailored to a GPU stream rather than
    // blocking the host thread with a generic mutex/condition_variable
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tag type also made it possible for a type-erased wrapper to define a vtable for a selected set of CPO tags and implement each entry by invoking the corresponding customization. P2300-era papers explicitly explored this technique for type-erasing senders and receivers.&lt;/p&gt;
&lt;h4 id=&quot;the-retreat-why-p2300-replaced-tag-invoke&quot;&gt;The Retreat: Why P2300 Replaced &lt;code&gt;tag_invoke&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Despite these benefits, practical experience with &lt;code&gt;tag_invoke&lt;/code&gt; at scale exposed serious pain points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inscrutable Diagnostics&lt;/strong&gt;: When a &lt;code&gt;tag_invoke&lt;/code&gt; overload fails a concept constraint, compilers emit multi-page template instantiation cascades rather than pinpointing what signature was expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overload-Set Bloat&lt;/strong&gt;: Every customized operation sharing the single name &lt;code&gt;tag_invoke&lt;/code&gt; meant that in massive codebases, ADL candidate sets became large, slowing down compilation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interface Obscurity&lt;/strong&gt;: Reading a class definition gave no clear indication of what interface was being fulfilled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As explicitly documented in &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2300&quot;&gt;P2300R10&lt;/a&gt;, the authors replaced &lt;code&gt;tag_invoke&lt;/code&gt; with an explicit, multi-tiered customization model:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Receiver and Completion Customizations Moved to Named Members&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;schedule(sch)&lt;/code&gt; → dispatches to member &lt;code&gt;sch.schedule()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set_value(rcvr, ...)&lt;/code&gt; → dispatches to member &lt;code&gt;rcvr.set_value(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set_error(rcvr, ...)&lt;/code&gt; → dispatches to member &lt;code&gt;rcvr.set_error(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set_stopped(rcvr)&lt;/code&gt; → dispatches to member &lt;code&gt;rcvr.set_stopped()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution-Domain Dispatch for Algorithms&lt;/strong&gt;:&lt;br&gt;
Sender algorithms like &lt;code&gt;sync_wait&lt;/code&gt; and &lt;code&gt;then&lt;/code&gt; now customize via an explicit execution-domain mechanism using &lt;code&gt;get_domain&lt;/code&gt;, &lt;code&gt;get_completion_domain&lt;/code&gt;, and &lt;code&gt;apply_sender&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query Customization via Environments&lt;/strong&gt;:&lt;br&gt;
Environment queries use &lt;code&gt;get_env(obj)&lt;/code&gt; returning queryable environment objects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This architectural shift is telling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Raw ADL ──&amp;gt; CPOs ──&amp;gt; tag_invoke ──&amp;gt; Named Member Customization + Domain Dispatch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The evolution was not simply &quot;standardizing &lt;code&gt;tag_invoke&lt;/code&gt;.&quot; It was realizing that different customization points have different requirements, and completion operations benefit from explicit member syntax.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;barry-revzin-s-critique-tag-invoke-is-not-a-trait-system&quot;&gt;Barry Revzin's Critique: &lt;code&gt;tag_invoke&lt;/code&gt; Is Not a Trait System&lt;/h3&gt;
&lt;p&gt;In December 2020, Barry Revzin published &lt;a rel=&quot;nofollow&quot; href=&quot;https://brevzin.github.io/c++/2020/12/01/tag-invoke/&quot;&gt;Why tag_invoke is not the solution I want&lt;/a&gt;, followed by &lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2279&quot;&gt;P2279R0: We need a language mechanism for customization points&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Barry framed the central question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;tag_invoke&lt;/code&gt; answers: &lt;em&gt;&quot;How can libraries build a unified, non-intrusive customization mechanism using existing language rules?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Barry asks: &lt;em&gt;&quot;Why should users have to construct this machinery in a library when the language could understand customization interfaces directly?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;1-inscrutable-interface-definitions&quot;&gt;1. Inscrutable Interface Definitions&lt;/h4&gt;
&lt;p&gt;With language-level traits (such as Rust's &lt;code&gt;PartialEq&lt;/code&gt;), an interface with a mandatory function and an overridable default is concise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;trait PartialEq {
    fn eq(&amp;amp;self, rhs: &amp;amp;Self) -&amp;gt; bool;
    fn ne(&amp;amp;self, rhs: &amp;amp;Self) -&amp;gt; bool {
        !self.eq(rhs)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;tag_invoke&lt;/code&gt;, expressing that same two-function interface requires dozens of lines of template metaprogramming, nested &lt;code&gt;requires requires&lt;/code&gt; clauses, SFINAE fallbacks, and tag-dispatch helper structs. For an application programmer reading the library header, the actual contractual interface is buried under implementation machinery.&lt;/p&gt;
&lt;h4 id=&quot;2-lack-of-explicit-interface-declarations-amp-deferred-errors&quot;&gt;2. Lack of Explicit Interface Declarations &amp;amp; Deferred Errors&lt;/h4&gt;
&lt;p&gt;In a language with native traits (or with &lt;code&gt;virtual ... override&lt;/code&gt;), a type explicitly states which interface it implements. If the signature is slightly off, the compiler halts immediately at the type definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct D : B {
    void f(unsigned int) override; // Compile error at definition!
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;tag_invoke&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Widget {
    int id;

    // Subtle slip: takes Widget&amp;amp; instead of const Widget&amp;amp;
    friend bool tag_invoke(eq_tag, Widget&amp;amp; a, Widget&amp;amp; b) {
        return a.id == b.id;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This hidden friend does not declare that it is satisfying &lt;code&gt;eq&lt;/code&gt;. It merely declares an overload that might be found by future ADL lookups. If someone later calls &lt;code&gt;eq(const_widget1, const_widget2)&lt;/code&gt;, the overload is quietly ignored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a default fallback exists, the compiler silently calls it (possibly producing incorrect runtime results).&lt;/li&gt;
&lt;li&gt;If no fallback exists, the compiler emits a deep template-instantiation error stating that &lt;code&gt;Widget&lt;/code&gt; does not satisfy &lt;code&gt;tag_invocable&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;what-tag-invoke-deliberately-does-not-solve&quot;&gt;What &lt;code&gt;tag_invoke&lt;/code&gt; Deliberately Does Not Solve&lt;/h4&gt;
&lt;p&gt;It is also worth making clear what &lt;code&gt;tag_invoke&lt;/code&gt; is not designed to address:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Member operator synthesis&lt;/strong&gt;: It does not synthesize &lt;code&gt;operator++&lt;/code&gt;, &lt;code&gt;operator*&lt;/code&gt;, or &lt;code&gt;operator[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Associated types and typedefs&lt;/strong&gt;: It does not define member types like &lt;code&gt;iterator_category&lt;/code&gt; or &lt;code&gt;value_type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structural interface declarations&lt;/strong&gt;: It does not declare class layouts or interface contracts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For synthesizing class interfaces and operators (such as writing an iterator from minimal primitives), developers still use mixin techniques, CRTP, or C++23 explicit object parameters (&lt;code&gt;deducing this&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;compile-time-cost-of-customization-why-adl-matters&quot;&gt;Compile-Time Cost of Customization: Why ADL Matters&lt;/h3&gt;
&lt;p&gt;Runtime performance is rarely the only consideration in large C++ projects; compile time is often just as critical.&lt;/p&gt;
&lt;p&gt;ADL-based customization has direct compile-time consequences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Associated Namespace Traversal&lt;/strong&gt;: Every ADL lookup must examine every namespace associated with every argument type, including enclosing namespaces, base classes, and template arguments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overload-Set Size&lt;/strong&gt;: When all customizations share the single identifier &lt;code&gt;tag_invoke&lt;/code&gt;, every participating type that is reachable contributes candidate overloads to the ADL overload set. The compiler must evaluate SFINAE constraints or concept &lt;code&gt;requires&lt;/code&gt; clauses on every candidate to determine the best match.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Template Instantiation Depth&lt;/strong&gt;: Constraining CPOs with &lt;code&gt;std::tag_invocable&lt;/code&gt; triggers nested concept evaluations that increase memory consumption and compiler throughput times in translation units with heavy generic code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Part of the motivation in P2300's shift to named member functions (&lt;code&gt;sch.schedule()&lt;/code&gt;, &lt;code&gt;rcvr.set_value(...)&lt;/code&gt;) was to make lookup direct, localized, and fast to compile, bypassing ADL candidate set construction entirely for hot completion paths.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters-in-embedded-c-&quot;&gt;Why This Matters in Embedded C++&lt;/h3&gt;
&lt;p&gt;For embedded systems and bare-metal programming, customization mechanisms are scrutinized under specific constraints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Zero Runtime Overhead&lt;/strong&gt;: Like CPOs, &lt;code&gt;tag_invoke&lt;/code&gt; resolves entirely at compile time. Overloads inline aggressively, with no vtable indirection and no heap allocation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exception Safety &amp;amp; &lt;code&gt;noexcept&lt;/code&gt;&lt;/strong&gt;: Embedded environments frequently compile with &lt;code&gt;-fno-exceptions&lt;/code&gt;. A well-designed &lt;code&gt;tag_invoke&lt;/code&gt; dispatcher enforces &lt;code&gt;nothrow_tag_invocable&lt;/code&gt;, guaranteeing that customization overloads do not introduce throwing paths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapting Vendor Drivers &amp;amp; HALs Without Modification&lt;/strong&gt;:&lt;br&gt;
In embedded projects, you frequently deal with vendor-provided Hardware Abstraction Layers (HALs) that you cannot modify. With &lt;code&gt;tag_invoke&lt;/code&gt;, you can customize higher-level protocols for vendor structs non-intrusively:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Vendor HAL type (unmodified)
namespace vendor_hal {
    struct stm32_spi_bus { /* hardware registers... */ };
}

// Our application customization
namespace embedded {
    inline constexpr struct transfer_fn {
        template &amp;lt;typename Device, typename Buffer&amp;gt;
            requires core::tag_invocable&amp;lt;transfer_fn, Device&amp;amp;, Buffer&amp;amp;&amp;gt;
        void operator()(Device&amp;amp; dev, Buffer&amp;amp; buf) const noexcept {
            core::tag_invoke(*this, dev, buf);
        }
    } transfer{};
}

// Customize vendor_hal::stm32_spi_bus for embedded::transfer
// without touching vendor headers!
namespace vendor_hal {
    inline void tag_invoke(core::tag_t&amp;lt;embedded::transfer&amp;gt;,
                           stm32_spi_bus&amp;amp; bus,
                           std::span&amp;lt;uint8_t&amp;gt;&amp;amp; data) noexcept {
        // low-level register writes...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, generic decorators like &lt;code&gt;dma_device&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;logged_device&amp;lt;T&amp;gt;&lt;/code&gt;, or &lt;code&gt;mock_device&amp;lt;T&amp;gt;&lt;/code&gt; can forward &lt;code&gt;embedded::transfer&lt;/code&gt; generically across different MCU targets.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;comparison-matrix&quot;&gt;Comparison Matrix&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Raw ADL (&lt;code&gt;swap&lt;/code&gt;) &lt;/th&gt;&lt;th&gt;ADL-based CPO (Ranges) &lt;/th&gt;&lt;th&gt;&lt;code&gt;tag_invoke&lt;/code&gt; Protocol&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Call syntax&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Unqualified + &lt;code&gt;using&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Qualified (&lt;code&gt;ranges::swap&lt;/code&gt;) &lt;/td&gt;&lt;td&gt;Qualified (&lt;code&gt;lib::foo&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Caller controls dispatch&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Easily bypassed (forgotten &lt;code&gt;using&lt;/code&gt;) &lt;/td&gt;&lt;td&gt;CPO controls it &lt;/td&gt;&lt;td&gt;CPO controls it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Passable as function object&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Operation-specific dispatch&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Via function name &lt;/td&gt;&lt;td&gt;Via function name / CPO &lt;/td&gt;&lt;td&gt;Via tag type&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Name collision risk&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Possible and potentially serious &lt;/td&gt;&lt;td&gt;Possible (ADL name shared) &lt;/td&gt;&lt;td&gt;Greatly reduced (encoded in tag)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Generic wrapper forwarding&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Very difficult &lt;/td&gt;&lt;td&gt;Very difficult &lt;/td&gt;&lt;td&gt;Natural (single catch-all overload)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Definition-time interface checking&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Usually no &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary-and-key-takeaways&quot;&gt;Summary and Key Takeaways&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CPO ≠ &lt;code&gt;tag_invoke&lt;/code&gt;&lt;/strong&gt;: A Customization Point Object is a caller-facing function object that encapsulates dispatch policy; &lt;code&gt;tag_invoke&lt;/code&gt; is a tag-dispatch customization protocol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPOs Fix Invocation&lt;/strong&gt;: By moving the dispatch decision from the call site into a library-owned object, CPOs eliminate the fragile two-step ADL dance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tag_invoke&lt;/code&gt;'s Core Strength Is Generic Forwarding&lt;/strong&gt;: By routing customizations through a single ADL name differentiated by tag type, wrappers can forward arbitrary CPOs without hardcoding their names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boost.JSON Proves Its Value for Conversions&lt;/strong&gt;: Boost.JSON demonstrates that &lt;code&gt;tag_invoke&lt;/code&gt; is a proven, highly effective pattern for decoupled domain conversions where types should not depend on JSON headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Lesson of P2300&lt;/strong&gt;: &lt;code&gt;tag_invoke&lt;/code&gt; was not a failed idea—it solved real problems that earlier CPOs handled poorly. Its retreat from P2300 was a pragmatic recognition that completion operations benefit from explicit member syntax, cleaner diagnostics, and faster compile times, while complex algorithms benefit from domain-based dispatch.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Until C++ gains native language support for customization points, &lt;code&gt;tag_invoke&lt;/code&gt; remains one of the most clever, powerful, and architecturally instructive patterns in modern C++ generic library design.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-amp-further-reading&quot;&gt;References &amp;amp; Further Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf&quot;&gt;P1895R0: tag_invoke — A general pattern for supporting customisable functions&lt;/a&gt; — Lewis Baker, Eric Niebler, Kirk Shoop (2019)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://brevzin.github.io/c++/2020/12/01/tag-invoke/&quot;&gt;Why tag_invoke is not the solution I want&lt;/a&gt; — Barry Revzin (2020)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/n4381&quot;&gt;N4381: Suggested Design for Customization Points&lt;/a&gt; — Eric Niebler (2015)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2279&quot;&gt;P2279R0: We need a language mechanism for customization points&lt;/a&gt; — Barry Revzin (2021)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2547&quot;&gt;P2547R1: Language Support for Customisable Functions&lt;/a&gt; — Barry Revzin (2022)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p1292&quot;&gt;P1292R0: Customization Point Functions&lt;/a&gt; — Matt Calabrese (2018)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://wg21.link/p2300&quot;&gt;P2300R10: std::execution&lt;/a&gt; — Michal Dominiak, Lewis Baker, Lee Howes, Eric Niebler, Kirk Shoop, et al. (2024)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.boost.org/doc/libs/1_86_0/libs/json/doc/html/json/conversion/custom_conversions.html&quot;&gt;Boost.JSON Value Conversion &amp;amp; Custom Conversions Documentation&lt;/a&gt; — Vinnie Falco, Kary Pardy&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/boostorg/json&quot;&gt;Boost.JSON GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26950/c-customization-points-why-taginvoke-was-brilliant-and-why-p2300-moved-away-from-it</guid>
<pubDate>Sat, 12 Sep 2026 09:41:39 +0000</pubDate>
</item>
<item>
<title>No default constructor exists for class</title>
<link>https://coderlegion.com/26936/no-default-constructor-exists-for-class</link>
<description>&lt;p&gt;
In C++, a default constructor is a constructor that is automatically created by the compiler if no other constructors have been defined for a class. However, if a class has any constructors that have been explicitly defined, the compiler will not create a default constructor for that class. This article will discuss the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&quot;no default constructor exists for class&quot;&lt;/span&gt; error in C++ and provide solutions on how to fix it. Whether you are a beginner or an experienced C++ developer, this article will help you understand the concept of default constructors and how to use them effectively. So, let's dive in and explore the world of C++ constructors!
&lt;/p&gt;&lt;hr&gt;
&lt;div id=&quot;toc-div&quot;&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents:&lt;a id=&quot;toc-toogle&quot; class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div id=&quot;toc-list&quot;&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#what-is-a-constructor-and-it's-role-in-a-class&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;What is a constructor and it's role in a class?&lt;/span&gt;&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#differences-between-default-and-parameterized-constructors&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Differences between default and parameterized constructors&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#working-with-default-constructor-in-c++&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Working with default constructor in C++&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#best-practices-for-using-constructors-in-c++&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Best practices for using constructors in C++&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#comparison-of-default-constructors-in-c++-and-other-programming-languages&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Comparison of default constructors in C++ and other programming languages&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#conclusion&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#references&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;What is a constructor and it's role in a class?&lt;/span&gt;&lt;a id=&quot;what-is-a-constructor-and-it's-role-in-a-class&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A constructor is a special member function of a class that is executed when an object of that class is created. It is used to initialize the state of an object, and its name is always the same as the class name. The purpose of a constructor is to ensure that the object of a class is created in a valid state.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Differences between default and parameterized constructors&lt;/span&gt;&lt;a id=&quot;differences-between-default-and-parameterized-constructors&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In C++, constructors can be defined with or without parameters. A constructor without parameters is known as a &lt;span style=&quot;color: #ff04fc;&quot;&gt;default constructor&lt;/span&gt;, while a constructor with parameters is known as a &lt;span style=&quot;color: #ff04fc;&quot;&gt;parameterized constructor&lt;/span&gt;. The default constructor is called when an object is created without any arguments, and it is responsible for initializing the object to a default state. On the other hand, a parameterized constructor is called when an object is created with specific arguments, and it is responsible for initializing the object to a specific state based on the arguments passed.&lt;/p&gt;
&lt;p&gt;For example, consider the following class definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt; 
class MyClass {
    int x;
public:
    MyClass() {
        x = 0;
    }
    MyClass(int value) {
        x = value;
    }
};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;MyClass&lt;/code&gt;&lt;/span&gt; class has two constructors, one default and one parameterized. The default constructor initializes the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;x&lt;/code&gt;&lt;/span&gt; variable to &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;0&lt;/code&gt;&lt;/span&gt;, while the parameterized constructor initializes the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;x&lt;/code&gt;&lt;/span&gt; variable to the value passed as an argument.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
MyClass obj; // calls default constructor and x = 0
MyClass obj1(10); // calls parameterized constructor and x = 10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;It's worth noting that, If a class does not have any constructor defined, the compiler creates a default constructor automatically. However, if the class has any constructor defined explicitly, the compiler will not create a default constructor automatically.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;This can lead to a problem if a program attempts to create an object of that class without explicitly calling one of the defined constructors.&lt;/p&gt;
&lt;p&gt;For example, consider the following class definition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
public:
    MyClass(int x) {
        // constructor code
    }
};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the class &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;MyClass&lt;/code&gt;&lt;/span&gt; has a single constructor that takes an int argument. Because this constructor has been explicitly defined, the compiler will not create a default constructor for the class. As a result, the following code will not compile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
MyClass myObject;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result in a compile-time error since we are trying to initialize an object with a default constructor whereas we created a constructor in our class definition without defining a default constructor.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Working with default constructor in C++&lt;/span&gt;&lt;a id=&quot;working-with-default-constructor-in-c++&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To fix this issue, you can either define a default constructor for the class or create an object of the class by explicitly calling one of the defined constructors. For example, you can define a default constructor for the class as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
public:
    MyClass(int x) {
        // constructor code
    }

    MyClass() {
        // default constructor code
    }
};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to create an object of the class without any arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
MyClass myObject;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can create an object of the class by explicitly calling one of the defined constructors:&lt;br&gt;
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;br&gt;
MyClass myObject(42);&lt;br&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
Another way to fix this issue is to use the initialization list to initialize the object by passing the required parameters.
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
class MyClass {
    int x;
public:
    MyClass(int x) : x(x) {
        // constructor code
    }
};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the object is created and initialized in the same statement.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Best practices for using constructors in C++&lt;/span&gt;&lt;a id=&quot;best-practices-for-using-constructors-in-c++&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When working with constructors in C++, it is important to follow certain best practices to ensure that your code is efficient, readable, and maintainable. Here are some best practices to keep in mind when using constructors in C++:&lt;br&gt;
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Always initialize member variables&lt;/b&gt;: When defining a constructor, make sure to initialize all member variables to their default or appropriate values. This ensures that the object is created in a valid state.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Avoid duplicating code&lt;/b&gt;: When defining multiple constructors for a class, try to avoid duplicating code across constructors. Instead, use a single constructor and pass default values to the member variables.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Use the initialization list&lt;/b&gt;: Instead of initializing the member variables in the constructor body, use the initialization list to initialize the member variables directly. This is more efficient and prevents the object from being in an invalid state if an exception is thrown.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Make the constructors explicit&lt;/b&gt;: When defining a constructor with a single argument, make the constructor explicit to avoid unexpected type conversions.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Avoid overloading constructors&lt;/b&gt;: Overloading constructors can make the code hard to read and maintain. Instead, use default values for member variables in constructors to allow for more flexibility when creating objects.&lt;/li&gt;&lt;/ul&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Comparison of default constructors in C++ and other programming languages&lt;/span&gt;&lt;a id=&quot;comparison-of-default-constructors-in-c++-and-other-programming-languages&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When comparing default constructors in C++ to other programming languages, it is important to note that the behavior and implementation of default constructors can vary.&lt;/p&gt;
&lt;p&gt;In some programming languages such as Java, the default constructor is automatically created by the compiler even if other constructors are defined. This means that in Java, the &quot;no default constructor exists for class&quot; error will not occur.&lt;/p&gt;
&lt;p&gt;In other programming languages such as C#, the default constructor is not created by the compiler unless the programmer explicitly defines it. This is similar to the behavior in C++, where the default constructor is not created by the compiler if any other constructors are defined.&lt;/p&gt;
&lt;p&gt;In contrast, in languages like Python, constructors are not mandatory and there is no concept of default constructors. Instead, the initialization of an object can be done using the &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;__init__()&lt;/code&gt;&lt;/span&gt; method, which is called when an object is created.&lt;/p&gt;
&lt;p&gt;It is important to note that understanding the behavior and implementation of default constructors in different programming languages is crucial, especially when working on projects that involve multiple languages or when transitioning from one language to another. This is because the &quot;no default constructor exists for class&quot; error in C++ can be avoided by using a different programming language with different default constructor behavior.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a class has any constructors that have been explicitly defined, the compiler will not create a default constructor for that class. This can lead to a problem if a program attempts to create an object of that class without explicitly calling one of the defined constructors. To fix this issue, you can either define a default constructor for the class or create an object of the class by explicitly calling one of the defined constructors, or use the Initialization list.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;References&lt;/span&gt;&lt;a id=&quot;references&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;&lt;li&gt;Stroustrup, B. (2013). The C++ Programming Language. Pearson Education.&lt;/li&gt;&lt;li&gt;Deitel, P. J., &amp;amp; Deitel, H. M. (2017). C++ how to program. Pearson.&lt;/li&gt;&lt;li&gt;Lippman, S. B., Lajoie, J., &amp;amp; Moo, B. E. (2014). C++ primer. Addison-Wesley.&lt;/li&gt;&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26936/no-default-constructor-exists-for-class</guid>
<pubDate>Sat, 12 Sep 2026 06:47:15 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 21 — Dangers of Enums</title>
<link>https://coderlegion.com/26340/tuesday-coding-tip-21-dangers-of-enums</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;C++ and C# have a caveat in their &lt;code&gt;enum&lt;/code&gt; types. Each &lt;code&gt;enum&lt;/code&gt; has an underlying storage type which limits a maximum number of values and how much space it takes in memory. However, it also works the other way — it says how many distinct values the &lt;code&gt;enum&lt;/code&gt; handles.&lt;/p&gt;
&lt;p&gt;If you define an &lt;code&gt;enum&lt;/code&gt; with just three values, if it has an underlying type of &lt;code&gt;uint8&lt;/code&gt;, it handles 256 distinct values. You can take any integer up to 255, cast it to that &lt;code&gt;enum&lt;/code&gt; type, and get a “valid” value. When branching on an &lt;code&gt;enum&lt;/code&gt;, you need to keep in mind that you might get an undefined value, especially if it was deserialized from the wire.&lt;/p&gt;
&lt;p&gt;To add insult to injury, compilers won’t warn you by default. Only pattern matching does for C#. C++ can warn you if you don’t cover a defined &lt;code&gt;enum&lt;/code&gt; value in a switch statement, but you must not use the &lt;code&gt;default&lt;/code&gt; case — preventing you from correctly handling ill-formed values.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;

enum class Mode : char {
    Primary,
    Secondary
};

std::string to_string(Mode mode)
{   
    switch (mode)
    {
        case Mode::Primary: return &quot;Primary&quot;;
        // With -Wswitch-enum
        // warning: enumeration value 'Secondary' not handled in switch
    }
}

std::string to_string2(Mode mode)
{
    switch (mode)
    {
        case Mode::Primary: return &quot;Primary&quot;;
        // With -Wswitch-enum
        // no other warning
        default: return &quot;Undefined&quot;;
    }
}

std::string to_string3(Mode mode)
{
    switch (mode)
    {
        case Mode::Primary: return &quot;Primary&quot;;
        case Mode::Secondary: return &quot;Secondary&quot;;
        // With -Wswitch-enum
        // no other warning
    }
}

int main()
{
    auto&amp;amp;&amp;amp; str = to_string3(static_cast&amp;lt;Mode&amp;gt;(42));
    std::cout &amp;lt;&amp;lt; str &amp;lt;&amp;lt; std::endl; // Output is an undefined behaviour
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26340/tuesday-coding-tip-21-dangers-of-enums</guid>
<pubDate>Tue, 08 Sep 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>C++ Smart Pointers, unique_ptr vs shared_ptr vs weak_ptr — How to Choose</title>
<link>https://coderlegion.com/26260/c-smart-pointers-uniqueptr-vs-sharedptr-vs-weakptr-how-to-choose</link>
<description>&lt;p&gt;When I first learned about smart pointers, I was equally confused by all three of them! They are used to manage memory dynamically. &lt;code&gt;unique_ptr&lt;/code&gt; is like a master who is responsible for its pet's life. &lt;code&gt;shared_ptr&lt;/code&gt; is like you and your friends renting a house together. &lt;code&gt;weak_ptr&lt;/code&gt; is like a bystander who can only watch. This post explains what each one really does, with short examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick answer: use &lt;code&gt;unique_ptr&lt;/code&gt; by default. Use &lt;code&gt;shared_ptr&lt;/code&gt; only when ownership is truly shared. Use &lt;code&gt;weak_ptr&lt;/code&gt; when you need to observe without owning.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;unique_ptr          &lt;/th&gt;&lt;th&gt;shared_ptr                                         &lt;/th&gt;&lt;th&gt;weak_ptr&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ownership model             &lt;/td&gt;&lt;td&gt;exclusive ownership &lt;/td&gt;&lt;td&gt;shared ownership                                   &lt;/td&gt;&lt;td&gt;no ownership&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Can it be copied            &lt;/td&gt;&lt;td&gt;No                  &lt;/td&gt;&lt;td&gt;Yes                                                &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Can it have multiple owners &lt;/td&gt;&lt;td&gt;No                  &lt;/td&gt;&lt;td&gt;Yes                                                &lt;/td&gt;&lt;td&gt;No (it owns nothing)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reference counting overhead &lt;/td&gt;&lt;td&gt;none                &lt;/td&gt;&lt;td&gt;atomic count increase/decrease on copy and destroy &lt;/td&gt;&lt;td&gt;touches a separate weak count, never the strong count&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;How to choose               &lt;/td&gt;&lt;td&gt;the default choice  &lt;/td&gt;&lt;td&gt;when ownership is truly shared                     &lt;/td&gt;&lt;td&gt;to break circular references&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;unique-ptr&quot;&gt;Unique_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;unique_ptr&lt;/code&gt; means: &quot;I'm your master and responsible for your life and death.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;
using namespace std;

struct Task {
    int mId;
    Task(int id) :mId(id) {
        cout &amp;lt;&amp;lt; &quot;Task::Constructor&quot; &amp;lt;&amp;lt; endl;
    }
    ~Task() {
        cout &amp;lt;&amp;lt; &quot;Task::Destructor&quot; &amp;lt;&amp;lt; endl;
    }
};

int main()
{
    unique_ptr&amp;lt;Task&amp;gt; taskPtr(new Task(23));
    int id = taskPtr-&amp;gt;mId;

    cout &amp;lt;&amp;lt; id &amp;lt;&amp;lt; endl;

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Task::Constructor
23
Task::Destructor
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-unique-ptr-can-t-be-copied&quot;&gt;Why unique_ptr can't be copied&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;unique_ptr&lt;/code&gt; can't be copy-constructed or copy-assigned, because copying would let two &lt;code&gt;unique_ptr&lt;/code&gt;s point to the same object — and when both are destroyed, the object would be deleted twice. Instead, you use &lt;code&gt;std::move&lt;/code&gt; to transfer ownership.&lt;/p&gt;
&lt;p&gt;More on ownership transfer: see my earlier post, &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/25146/c-uniqueptr-reset-vs-release-vs-std-move&quot;&gt;reset() vs release() vs std::move&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;when-to-choose-unique-ptr&quot;&gt;When to choose unique_ptr&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unique_ptr&lt;/code&gt; prevents memory leaks caused by programming errors — the object is freed automatically when the pointer goes out of scope, even if you forget to &lt;code&gt;delete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ownership can be transferred cheaply with &lt;code&gt;std::move&lt;/code&gt;, without copying the managed object.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shared-ptr&quot;&gt;Shared_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt; means: the house is rented together by friends. When one friend moves out, the house still belongs to the rest — the object is destroyed only when the last owner releases it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class House {
public:
    House()  { std::cout &amp;lt;&amp;lt; &quot;That's a big house.&quot; &amp;lt;&amp;lt; std::endl; }
    ~House() { std::cout &amp;lt;&amp;lt; &quot;Bye&quot; &amp;lt;&amp;lt; std::endl; }
};

int main() {
    std::shared_ptr&amp;lt;House&amp;gt; friend1 = std::make_shared&amp;lt;House&amp;gt;();  // one allocation: object + control block
    std::shared_ptr&amp;lt;House&amp;gt; friend2 = friend1;  // a roommate moves in

    std::cout &amp;lt;&amp;lt; friend1.use_count() &amp;lt;&amp;lt; std::endl;  // 2
    std::cout &amp;lt;&amp;lt; friend2.use_count() &amp;lt;&amp;lt; std::endl;  // 2

    friend1.reset();  // a friend moves out
    std::cout &amp;lt;&amp;lt; friend2.use_count() &amp;lt;&amp;lt; std::endl;  // 1
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each &lt;code&gt;shared_ptr&lt;/code&gt; copy increments the reference count; each destruction or &lt;code&gt;reset()&lt;/code&gt; decrements it. When the count reaches zero, the object is destroyed.&lt;/p&gt;
&lt;h4 id=&quot;thread-safety-of-shared-ptr&quot;&gt;Thread safety of shared_ptr&lt;/h4&gt;
&lt;p&gt;The member functions of &lt;code&gt;shared_ptr&lt;/code&gt; (including the copy constructor and copy assignment operator) can be called on different &lt;code&gt;shared_ptr&lt;/code&gt; instances from multiple threads at the same time without any extra synchronization. However, if multiple threads access the same &lt;code&gt;shared_ptr&lt;/code&gt; instance and at least one of them calls a non-const member function, a data race occurs. To share the same instance safely, use &lt;code&gt;std::atomic&amp;lt;std::shared_ptr&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; (since C++20). In C++11/14/17, use the free functions &lt;code&gt;std::atomic_load()&lt;/code&gt; and &lt;code&gt;std::atomic_store()&lt;/code&gt; instead.&lt;/p&gt;
&lt;h4 id=&quot;the-trap-of-shared-ptr-circular-references&quot;&gt;The trap of shared_ptr: circular references&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt; has one famous trap: circular references. If two objects own each other with &lt;code&gt;shared_ptr&lt;/code&gt;, neither reference count can ever reach zero — the memory leaks even after you drop all external references. A doubly-linked structure shows the shape of the problem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;memory&amp;gt;

struct Node {
    std::shared_ptr&amp;lt;Node&amp;gt; next;
    std::weak_ptr&amp;lt;Node&amp;gt; prev;  // must NOT be shared_ptr — otherwise two nodes
                               // keep each other alive forever and leak
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;weak_ptr&lt;/code&gt; for the &quot;back&quot; direction. It breaks the cycle: the object is freed when no one strongly owns it, and the back-pointer simply expires.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;weak-ptr&quot;&gt;Weak_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;weak_ptr&lt;/code&gt; means: I'm a bystander. I can observe you, but I'm not responsible for you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class Object {
public:
    Object(int value) : data(value) {
        std::cout &amp;lt;&amp;lt; &quot;Object created with value: &quot; &amp;lt;&amp;lt; data &amp;lt;&amp;lt; std::endl;
    }
    ~Object() {
        std::cout &amp;lt;&amp;lt; &quot;Object destroyed with value: &quot; &amp;lt;&amp;lt; data &amp;lt;&amp;lt; std::endl;
    }
    int data;
};

int main() {
    std::shared_ptr&amp;lt;Object&amp;gt; sharedObjectA = std::make_shared&amp;lt;Object&amp;gt;(42);
    std::weak_ptr&amp;lt;Object&amp;gt; weakObjectA = sharedObjectA;

    if (auto sp = weakObjectA.lock()) {  // safe idiom: check and lock in one step
        std::cout &amp;lt;&amp;lt; &quot;Value: &quot; &amp;lt;&amp;lt; sp-&amp;gt;data &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;Object is gone&quot; &amp;lt;&amp;lt; std::endl;
    }

    sharedObjectA.reset();
    std::cout &amp;lt;&amp;lt; &quot;End of the Program&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;key-member-functions&quot;&gt;Key member functions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lock()&lt;/code&gt;: if the object is still alive, returns a temporary &lt;code&gt;shared_ptr&lt;/code&gt;; if it has been destroyed, returns an empty &lt;code&gt;shared_ptr&lt;/code&gt;. Always use &lt;code&gt;if (auto sp = w.lock())&lt;/code&gt; instead of calling &lt;code&gt;expired()&lt;/code&gt; and then &lt;code&gt;lock()&lt;/code&gt; separately — the object could be destroyed between the two calls.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expired()&lt;/code&gt;: checks whether the managed object has already been destroyed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reset()&lt;/code&gt;: releases the weak reference.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swap()&lt;/code&gt;: swaps the managed object with another &lt;code&gt;weak_ptr&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;application&quot;&gt;Application&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Breaking circular references&lt;/strong&gt;: when an object needs to reference another object without owning it — like &lt;code&gt;Node::prev&lt;/code&gt; above — use &lt;code&gt;weak_ptr&lt;/code&gt;. The cycle is broken, and both objects are safely released when no longer needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching systems&lt;/strong&gt;: caches often hold &lt;code&gt;weak_ptr&lt;/code&gt;s so they don't keep objects alive; when an object is no longer used anywhere else, the cache entry simply expires instead of leaking.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;q-amp-a&quot;&gt;Q&amp;amp;A&lt;/h3&gt;
&lt;p&gt;Q: &lt;strong&gt;Can a &lt;code&gt;unique_ptr&lt;/code&gt; be converted into a &lt;code&gt;shared_ptr&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: Yes, and it's the only &quot;copy-like&quot; conversion allowed — it actually moves the ownership: &lt;code&gt;std::shared_ptr&amp;lt;T&amp;gt; sp(std::move(up));&lt;/code&gt;. The reference count starts at 1 and nobody has to worry about double deletion.&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;Is &lt;code&gt;shared_ptr&lt;/code&gt; thread-safe?&lt;/strong&gt;&lt;br&gt;
A: Partly. The reference counting is atomic, so copies and destructions on different instances are safe. But the pointed-to object itself is not protected — if two threads read and write the object's data through their &lt;code&gt;shared_ptr&lt;/code&gt;s, you still need a mutex or an atomic.&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;Can I copy a &lt;code&gt;unique_ptr&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: No, by design — copying would cause the object to be deleted twice. If you really need to transfer it, &lt;code&gt;std::move&lt;/code&gt; it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reference&quot;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr&quot;&gt;std::unique_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/shared_ptr&quot;&gt;std::shared_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/weak_ptr&quot;&gt;std::weak_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26260/c-smart-pointers-uniqueptr-vs-sharedptr-vs-weakptr-how-to-choose</guid>
<pubDate>Sat, 05 Sep 2026 13:18:53 +0000</pubDate>
</item>
<item>
<title>C++ STL: map vs unordered_map — Key Differences and How to Choose</title>
<link>https://coderlegion.com/25167/c-stl-map-vs-unorderedmap-key-differences-and-how-to-choose</link>
<description>&lt;p&gt;For a long time I used &lt;code&gt;std::map&lt;/code&gt; and &lt;code&gt;std::unordered_map&lt;/code&gt; interchangeably, and honestly I couldn't tell you why I picked one over the other. When I finally sat down and learned how they work under the hood — a red-black tree vs a hash table — choosing became obvious. This is the note I wish I had when I started.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR: need keys in sorted order or range queries → &lt;code&gt;std::map&lt;/code&gt;. Just fast lookups → &lt;code&gt;std::unordered_map&lt;/code&gt;.&lt;/strong&gt; The rest of this post explains why.&lt;/p&gt;
&lt;h3 id=&quot;1-overview&quot;&gt;1. Overview&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;std::map&lt;/code&gt; and &lt;code&gt;std::unordered_map&lt;/code&gt; are containers used to store &lt;code&gt;{key, value}&lt;/code&gt; pairs, and provide efficient insert, search, and delete operations.&lt;/p&gt;
&lt;p&gt;The key difference: &lt;code&gt;std::map&lt;/code&gt; keeps its elements &lt;strong&gt;sorted by key&lt;/strong&gt;, while &lt;code&gt;std::unordered_map&lt;/code&gt; makes &lt;strong&gt;no guarantee about order&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compare &lt;code&gt;std::map&lt;/code&gt; and &lt;code&gt;std::unordered_map&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;map&lt;/code&gt;                                     &lt;/th&gt;&lt;th&gt;&lt;code&gt;unordered_map&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Time complexity           &lt;/td&gt;&lt;td&gt;O(log N)                                  &lt;/td&gt;&lt;td&gt;Average O(1), worst O(N)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ordering                  &lt;/td&gt;&lt;td&gt;sorted by key                             &lt;/td&gt;&lt;td&gt;no guaranteed order&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Underlying implementation &lt;/td&gt;&lt;td&gt;Red-black tree                            &lt;/td&gt;&lt;td&gt;Hash table&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Key requirements          &lt;/td&gt;&lt;td&gt;must support &lt;code&gt;&amp;lt;&lt;/code&gt; (or a custom comparator) &lt;/td&gt;&lt;td&gt;must support &lt;code&gt;==&lt;/code&gt; and be hashable&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;How to choose&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;map&lt;/code&gt; when you need to traverse in key order, do range queries (&lt;code&gt;lower_bound&lt;/code&gt;/&lt;code&gt;upper_bound&lt;/code&gt;), or when the key type doesn't have a good hash function.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;unordered_map&lt;/code&gt; if you just need point lookups, frequent inserts, a large number of elements, and the hash distribution is uniform.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-space-complexity&quot;&gt;2. Space complexity&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;std::unordered_map&lt;/code&gt; spends extra memory on its bucket array, and it keeps the table less than fully packed (that's the load factor at work), so it typically uses more memory than &lt;code&gt;std::map&lt;/code&gt;. As you insert more elements, the table rehashes and the bucket array keeps growing.&lt;/p&gt;
&lt;h3 id=&quot;3-requirements-on-the-key&quot;&gt;3. Requirements on the key&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;std::map&lt;/code&gt; stores its elements in a red-black tree, which requires comparing keys with &lt;code&gt;&amp;lt;&lt;/code&gt;. The standard library uses &lt;code&gt;operator&amp;lt;&lt;/code&gt; by default, but you can provide a custom comparator instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example — &lt;code&gt;Person&lt;/code&gt; as a key of &lt;code&gt;std::map&lt;/code&gt;, with a custom comparator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;

struct Person {
    std::string name;
    int age;

    friend std::ostream&amp;amp; operator&amp;lt;&amp;lt;(std::ostream&amp;amp; os, const Person&amp;amp; p) {
        return os &amp;lt;&amp;lt; &quot;(&quot; &amp;lt;&amp;lt; p.name &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; p.age &amp;lt;&amp;lt; &quot;)&quot;;
    }
};

struct PersonCompare {
    bool operator()(const Person&amp;amp; lhs, const Person&amp;amp; rhs) const {
        if (lhs.age != rhs.age) { return lhs.age &amp;lt; rhs.age; }
        return lhs.name &amp;lt; rhs.name;
    }
};

int main() {
    std::map&amp;lt;Person, std::string, PersonCompare&amp;gt; myMap;
    myMap[{&quot;Alice&quot;, 25}]   = &quot;Engineer&quot;;
    myMap[{&quot;Bob&quot;, 20}]     = &quot;Student&quot;;
    myMap[{&quot;Charlie&quot;, 25}] = &quot;Designer&quot;;

    for (const auto&amp;amp; [key, value] : myMap) {
        std::cout &amp;lt;&amp;lt; key &amp;lt;&amp;lt; &quot; -&amp;gt; &quot; &amp;lt;&amp;lt; value &amp;lt;&amp;lt; std::endl;
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it and notice: the output is strictly sorted by &lt;code&gt;(age, name)&lt;/code&gt; — that's the red-black tree at work.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;std::unordered_map&lt;/code&gt; needs a hash function to decide which bucket each key goes into. Because different keys can end up with the same hash (a collision), it also needs &lt;code&gt;==&lt;/code&gt; to tell keys apart.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And the same &lt;code&gt;Person&lt;/code&gt; as a key of &lt;code&gt;std::unordered_map&lt;/code&gt; — note the &lt;strong&gt;two&lt;/strong&gt; extra template parameters (hash + equality) instead of one comparator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;string&amp;gt;

struct Person {
    std::string name;
    int age;

    friend std::ostream&amp;amp; operator&amp;lt;&amp;lt;(std::ostream&amp;amp; os, const Person&amp;amp; p) {
        return os &amp;lt;&amp;lt; &quot;(&quot; &amp;lt;&amp;lt; p.name &amp;lt;&amp;lt; &quot;, &quot; &amp;lt;&amp;lt; p.age &amp;lt;&amp;lt; &quot;)&quot;;
    }
};

struct PersonHash {
    std::size_t operator()(const Person&amp;amp; p) const noexcept {
        return std::hash&amp;lt;std::string&amp;gt;{}(p.name) ^ (std::hash&amp;lt;int&amp;gt;{}(p.age) &amp;lt;&amp;lt; 1);
    }
};

struct PersonEqual {
    bool operator()(const Person&amp;amp; lhs, const Person&amp;amp; rhs) const noexcept {
        return lhs.age == rhs.age &amp;amp;&amp;amp; lhs.name == rhs.name;
    }
};

int main() {
    std::unordered_map&amp;lt;Person, std::string, PersonHash, PersonEqual&amp;gt; myMap;
    myMap[{&quot;Alice&quot;, 25}]   = &quot;Engineer&quot;;
    myMap[{&quot;Bob&quot;, 20}]     = &quot;Student&quot;;
    myMap[{&quot;Charlie&quot;, 25}] = &quot;Designer&quot;;

    for (const auto&amp;amp; [key, value] : myMap) {
        std::cout &amp;lt;&amp;lt; key &amp;lt;&amp;lt; &quot; -&amp;gt; &quot; &amp;lt;&amp;lt; value &amp;lt;&amp;lt; std::endl;
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run this one too: the output order will be different — and not sorted. That single difference in output is the &quot;Ordering&quot; row of the table above, live.&lt;/p&gt;
&lt;p&gt;Two details worth noticing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;std::hash&amp;lt;std::string&amp;gt;{}(p.name) ^ (std::hash&amp;lt;int&amp;gt;{}(p.age) &amp;lt;&amp;lt; 1)&lt;/code&gt; combines the two field hashes with XOR; the &lt;code&gt;&amp;lt;&amp;lt; 1&lt;/code&gt; shift keeps &lt;code&gt;(name, age)&lt;/code&gt; pairs from hashing symmetrically.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PersonEqual&lt;/code&gt; exists because of collisions: two different &lt;code&gt;Person&lt;/code&gt;s can land in the same bucket, and &lt;code&gt;==&lt;/code&gt; is what tells them apart — exactly the chaining mechanism in the Q&amp;amp;A below.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;4-q-amp-a&quot;&gt;4. Q&amp;amp;A&lt;/h3&gt;
&lt;p&gt;Q: &lt;strong&gt;How does &lt;code&gt;unordered_map&lt;/code&gt; deal with hash collisions?&lt;/strong&gt;&lt;br&gt;
A: C++ STL uses chaining — elements in the same bucket are linked into a list. When inserting, you attach to the end of the chain; when searching, you first use the hash to find the bucket, then traverse the list comparing keys. The more collisions there are, the longer the chain, and the worse the performance.&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;Why is the worst case O(n)?&lt;/strong&gt;&lt;br&gt;
A: In the extreme case where all keys hash to the same bucket, the whole table degenerates into a linked list, and searching turns into a linear scan.&lt;/p&gt;
&lt;h3 id=&quot;reference-1&quot;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/container/map&quot;&gt;std::map - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/container/unordered_map&quot;&gt;std::unordered_map - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25167/c-stl-map-vs-unorderedmap-key-differences-and-how-to-choose</guid>
<pubDate>Sun, 23 Aug 2026 10:23:17 +0000</pubDate>
</item>
<item>
<title>C++ unique_ptr: reset() vs release() vs std::move</title>
<link>https://coderlegion.com/25146/c-uniqueptr-reset-vs-release-vs-std-move</link>
<description>&lt;p&gt;When you first learn &lt;code&gt;std::unique_ptr&lt;/code&gt;, three things confuse almost everyone: &lt;code&gt;release()&lt;/code&gt;, &lt;code&gt;reset()&lt;/code&gt;, and &lt;code&gt;std::move&lt;/code&gt;. They all look like they &quot;give something up&quot;, but they do very different things. This post explains what each one really does, with short examples.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-the-key-to-understanding-release-&quot;&gt;1. The key to understanding &lt;code&gt;release()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;release()&lt;/code&gt; means: &quot;I don't want to manage this object anymore.&quot;&lt;/p&gt;
&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; delete the object. It just gives up ownership and hands the raw pointer back to you. After the call, the smart pointer is empty (&lt;code&gt;get()&lt;/code&gt; returns &lt;code&gt;nullptr&lt;/code&gt;), and &lt;strong&gt;you&lt;/strong&gt; are now responsible for deleting the object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;br&gt;
(None)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Return value&lt;/strong&gt;&lt;br&gt;
The raw pointer to the managed object, or &lt;code&gt;nullptr&lt;/code&gt; if there was no managed object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;memory&amp;gt;
#include &amp;lt;iostream&amp;gt;

int main(){
std::unique_ptr&amp;lt;int&amp;gt; ptr = std::make_unique&amp;lt;int&amp;gt;(100);
int* raw = ptr.release(); // ptr gives up ownership, ptr is empty now
if(!ptr)
std::cout&amp;lt;&amp;lt; *raw &amp;lt;&amp;lt; std::endl;
delete raw; // we must delete raw manually
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Warning: &lt;code&gt;release()&lt;/code&gt; is rarely what you want:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you forget to capture the returned raw pointer, the object leaks immediately.&lt;/li&gt;
&lt;li&gt;After taking the raw pointer, you must remember to &lt;code&gt;delete&lt;/code&gt; it yourself — you are back to manual raw-pointer management.&lt;/li&gt;
&lt;li&gt;The one legitimate use: handing the object to a legacy C API that requires a raw pointer and promises to take ownership.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-the-right-way-to-use-reset-&quot;&gt;2. The right way to use &lt;code&gt;reset()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;reset()&lt;/code&gt; replaces the managed object.&lt;/p&gt;
&lt;p&gt;Called with no argument, &lt;code&gt;ptr.reset()&lt;/code&gt; deletes the object it owns and becomes empty. If you pass in a new pointer, the old object is destroyed first, and the smart pointer then takes ownership of the new one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ptr&lt;/code&gt; - (optional) pointer to a new object to manage&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;memory&amp;gt;
#include &amp;lt;iostream&amp;gt;

int main(){
std::unique_ptr&amp;lt;int&amp;gt; ptr = std::make_unique&amp;lt;int&amp;gt;(100);

ptr.reset();  // delete the original object, ptr is empty.

ptr.reset(new int(10)); // delete the old object, manage the new pointer.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-comparing-release-and-reset-&quot;&gt;3. Comparing &lt;code&gt;release()&lt;/code&gt; and &lt;code&gt;reset()&lt;/code&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;release()&lt;/code&gt;: does not delete the old object — it hands the raw pointer back to you&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reset()&lt;/code&gt;: deletes the old object (if any), then manages the new pointer you pass in&lt;/li&gt;
&lt;li&gt;Both make the smart pointer lose control of the old resource; the difference is who deletes the object and when&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;release() &lt;/th&gt;&lt;th&gt;reset()&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Old object destroyed? &lt;/td&gt;&lt;td&gt;No — you must delete it yourself &lt;/td&gt;&lt;td&gt;Yes, immediately&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Smart pointer after call &lt;/td&gt;&lt;td&gt;empty &lt;/td&gt;&lt;td&gt;manages the new pointer (or nothing)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Returns &lt;/td&gt;&lt;td&gt;the raw pointer &lt;/td&gt;&lt;td&gt;nothing&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-how-std-move-fits-in&quot;&gt;4. How &lt;code&gt;std::move&lt;/code&gt; fits in&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;std::move&lt;/code&gt; does not move anything by itself. It only marks an object as &quot;moved-from&quot; — a permission slip saying &quot;you may take this object's resources instead of copying them&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ob&lt;/code&gt; - the object to be moved&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class MyClass{
std::string data;
public:
MyClass(std::string&amp;amp;&amp;amp; str) : data(std::move(str)) {}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;std::vector&amp;lt;std::string&amp;gt; v;
std::string temp = &quot;example&quot;;
v.push_back(std::move(temp));
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;memory&amp;gt;
#include &amp;lt;iostream&amp;gt;

int main(){
std::unique_ptr&amp;lt;int&amp;gt; ptr1(new int(10));
std::unique_ptr&amp;lt;int&amp;gt; ptr2 = std::move(ptr1);
if(!ptr1)
std::cout&amp;lt;&amp;lt; &quot;ptr1 is empty&quot; &amp;lt;&amp;lt; std::endl;
if(ptr2)
std::cout&amp;lt;&amp;lt; &quot;ptr2 owns the resource and value is: &quot;&amp;lt;&amp;lt;*ptr2 &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why is &lt;code&gt;std::move&lt;/code&gt; so common with smart pointers?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;std::unique_ptr&lt;/code&gt; has exclusive ownership: it cannot be copied, because two owners would delete the same object twice. So the only way to transfer ownership between &lt;code&gt;unique_ptr&lt;/code&gt;s is &lt;code&gt;std::move&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr/release&quot;&gt;std::unique_ptr::release — cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr/reset&quot;&gt;std::unique_ptr::reset — cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/utility/move&quot;&gt;std::move — cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25146/c-uniqueptr-reset-vs-release-vs-std-move</guid>
<pubDate>Sat, 22 Aug 2026 15:48:55 +0000</pubDate>
</item>
<item>
<title>Case label in switch statement must be constants only</title>
<link>https://coderlegion.com/23983/case-label-in-switch-statement-must-be-constants-only</link>
<description>&lt;p&gt;The switch statement is a powerful control flow construct in C++, allowing for efficient execution of code based on the value of a given expression. One of the key features of the switch statement is the use of case labels to determine which block of code should be executed. However, there are certain rules that must be followed when using case labels in C++, specifically, that &lt;span style=&quot;color: #ff04fc;&quot;&gt;the case label must be a constant expression&lt;/span&gt;. This article will delve into the details of this rule, explaining why it exists and how it can be worked around in certain situations. We will also discuss the implications of this rule on the usage of switch statement and the best practices to follow. With this understanding, you will be able to effectively utilize the switch statement in your C++ code and write efficient, maintainable code.
&lt;/p&gt;&lt;hr&gt;
&lt;div&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt; Table of Contents:&lt;a class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#switch-statement-and-its-uses.&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Switch statement and its uses.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#why-case-label-must-be-a-constant-expression.&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Why case label must be a constant expression.&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#conclusion&quot;&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Switch statement and its uses.&lt;/span&gt;&lt;a id=&quot;switch-statement-and-its-uses.&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A switch statement is a control flow construct that allows for efficient execution of code based on the value of a given expression. The switch statement is used to test the value of a variable against a list of possible cases, and then execute a block of code corresponding to the first matching case. This can be useful in a variety of situations, such as:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Menu-driven Programs: The switch statement can be used to create a simple menu-driven program, where the user is presented with a list of options and the program takes an action based on the user's choice.&lt;/li&gt;&lt;li&gt;Handling Multiple Conditions: The switch statement can be used to handle multiple conditions in a more concise way. Instead of using multiple if-else statements, a switch statement allows for the same functionality with less code.&lt;/li&gt;&lt;li&gt;Enumerated Types: The switch statement is often used in conjunction with enumerated types, which are a way to define a set of named values. The cases in a switch statement can match the enumerated values, making the code more readable and maintainable.&lt;/li&gt;&lt;li&gt;Error Handling: The switch statement can be used to handle errors in a program by testing the value of an error code and taking appropriate action.&lt;/li&gt;&lt;li&gt;State machines: The switch statement is often used to implement state machines, a design pattern used to handle the various states of a program or system.&lt;/li&gt;&lt;li&gt;Network protocol handling: Switch statement can be used to handle different types of packets, messages or protocols in network communication.&lt;/li&gt;&lt;/ul&gt;
&lt;div class=&quot;div-purple&quot;&gt;
&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt;
&lt;span class=&quot;alert-body&quot;&gt;It's worth noting that although switch statement is quite efficient in dealing with multiple conditions and handling them, it's not always the best option when it comes to complex conditions and nested statements. In such situations, a combination of if-else statements and other constructs such as function calls, might be more appropriate.&lt;/span&gt;
&lt;/div&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Why case label must be a constant expression.&lt;/span&gt;&lt;a id=&quot;why-case-label-must-be-a-constant-expression.&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In C++, &lt;span style=&quot;color: #ff04fc;&quot;&gt;the case label in a switch statement must be a constant expression&lt;/span&gt;. This means that the value must be known at compile-time and cannot be the result of a function call or an expression that involves a variable. This restriction is in place because the switch statement uses a jump table to determine which case to execute, and the jump table must be generated at compile-time.&lt;/p&gt;
&lt;p&gt;One example of a valid case label in a switch statement is a constant integer, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
switch (x) {
    case 0:
        // code to execute when x is 0
        break;
    case 1:
        // code to execute when x is 1
        break;
    // etc.
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of an invalid case label would be a variable, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
int y = 5;
switch (x) {
    case y: // error, y is not a constant expression
        // code to execute when x is y
        break;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, in C++11, a new feature called &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;constexpr&lt;/code&gt;&lt;/span&gt; was introduced, which allows variables and functions to be used in constant expressions. This means that in C++11 and later, it is possible to use a variable as a case label in a switch statement, as long as it is declared as &quot;constexpr&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;
constexpr int y = 5;
switch (x) {
    case y: // valid in C++11 and later
        // code to execute when x is y
        break;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is worth mentioning that the variable used in the switch statement must be an integral type(char, int, etc.) or an enumeration type.&lt;/p&gt;
&lt;p&gt;This restriction on the case label in a switch statement is specified in the C++ standard. &lt;/p&gt;
&lt;div class=&quot;div-faq&quot;&gt;
&lt;span class=&quot;faq-header&quot;&gt;What is the difference between switch statement and if-else statement?&lt;/span&gt;
&lt;span class=&quot;faq-body&quot;&gt;The main difference between &lt;span style=&quot;color: #ff04fc;&quot;&gt;`switch`&lt;/span&gt; statement and &lt;span style=&quot;color: #ff04fc;&quot;&gt;`if-else`&lt;/span&gt; statement is the way they test conditions. A switch statement tests for equality between the value of an expression and a list of possible cases, while an if-else statement tests for a Boolean condition. Switch statement is more efficient when handling multiple conditions with the same variable, while if-else statement is better suited for more complex conditions and nested statements.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;In addition to the constness of the case label, it's also important to note that the range of the case label should be considered when using switch statement. In C++ standard, it's not guaranteed that a case label outside the range of the switch statement's controlling expression will be diagnosed.&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family:arial,helvetica,sans-serif;&quot;&gt;Conclusion&lt;/span&gt;&lt;a class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In conclusion, &lt;span style=&quot;color: #ff04fc;&quot;&gt;the case label in a switch statement must be a constant expression&lt;/span&gt; in C++. This restriction ensures that the jump table used by the switch statement can be generated at compile-time. However, this restriction is relaxed in C++11 with the introduction of &lt;span style=&quot;color: #ff04fc;&quot;&gt;&lt;code&gt;constexpr&lt;/code&gt;&lt;/span&gt; feature, which allows variables and functions to be used as case labels in switch statement as long as they are declared as constexpr. It's also important to take into account the range of the case label when using switch statement.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23983/case-label-in-switch-statement-must-be-constants-only</guid>
<pubDate>Sun, 02 Aug 2026 07:23:56 +0000</pubDate>
</item>
<item>
<title>Raw Pointers VS Smart Pointers, Which One Should Beginners Learn First</title>
<link>https://coderlegion.com/23675/raw-pointers-vs-smart-pointers-which-one-should-beginners-learn-first</link>
<description>&lt;p&gt;These days, I've been learning C++ and have posted some articles about it. Since I started to learn C++ with raw pointers, I naturally hit some common questions: &lt;code&gt;new/delete&lt;/code&gt;, manual memory manage, and so on.&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/23326/c-traps-that-caught-me-off-guard-a-beginners-log&quot;&gt;C++ Traps That Caught Me Off Guard — A Beginner's Log - Coder Legion&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A few days ago, I discussed this topic with some C++ developers. As far as I'm concerned, I believe that &lt;strong&gt;raw pointers are fundamental and it's necessary for beginners to build a mental model of RAII and memory management before moving to smart pointers&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;On the other hand, some developers think that &lt;strong&gt;raw pointers are not a suitable topic for beginners - we have the safer and more modern smart pointers to manage, so why not start with them directly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Actually, it's up for debate whether beginners should learn these constructs. From my own experience learning C++ through raw pointers, I believe beginners can benefit from a moderate understanding of them. &lt;/p&gt;
&lt;p&gt;As for performance, there's no absolute winner. Smart pointers are safer and work well for most scenarios, especially when code maintainability matters. Raw pointers still have their place in performance-critical or low-level contexts. The key is knowing both and choosing wisely.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Which one would you learn first if you are a beginner? And why?&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23675/raw-pointers-vs-smart-pointers-which-one-should-beginners-learn-first</guid>
<pubDate>Tue, 28 Jul 2026 14:07:37 +0000</pubDate>
</item>
<item>
<title>C++ Traps That Caught Me Off Guard — A Beginner's Log</title>
<link>https://coderlegion.com/23326/c-traps-that-caught-me-off-guard-a-beginners-log</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Learning C++ has been a journey full of traps. Here are three I hit — hope they save you some debugging time.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;-trap-1-new-delete-instead-of-delete-&quot;&gt;🎣 Trap 1: &lt;code&gt;new[]&lt;/code&gt; + &lt;code&gt;delete&lt;/code&gt;(instead of &lt;code&gt;delete[]&lt;/code&gt;)&lt;/h3&gt;
&lt;h4 id=&quot;wrong-code&quot;&gt;Wrong Code&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt; int* a = new int[3];
 
 // delete a; ❌ --&amp;gt; undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class MyClass{
public:
~MyClass(){
std::cout&amp;lt;&amp;lt;&quot;delete successfully&quot;&amp;lt;&amp;lt;std::endl;
}
}
int main(){
MyClass* mc = new MyClass[2];
//delete mc;  ❌ -&amp;gt; only outputs one &quot;delete successfully&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-happens&quot;&gt;What Happens&lt;/h4&gt;
&lt;p&gt;C++ standard clearly states that this is &lt;strong&gt;undefined behavior&lt;/strong&gt;. Relying on it is extremely dangerous — different compilers, optimization levels, and platforms may behave completely differently.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;new[]&lt;/code&gt; + &lt;code&gt;delete&lt;/code&gt; only calls the destructor once, while &lt;code&gt;delete[]&lt;/code&gt; calls it for every element in the array. Using &lt;code&gt;delete&lt;/code&gt; instead of &lt;code&gt;delete[]&lt;/code&gt; causes resource leaks for the remaining elements. &lt;/p&gt;
&lt;h4 id=&quot;how-to-fix&quot;&gt;How to Fix&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt; int* a = new int[3];
 
 delete[] a;  -&amp;gt; ✔
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class MyClass{
public:
~MyClass(){
std::cout&amp;lt;&amp;lt;&quot;delete sucessfully&quot;&amp;lt;&amp;lt;std::endl;
}
};
int main(){
MyClass* mc = new MyClass[2];
delete[] mc; -&amp;gt; ✔ outputs &quot;delete successfully&quot; twice
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-trap-2-cin-getline-eating-your-input&quot;&gt;🎣 Trap 2: &lt;code&gt;cin&lt;/code&gt; + &lt;code&gt;getline&lt;/code&gt; eating your input&lt;/h3&gt;
&lt;h4 id=&quot;wrong-code-1&quot;&gt;Wrong Code&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int age;
std::string name;

std::cout&amp;lt;&amp;lt;&quot;Enter age: &quot;;
std::cin&amp;gt;&amp;gt;age;

std::cout&amp;lt;&amp;lt;&quot;Enter name: &quot;;
std::getline(cin, name); // ❌ name ends up empty!

std::cout&amp;lt;&amp;lt;name&amp;lt;&amp;lt;std::endl;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-happens-1&quot;&gt;What Happens&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;cin&lt;/code&gt; leaves a newline character (&lt;code&gt;\n&lt;/code&gt;) in the input buffer, and &lt;code&gt;getline&lt;/code&gt; reads it immediately — so it stops before you even type anything.&lt;/p&gt;
&lt;h4 id=&quot;why&quot;&gt;Why&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;cin &amp;gt;&amp;gt;&lt;/code&gt; reads the value you type but leaves &lt;code&gt;\n&lt;/code&gt; in the buffer. &lt;code&gt;getline&lt;/code&gt; stops reading when it encounters &lt;code&gt;\n&lt;/code&gt;, so it grabs that leftover newline and returns an empty string.&lt;/p&gt;
&lt;h4 id=&quot;how-to-fix-1&quot;&gt;How to Fix&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;std::cin &amp;gt;&amp;gt; age;
cin.ignore();    // 👈 Eat the leftover newline
std::getline(cin, name);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-trap-3-copy-constructor-vs-copy-assignment&quot;&gt;🎣 Trap 3: &lt;code&gt;Copy constructor&lt;/code&gt; vs &lt;code&gt;copy assignment&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class FixArray{
    std::string name_;
    int* data_;
    size_t size_;
public:
~FixArray(){
        delete[] data_;
    }

    FixArray(const FixArray&amp;amp; other) : FixArray(other.size_, other.name_){
        std::copy(other.data_, other.data_ + size_, data_);
    }

    FixArray&amp;amp; operator=(const FixArray&amp;amp; other){
        if(this != &amp;amp;other){
            int* new_data = new int[other.size_];
            std::copy(other.data_, other.data_ + other.size_, new_data);
            delete[] data_;
            data_ = new_data;
            size_ = other.size_;
            name_ = other.name_;
        }
        return *this;
    } 
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Many beginners can't tell the difference between a &lt;strong&gt;copy constructor&lt;/strong&gt; and a &lt;strong&gt;copy assignment&lt;/strong&gt;. Actually, it's quite simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Copy constructor: creating a new object from an existing one
FixArray fa(10, &quot;example&quot;);
FixArray fb = fa;    // 👈 copy constructor called

// Copy assignment: assigning to an already-existing object
FixArray fc(5, &quot;other&quot;);
fc = fa;            // 👈 copy assignment called
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Copy constructor&lt;/strong&gt; — creates a brand new object as a copy of an existing one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Copy assignment&lt;/strong&gt; — replaces the contents of an already-existing object with another object's data.&lt;/p&gt;
&lt;p&gt;The key difference: &lt;strong&gt;copy constructor builds something from nothing; copy assignment replaces something that already exists.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;gt;&lt;em&gt;These are three traps I hit while learning C++. If you're a beginner too, I hope this saves you some debugging time. Have you encountered other tricky C++ traps? Share them below!&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23326/c-traps-that-caught-me-off-guard-a-beginners-log</guid>
<pubDate>Thu, 23 Jul 2026 09:28:37 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 20 — Be `explicit`!</title>
<link>https://coderlegion.com/21475/tuesday-coding-tip-20-be-explicit</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you choose to add a conversion operator for your class in C++, be sure to mark it as &lt;code&gt;explicit&lt;/code&gt; or you can get nasty side-effects that will be hard to debug. For example, having an implicit conversion operator to bool will make the operator less-than “work” for your class, meaning that class can be used as a key for &lt;code&gt;std::map&lt;/code&gt; and suddenly, all hell breaks loose.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

class A {
public:
    operator bool() {
        return true;
    }
};

class B {
public:
    explicit operator bool() {
        return true;
    }
};

int main() {
    // This is ok
    std::cout &amp;lt;&amp;lt; (A() &amp;lt; A()) &amp;lt;&amp;lt; std::endl;
    A a;
    if (a); // implicit conversion allowed

    // Error: error: invalid operands to binary expression
    std::cout &amp;lt;&amp;lt; (B() &amp;lt; B()) &amp;lt;&amp;lt; std::endl;
    B b;
    if (bool(b)); // explicit conversion required

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, it is good practice to mark single parameter constructors &lt;code&gt;explicit&lt;/code&gt; to prevent implicit conversions from trivial data types or to possibly track places where your object is copied.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A {
public:
    A(int) {}
    A(const A&amp;amp;) {}
};

class B {
public:
    explicit B(int) {}
    explicit B(const B&amp;amp;) {}
};

int main() {
    A a1 = 10; // ok
    A a2 = { 10 }; // ok
    A a3 = a1; // ok

    B b1 = 10; // error: no viable conversion, explicit constructor is not a candidate
    B b2 = { 10 }; // error
    B b3 = B{10}; // ok
    B b4 = b1; // error
    B b5 = B{b1}; // ok

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21475/tuesday-coding-tip-20-be-explicit</guid>
<pubDate>Tue, 21 Jul 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 19 — [[nodiscard]] annotation</title>
<link>https://coderlegion.com/21474/tuesday-coding-tip-19-nodiscard-annotation</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Since C++17, one can use &lt;code&gt;[[nodiscard]]&lt;/code&gt; annotation to mark API functions whose return values should not be discarded without consideration. This usually applies to methods that make no sense to call without evaluating their return value (like predicate functions such as &lt;code&gt;empty&lt;/code&gt; or a factory function). The compiler will issue a warning if the return value is not handled.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class [[nodiscard]] A {};

// OR

class B {
public:
    [[nodiscard]] B() = default;
};

// Enums can be nodiscard too
enum class [[nodiscard]] State {
    Default,
    Foo,
    Bar
};

int main()
{
    // warning: ignoring temporary created by a constructor declared with 'nodiscard' attribute
    A();
    B();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But beware! Sometimes it might be tempting to return some scope guard that will maintain postconditions automatically at the end of the scope using RAII (such as committing transactions). However, a user of your API can pipe the result of your &lt;code&gt;nodiscard&lt;/code&gt; function into a &lt;code&gt;std::ignore&lt;/code&gt; helper which causes the temporary to destruct immediately, defeating the idea of end-of-scope cleanup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;tuple&amp;gt; // std::ignore

class [[nodiscard]] A {};
class B {
public:
    [[nodiscard]] bool isOk() const { return true; }
};

// no need for nodiscard, A is nodiscard
A foo() {
    return A();
}

// B is not nodiscard, mark it explicitly
[[nodiscard]] B bar() {
    return B();
}

int main()
{    
    foo(); // warning: ignoring temporary created by a constructor declared with 'nodiscard' attribute
    bar(); // warning

    std::ignore = foo(); // ok
    auto b = bar(); // ok
    b.isOk(); // warning
    if (b.isOk()) {} // ok
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21474/tuesday-coding-tip-19-nodiscard-annotation</guid>
<pubDate>Tue, 14 Jul 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Building a Custom Terminal from Scratch: Devlog #1</title>
<link>https://coderlegion.com/22257/building-a-custom-terminal-from-scratch-devlog-1</link>
<description>&lt;h3 id=&quot;purpose&quot;&gt;Purpose&lt;/h3&gt;
&lt;p&gt;I recently got interested in low-level programming and microcontrollers. For the purpose of exploring the area and gaining some experience, I started building my own bash-like terminal. The language of my choosing—unsurprisingly—was C++, as it is commonly used in low-level systems. &lt;/p&gt;
&lt;p&gt;I made specific design choices and trade-offs to overcome challenges, which I am going to share in the next few devlogs.&lt;/p&gt;
&lt;h3 id=&quot;why-did-i-ditch-strings-for-raw-char-pointers-&quot;&gt;Why did I ditch strings for raw char pointers?&lt;/h3&gt;
&lt;p&gt;My first task was seemingly simple: separate the input into an array. &lt;/p&gt;
&lt;p&gt;Normally, I would have just used a built-in function to split the input. However, since I wanted this to be a challenge that would push me to think at a low level, I decided to opt out of built-in methods. In fact, throughout the entire process, I actively dodged the C++ libraries designed to make a dev’s life easier.&lt;/p&gt;
&lt;p&gt;Going back to the question, I first need to explain the difference between strings and char pointers in C++. If you have any experience with programming languages, you already know the string, or text, type. A string is actually just a list of characters, and that is how it is stored under the hood. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;std::string&lt;/code&gt; type is just a wrapper around this char list. In fact, C doesn’t even have a native &quot;string&quot; type. Instead, to handle text, you need to define a char array. This is how software works fundamentally, and I went a step further with this approach by using char pointers. &lt;/p&gt;
&lt;p&gt;Char pointers just store the memory address of this char list, which is manually allocated by the developer. But that brings up a problem: whenever the char pointer goes out of scope or is destroyed, it only frees the memory space allocated to store the &lt;em&gt;address&lt;/em&gt; of the array. The array itself still exists in memory, which causes a memory leak. Because of this, I have to manually call &lt;code&gt;free()&lt;/code&gt; (or &lt;code&gt;delete[]&lt;/code&gt;) to empty that allocated memory. &lt;/p&gt;
&lt;p&gt;I just thought managing this myself would be a great way to better understand how computers work, even though the standard string type normally handles this automatically.&lt;/p&gt;
&lt;h3 id=&quot;what-about-scalability-&quot;&gt;What about scalability?&lt;/h3&gt;
&lt;p&gt;Soon, I noticed a major issue. Creating these char pointers in various locations throughout the code felt way too disorganized. I also needed to keep track of the maximum size I allocated for each char pointer to hold, since they don't have a &lt;code&gt;size()&lt;/code&gt; function. If that allocated space is exceeded, it can create troublesome, hard-to-debug errors. You are basically writing into an unknown space in memory, which is like playing with fire.&lt;/p&gt;
&lt;p&gt;My solution was to create a &lt;code&gt;carr&lt;/code&gt; (char array) struct, and equip it with functions to expand, delete, and initialize itself. &lt;/p&gt;
&lt;p&gt;I also overloaded some operators, such as &lt;code&gt;=&lt;/code&gt;, to directly assign these char pointers. Overloading is simply changing the behavior of an operator for the interactions of that specific struct or class.&lt;/p&gt;
&lt;h3 id=&quot;a-small-touch&quot;&gt;A small touch&lt;/h3&gt;
&lt;p&gt;It is true that I wanted to create a kind of bash clone, but I still didn't want a one-to-one copy. So, I decided to create my own command names. &lt;/p&gt;
&lt;p&gt;That is why when I coded the features to get the current directory and move between directories, I chose &quot;here&quot; and &quot;go&quot; rather than &quot;pwd&quot; and &quot;cd.&quot; It was just a little flavor I wanted to add. &lt;/p&gt;
&lt;p&gt;Of course, when I was coding those commands, I still avoided using standard modern libraries like &lt;code&gt;&amp;lt;filesystem&amp;gt;&lt;/code&gt; and went for &lt;code&gt;&amp;lt;dirent.h&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;unistd.h&amp;gt;&lt;/code&gt;. They were a bit harder to use, but they gave me the exact low-level systems exposure I was looking for.&lt;/p&gt;
&lt;h3 id=&quot;the-switch&quot;&gt;The switch&lt;/h3&gt;
&lt;p&gt;So far, I have written about how I tried to avoid C++ features and chose C-style alternatives instead. Well, that begs the question: why not just switch to C entirely? &lt;/p&gt;
&lt;p&gt;My initial reasoning for using C++ was simply because I didn’t know C. However, this project actually created a huge opportunity for me to finally learn C, and I took it. &lt;/p&gt;
&lt;p&gt;I thought to myself, &quot;How hard could it be since I already coded it in a C-like style?&quot; Well, it turned out to be quite a challenge. &lt;/p&gt;
&lt;p&gt;First of all, remember how I added functions to my &lt;code&gt;carr&lt;/code&gt; struct and overloaded its operators? Well, that is not possible in C. Because C doesn't support member functions or operator overloading, I had to recode every single place where I manipulated strings—which was a lot. &lt;/p&gt;
&lt;p&gt;In the end, though, I gained complete control over my variables and how I manage them. And honestly, that was the whole point.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you would like to connect with me, you can check out my socials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/suleymansade&quot;&gt;linkedin.com/in/suleymansade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@suleymansade09&quot;&gt;@suleymansade09&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22257/building-a-custom-terminal-from-scratch-devlog-1</guid>
<pubDate>Sat, 11 Jul 2026 01:08:15 +0000</pubDate>
</item>
<item>
<title>How Jarvis Learned to Remember, Communicate, and Access Memory</title>
<link>https://coderlegion.com/22207/how-jarvis-learned-to-remember-communicate-and-access-memory</link>
<description>&lt;p&gt;In my previous articles, I learned about functions, arrays, strings, and loops by imagining how they could help build an intelligent assistant like Jarvis.&lt;/p&gt;
&lt;p&gt;This week, I continued that journey by learning about 2D arrays, string functions, and pointers.&lt;/p&gt;
&lt;p&gt;At first, these topics looked like ordinary programming concepts. But when I started thinking about how Jarvis would use them, their importance became much easier to understand.&lt;/p&gt;
&lt;p&gt;Giving Jarvis a Tactical Display with 2D Arrays&lt;br&gt;
Imagine Tony Stark is flying through a city and Jarvis is tracking multiple enemy drones.&lt;/p&gt;
&lt;p&gt;For every drone, Jarvis needs to store information such as:&lt;/p&gt;
&lt;p&gt;X Position&lt;/p&gt;
&lt;p&gt;Y Position&lt;/p&gt;
&lt;p&gt;Threat Level&lt;/p&gt;
&lt;p&gt;This information can be organized in rows and columns, just like a table.&lt;/p&gt;
&lt;p&gt;This is where 2D arrays become useful.&lt;/p&gt;
&lt;p&gt;A 2D array allows us to store data in a grid format.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;int enemies[5][3];&lt;/p&gt;
&lt;p&gt;Here, each row could represent a drone, and each column could represent information about that drone.&lt;/p&gt;
&lt;p&gt;Thinking about 2D arrays this way helped me understand how they can be used to organize large amounts of related data.&lt;/p&gt;
&lt;p&gt;Helping Jarvis Understand Commands with Strings&lt;br&gt;
One thing that makes Jarvis special is its ability to communicate with Tony Stark.&lt;/p&gt;
&lt;p&gt;When Tony says:&lt;/p&gt;
&lt;p&gt;&quot;Jarvis, scan the building.&quot;&lt;/p&gt;
&lt;p&gt;the system must understand the command before it can perform the action.&lt;/p&gt;
&lt;p&gt;In C programming, text is stored using strings.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;char command[] = &quot;scan the building&quot;;&lt;/p&gt;
&lt;p&gt;This week, I also learned several string functions that make working with text easier.&lt;/p&gt;
&lt;p&gt;Some useful string functions are:&lt;/p&gt;
&lt;p&gt;strlen() – Finds the length of a string&lt;/p&gt;
&lt;p&gt;strcpy() – Copies one string into another&lt;/p&gt;
&lt;p&gt;strcmp() – Compares two strings&lt;/p&gt;
&lt;p&gt;strcat() – Combines two strings&lt;/p&gt;
&lt;p&gt;These functions allow programs to process and manage text efficiently.&lt;/p&gt;
&lt;p&gt;If Jarvis receives hundreds of commands every day, string functions would play an important role in understanding and handling them.&lt;/p&gt;
&lt;p&gt;Teaching Jarvis Where Information Lives with Pointers&lt;br&gt;
Pointers were one of the most interesting topics I learned this week.&lt;/p&gt;
&lt;p&gt;Until now, Jarvis knew what information existed.&lt;/p&gt;
&lt;p&gt;With pointers, Jarvis learns where that information is stored.&lt;/p&gt;
&lt;p&gt;A pointer stores the memory address of another variable.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;int battery = 90;&lt;br&gt;
int *ptr = &amp;amp;battery;&lt;/p&gt;
&lt;p&gt;Here:&lt;/p&gt;
&lt;p&gt;battery stores the value 90&lt;/p&gt;
&lt;p&gt;ptr stores the address of battery&lt;/p&gt;
&lt;p&gt;Instead of only knowing the battery level, Jarvis now knows where that battery information is located in memory.&lt;/p&gt;
&lt;p&gt;This direct access to memory is one of the reasons C is widely used in embedded systems.&lt;/p&gt;
&lt;p&gt;As I learn more about microcontrollers in the coming weeks, I expect pointers to become even more important.&lt;/p&gt;
&lt;p&gt;My Key Takeaway&lt;br&gt;
This week helped me understand that programming is not only about writing code.&lt;/p&gt;
&lt;p&gt;It is also about organizing information, processing data, and managing memory efficiently.&lt;/p&gt;
&lt;p&gt;Through 2D arrays, Jarvis learned how to organize information in a structured way.&lt;/p&gt;
&lt;p&gt;Through strings and string functions, Jarvis learned how to understand and work with text-based commands.&lt;/p&gt;
&lt;p&gt;Through pointers, Jarvis learned how to directly access information stored in memory.&lt;/p&gt;
&lt;p&gt;Each new topic feels like adding another capability to an intelligent system.&lt;/p&gt;
&lt;p&gt;The more I learn, the more I realize that advanced technologies such as AI assistants, robots, and autonomous systems are built using these simple building blocks.&lt;/p&gt;
&lt;p&gt;This weekend, Jarvis learned how to organize information, communicate using text, and access memory.&lt;/p&gt;
&lt;p&gt;I wonder what new abilities he will gain next weekend.&lt;/p&gt;
&lt;p&gt;Jarvis Capability Log&lt;br&gt;
✓ Store data using variables&lt;/p&gt;
&lt;p&gt;✓ Perform calculations&lt;/p&gt;
&lt;p&gt;✓ Make decisions&lt;/p&gt;
&lt;p&gt;✓ Repeat tasks using loops&lt;/p&gt;
&lt;p&gt;✓ Organize abilities using functions&lt;/p&gt;
&lt;p&gt;✓ Store collections of information using arrays&lt;/p&gt;
&lt;p&gt;✓ Process text using strings&lt;/p&gt;
&lt;p&gt;✓ Analyze data using nested loops&lt;/p&gt;
&lt;p&gt;✓ Organize data using 2D arrays&lt;/p&gt;
&lt;p&gt;✓ Understand text using string functions&lt;/p&gt;
&lt;p&gt;✓ Access memory using pointers&lt;/p&gt;
&lt;p&gt;🚀 Next Upgrade: Coming Soon&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22207/how-jarvis-learned-to-remember-communicate-and-access-memory</guid>
<pubDate>Fri, 10 Jul 2026 09:50:20 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 18 — Generating random numbers in C++</title>
<link>https://coderlegion.com/21473/tuesday-coding-tip-18-generating-random-numbers-in-c</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Classic C way of generating random numbers is to set the current time as a seed and then modulo results of the rand function to get somewhat random numbers (modulo can introduce bias based on the implementation of rand).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;random&amp;gt;

// Get random integer in range &amp;lt;min, max)
int getRandomNumber(int min, int max)
{
    return rand() % (max - min) + min;
}

int main()
{
    // seeding with current time to get different output each time
    srand(time(NULL));

    for (int i = 0; i &amp;lt; 10; i++)
        std::cout &amp;lt;&amp;lt; getRandomNumber(10, 20) &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++11 introduces the concept of engines, adaptors, and distributions. Engines are seedable and give you random numbers similar to &lt;code&gt;rand()&lt;/code&gt; calls. However, each engine is implemented differently, impacting its speed and memory footprint. Adapters tamper with RNG output, such as shuffling the order of output values or discarding them.&lt;/p&gt;
&lt;p&gt;Distributions transform the output from the generators to get values in certain ranges and probabilistic properties, such as the middle value being the most probable (normal distribution). Distributions are more nuanced than just using the modulo operator so their outputs are more suitable for cryptography use-cases.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;random&amp;gt;

// Get random integer in range &amp;lt;min, max)
int getRandomNumber(int min, int max)
{
    return rand() % (max - min) + min;
}

int main()
{
    std::random_device rd; // non-seedable source of non-deterministic numbers from hardware
    std::mt19937 generator(rd()); // deterministic generator with uniform distribution
                                  // seeded with totally random number

    std::uniform_int_distribution&amp;lt;&amp;gt; distribution(10, 19);

    for (int i = 0; i &amp;lt; 10; i++)
        std::cout &amp;lt;&amp;lt; distribution(generator) &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21473/tuesday-coding-tip-18-generating-random-numbers-in-c</guid>
<pubDate>Tue, 07 Jul 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 17 — Wrapping C APIs</title>
<link>https://coderlegion.com/21472/tuesday-coding-tip-17-wrapping-c-apis</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Resource Acquisition is Initialization (RAII) is an awfully named principle saying that your class should be fully usable after construction and should clean itself up in the destructor (in other words, no (de)init methods). This principle is common in modern programming languages and is also adopted in modern C++ codebases.&lt;/p&gt;
&lt;p&gt;But what if you work with C-like API like &lt;code&gt;fopen&lt;/code&gt;? It returns a pointer you need to manually free before it goes out of scope. Otherwise, you leak memory and open file handles. This is where smart pointers come to the rescue as you can wrap raw pointers with them and give them custom callbacks to execute when they leave the scope.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;cstdlib&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

// MSVC disallows calling fopen
#pragma warning(disable : 4996)

void CloseFile(FILE* f)
{
    std::cout &amp;lt;&amp;lt; &quot;Closing file&quot; &amp;lt;&amp;lt; std::endl;
    fclose(f);
}

auto SafeFopen(const char* path, const char* mode)
{
    return std::unique_ptr&amp;lt;FILE, decltype(&amp;amp;CloseFile)&amp;gt;(
        fopen(path, mode), CloseFile);
}

int main()
{
    auto&amp;amp;&amp;amp; fp = SafeFopen(&quot;myfile.txt&quot;, &quot;w&quot;);
    return 0;
    // Outputs: Closing file
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21472/tuesday-coding-tip-17-wrapping-c-apis</guid>
<pubDate>Tue, 30 Jun 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 16 — Creating objects in existing memory</title>
<link>https://coderlegion.com/19946/tuesday-coding-tip-16-creating-objects-in-existing-memory</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;C++ allows you to scrap your OS’ memory allocators, get yourself a huge chunk of memory and write your own allocators (they can be used with the standard library). It’s a hardcore optimization, but here’s a related tip that might be handy in normal projects as well.&lt;/p&gt;
&lt;p&gt;When using the operator &lt;code&gt;new&lt;/code&gt;, one can provide it with an already allocated memory, so it runs a constructor for a particular type in there. After that, reinterpret that memory as a pointer to your desired object and use it normally. However, deletion is a bit more complicated, as you have to call the destructor on that object manually (because &lt;code&gt;delete&lt;/code&gt; must be called on the original memory, not the object).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// using some template type T
auto&amp;amp;&amp;amp; buffer = static_cast&amp;lt;std::byte*&amp;gt;(::operator new(sizeof(T)));
if (!buffer) throw std::bad_alloc();

new (buffer) T();
auto&amp;amp;&amp;amp; object = reinterpret_cast&amp;lt;T*&amp;gt;(buffer);

// ...

object-&amp;gt;~T(); // call destructor
delete[] buffer; // properly release memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used this trick when implementing a resource manager that needs to hold any user data type (that is still default-constructible in my case): &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nerudaj/dgm-lib/blob/main/lib/include/DGM/classes/ResourceManager.hpp&quot;&gt;link&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19946/tuesday-coding-tip-16-creating-objects-in-existing-memory</guid>
<pubDate>Tue, 16 Jun 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Why I Still Choose Go After All These Years</title>
<link>https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</link>
<description>&lt;p&gt;&lt;strong&gt;Hello everyone.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'd like to discuss a topic that has sparked endless debates among developers for years:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What programming language should you choose for a serious project?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Throughout my career, I've worked with C, Assembly, Go, and spent a considerable amount of time exploring modern languages such as Rust and C++. The more experience I gain, the more convinced I become that &lt;strong&gt;there is no perfect programming language&lt;/strong&gt;. Every language has its own strengths and weaknesses.&lt;/p&gt;
&lt;p&gt;Despite that, I deliberately chose &lt;strong&gt;Go&lt;/strong&gt; as the foundation of my primary project: &lt;strong&gt;ForgeZero&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;ForgeZero is a cross-platform build orchestration tool written in Go, with carefully optimized performance-critical components implemented in Plan 9 Assembly to minimize runtime overhead and eliminate unnecessary allocations.&lt;/p&gt;
&lt;p&gt;At first glance, it might seem logical that after working with C and Assembly, the next step should be Rust or modern C++, since they are widely considered the future of systems programming.&lt;/p&gt;
&lt;h3 id=&quot;rust&quot;&gt;Rust&lt;/h3&gt;
&lt;p&gt;Rust solves an impressive number of memory safety problems.&lt;/p&gt;
&lt;p&gt;Its ownership model and borrow checker force developers to think about lifetimes and resource management before the code even compiles.&lt;/p&gt;
&lt;p&gt;However, safety comes at a cost.&lt;/p&gt;
&lt;p&gt;There are moments when it feels like the language dictates your architecture rather than the other way around. In large projects, satisfying the borrow checker can sometimes consume more time than solving the original problem itself.&lt;/p&gt;
&lt;p&gt;Even with Rust's strict guarantees, bugs still exist. No language can eliminate every possible programming mistake.&lt;/p&gt;
&lt;p&gt;Personally, I also find large Rust codebases relatively difficult to read. The amount of syntax and abstractions often makes understanding unfamiliar code slower than it should be.&lt;/p&gt;
&lt;h3 id=&quot;c-&quot;&gt;C++&lt;/h3&gt;
&lt;p&gt;C++ is an incredibly powerful language.&lt;/p&gt;
&lt;p&gt;Unfortunately, that power comes with enormous complexity.&lt;/p&gt;
&lt;p&gt;Templates, metaprogramming, concepts, multiple paradigms, countless coding styles, and dozens of ways to solve the same problem can make maintaining large codebases challenging.&lt;/p&gt;
&lt;p&gt;Sometimes reading someone else's C++ project feels less like software engineering and more like an archaeological expedition.&lt;/p&gt;
&lt;h3 id=&quot;c&quot;&gt;C&lt;/h3&gt;
&lt;p&gt;C remains one of the greatest programming languages ever created.&lt;/p&gt;
&lt;p&gt;It provides maximum control over the system, minimal abstractions, and extremely predictable performance, making it an outstanding choice for low-level development.&lt;/p&gt;
&lt;p&gt;The downside, however, is responsibility.&lt;/p&gt;
&lt;p&gt;Every pointer, every allocation, and every memory access becomes your responsibility. Bugs such as use-after-free, buffer overflows, or segmentation faults can be devastating.&lt;/p&gt;
&lt;p&gt;And perhaps even more importantly, building large software systems in C requires an enormous investment of engineering time.&lt;/p&gt;
&lt;p&gt;Had ForgeZero been written entirely in C, both development and long-term maintenance would have been significantly more expensive.&lt;/p&gt;
&lt;h3 id=&quot;why-go-&quot;&gt;Why Go?&lt;/h3&gt;
&lt;p&gt;Go occupies a very unique position.&lt;/p&gt;
&lt;p&gt;It is simple enough that new contributors can understand a codebase within days, while still being powerful enough to build serious production-grade software.&lt;/p&gt;
&lt;p&gt;Goroutines, channels, built-in concurrency, and a minimalistic syntax allow developers to focus on solving problems instead of fighting the language.&lt;/p&gt;
&lt;p&gt;Of course, Go has its own limitations.&lt;/p&gt;
&lt;p&gt;The runtime, garbage collector, and automatic memory management make it a poor choice for operating system kernels, drivers, or extremely low-level software.&lt;/p&gt;
&lt;p&gt;Yet the common belief that Go cannot deliver high performance is simply incorrect.&lt;/p&gt;
&lt;p&gt;With careful architecture, proper escape analysis, object pooling, and manual optimization of performance-critical paths, it is entirely possible to achieve nearly zero allocations where they actually matter.&lt;/p&gt;
&lt;p&gt;That is exactly the direction ForgeZero takes.&lt;/p&gt;
&lt;p&gt;For me, Go represents an excellent balance between development speed, maintainability, readability, and performance.&lt;/p&gt;
&lt;p&gt;It is not perfect.&lt;/p&gt;
&lt;p&gt;But it allows me to build complex tooling faster than most alternatives without sacrificing quality.&lt;/p&gt;
&lt;p&gt;That is why, after all these years, I still choose Go.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;am-i-betraying-c-&quot;&gt;Am I Betraying C?&lt;/h2&gt;
&lt;p&gt;Absolutely not.&lt;/p&gt;
&lt;p&gt;C shaped me as a software engineer, and I still consider it one of the greatest programming languages ever designed.&lt;/p&gt;
&lt;p&gt;In fact, one of ForgeZero's goals is to &lt;strong&gt;make C development easier&lt;/strong&gt;, improving build workflows and project management for both newcomers and experienced engineers.&lt;/p&gt;
&lt;p&gt;Go may be my primary language today, but that does not mean abandoning C.&lt;/p&gt;
&lt;p&gt;A huge portion of modern software infrastructure still depends on it.&lt;/p&gt;
&lt;p&gt;Operating system kernels, drivers, networking libraries, embedded software, and countless system components have been built and refined in C for decades.&lt;/p&gt;
&lt;p&gt;Many developers now discuss Rust's introduction into the Linux kernel and the future of systems programming.&lt;/p&gt;
&lt;p&gt;But realistically, no major company or open-source project is going to rewrite millions of lines of stable legacy C code overnight simply because a newer language exists.&lt;/p&gt;
&lt;p&gt;Large migrations take years—sometimes decades—and happen gradually.&lt;/p&gt;
&lt;p&gt;For that reason alone, C will remain relevant for a very long time.&lt;/p&gt;
&lt;p&gt;And personally, I still use it whenever it is the right tool for the job.&lt;/p&gt;
&lt;p&gt;Recently, while training a small neural network on my home machine, I encountered an interesting problem.&lt;/p&gt;
&lt;p&gt;The hardware is far from modern: an eight-core CPU from 2014 paired with a GTX 1060 6GB (Pascal architecture). Although training was performed on the GPU through PyTorch, the CPU was still under significant thermal stress.&lt;/p&gt;
&lt;p&gt;Instead of building a monitoring solution in Python or deploying heavyweight background services, I wrote a small daemon in C that continuously monitored CPU temperature and automatically terminated non-essential processes once critical thresholds were reached.&lt;/p&gt;
&lt;p&gt;Could I have written the same utility in Go?&lt;/p&gt;
&lt;p&gt;Absolutely.&lt;/p&gt;
&lt;p&gt;But for this particular low-level task, C was simply the better tool.&lt;/p&gt;
&lt;p&gt;No runtime, minimal dependencies, tiny resource usage, and complete control over the operating system.&lt;/p&gt;
&lt;p&gt;That is exactly why I never claim that Go should replace C.&lt;/p&gt;
&lt;p&gt;Quite the opposite.&lt;/p&gt;
&lt;p&gt;I believe that &lt;strong&gt;Go + C + Assembly&lt;/strong&gt; is an incredibly powerful combination.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; excels at infrastructure, CLI applications, automation, backend services, and distributed systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; remains unmatched for systems programming and direct interaction with operating system internals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assembly&lt;/strong&gt; is invaluable when every CPU cycle matters and maximum optimization is required.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good engineer should choose the right tool for the problem—not the trendiest language on social media.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;my-advice-for-beginners&quot;&gt;My Advice for Beginners&lt;/h2&gt;
&lt;p&gt;If I had to start my programming journey again, I would begin with &lt;strong&gt;C&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not because it is the &quot;best&quot; language, but because it forces you to understand how computers actually work.&lt;/p&gt;
&lt;p&gt;How memory is organized.&lt;/p&gt;
&lt;p&gt;How the stack and the heap operate.&lt;/p&gt;
&lt;p&gt;How pointers behave.&lt;/p&gt;
&lt;p&gt;How a compiler transforms source code into assembly instructions and eventually into machine code executed by the processor.&lt;/p&gt;
&lt;p&gt;Once those fundamentals become clear, learning Go, Rust, C++, Java, or almost any other language becomes dramatically easier.&lt;/p&gt;
&lt;p&gt;You stop memorizing syntax and start understanding software engineering.&lt;/p&gt;
&lt;p&gt;So my advice is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't be afraid of low-level programming.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if your future career revolves around web services or cloud infrastructure, understanding what happens beneath the abstractions will make you a far stronger engineer.&lt;/p&gt;
&lt;p&gt;Programming languages come and go.&lt;/p&gt;
&lt;p&gt;Fundamental computer science knowledge stays with you forever.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</guid>
<pubDate>Wed, 10 Jun 2026 13:38:44 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 15 — Mathematical constants in C++</title>
<link>https://coderlegion.com/19942/tuesday-coding-tip-15-mathematical-constants-in-c</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and LinkedIn.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Since C++20, you can include header &lt;code&gt;&amp;lt;numbers&amp;gt;&lt;/code&gt; and use many commonly used, precomputed mathematical constants like Pi, Phi, square root of 2 and 3, natural logarithms of 2 and 10, and many more! Even better, you can choose their precision by specifying the underlying type via a template.&lt;/p&gt;
&lt;p&gt;And since C++17, the &lt;code&gt;&amp;lt;cmath&amp;gt;&lt;/code&gt; header contains some special math functions I’ve never heard of like cylindrical Bessel functions, associated Legendre polynomials, or Riemann zeta function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;numbers&amp;gt;
#include &amp;lt;iomanip&amp;gt; // std::setprecision
#include &amp;lt;limits&amp;gt; // std::numeric_limits

template&amp;lt;typename T&amp;gt;
void printConstant(T value)
{
    const auto default_precision{std::cout.precision()};

    std::cout &amp;lt;&amp;lt; std::setprecision(std::numeric_limits&amp;lt;T&amp;gt;::digits10 + 1);
    std::cout &amp;lt;&amp;lt; value &amp;lt;&amp;lt; std::endl;

    std::cout &amp;lt;&amp;lt; std::setprecision(default_precision);
}

int main()
{
    using namespace std::numbers;
    printConstant(pi_v&amp;lt;float&amp;gt;);
    printConstant(pi_v&amp;lt;double&amp;gt;);
    printConstant(pi_v&amp;lt;long double&amp;gt;);

    /* Outputs:
    3.141593
    3.141592653589793
    3.141592653589793239
    */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Constants are documented here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/numeric/constants&quot;&gt;https://en.cppreference.com/w/cpp/numeric/constants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Special functions are documented here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/numeric/special_functions&quot;&gt;https://en.cppreference.com/w/cpp/numeric/special_functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19942/tuesday-coding-tip-15-mathematical-constants-in-c</guid>
<pubDate>Tue, 09 Jun 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>How I saved my Python-to-C++ Transpiler from failing by pivoting to Gemini AI (Built 100% on Mobile) </title>
<link>https://coderlegion.com/20037/how-saved-my-python-to-transpiler-from-failing-by-pivoting-to-gemini-ai-built-100-on-mobile</link>
<description>&lt;p&gt;Hey devs!&lt;br&gt;
​A while ago, I started building Astmize, a web tool to transpile Python code into optimized C++. In the first version, I tried building a strict AST parser. Long story short: handling type inference across languages entirely from a mobile IDE was an absolute nightmare, and I almost gave up on the project.&lt;br&gt;
​Instead of quitting, I decided to pivot. I completely rewrote the backend to leverage Gemini AI as the core translation engine. Now, instead of breaking on complex structures, the AI understands the logic and generates clean, optimized C++ code dynamically.&lt;br&gt;
​And yes, the entire stack (Frontend on GitHub Pages, Backend API on Render) was built, debugged, and deployed 100% using my phone.&lt;br&gt;
​Version 2.0 is officially LIVE! I'd love for you guys to break it, test it with some complex Python logic, and give me your brutal feedback.&lt;br&gt;
​ Live Demo: [https://thespacetimedebugger.github.io/Astmize/]&lt;br&gt;
 GitHub Repo: [https://github.com/TheSpacetimeDebugger/Astmize.git]&lt;br&gt;
​Let's hear your thoughts!&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/inikvo3bpni8l9ug1dz5.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20037/how-saved-my-python-to-transpiler-from-failing-by-pivoting-to-gemini-ai-built-100-on-mobile</guid>
<pubDate>Tue, 09 Jun 2026 09:24:42 +0000</pubDate>
</item>
<item>
<title>I built an open-source Python to C++ AST Transpiler entirely from my phone</title>
<link>https://coderlegion.com/20036/i-built-an-open-source-python-to-c-ast-transpiler-entirely-from-my-phone</link>
<description>&lt;p&gt;Hey everyone! &lt;/p&gt;
&lt;p&gt;I wanted to share a project I've been pouring my heart into: &lt;strong&gt;Astmize&lt;/strong&gt;. It's an open-source Python-to-C++ transpiler built entirely from my mobile phone (using mobile editors since I don't own a PC at the moment). &lt;/p&gt;
&lt;p&gt;Instead of using basic regex or string replacement, &lt;strong&gt;Astmize&lt;/strong&gt; parses Python code into an &lt;strong&gt;Abstract Syntax Tree (AST)&lt;/strong&gt; and maps the logical nodes directly into structurally equivalent, clean C++ code.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-c-design-bottleneck-i-had-to-solve&quot;&gt;The C++ Design Bottleneck I Had to Solve&lt;/h4&gt;
&lt;p&gt;While developing the transpiler from my screen, I hit a massive roadblock regarding type emission. As we know, C++ is statically typed and strictly forbids using &lt;code&gt;auto&lt;/code&gt; for non-static class data members without a direct initialization expression, because the compiler must determine the exact object memory layout at compile time.&lt;/p&gt;
&lt;p&gt;In Python, fields are dynamically assigned inside &lt;code&gt;__init__&lt;/code&gt; via arguments (e.g., &lt;code&gt;self.speed = speed&lt;/code&gt;). Initially, my compiler emitted &lt;code&gt;auto speed;&lt;/code&gt; inside the generated C++ class struct, leading to immediate compilation failures.&lt;/p&gt;
&lt;h4 id=&quot;the-solution-smart-type-inference-engine&quot;&gt;The Solution: Smart Type Inference Engine&lt;/h4&gt;
&lt;p&gt;To fix this from my phone, I implemented a custom &lt;strong&gt;Type Inference Engine&lt;/strong&gt; in the Python backend that utilizes three core heuristics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;RHS Node Evaluation:&lt;/strong&gt; Evaluates the Right-Hand Side assignment node to detect concrete primitive constants or explicit type initializers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variable Tokenization:&lt;/strong&gt; Automatically splits attribute names by underscores and parses individual tokens against an explicit C++ type keyword library (e.g., detecting &lt;code&gt;is_&lt;/code&gt; or &lt;code&gt;has_&lt;/code&gt; prefixes infers a &lt;code&gt;bool&lt;/code&gt;; tokenizing &lt;code&gt;player_speed&lt;/code&gt; maps &lt;code&gt;speed&lt;/code&gt; to &lt;code&gt;int&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safe Fallback Mechanism:&lt;/strong&gt; If the type remains completely ambiguous, it safely defaults to &lt;code&gt;int&lt;/code&gt; instead of emitting an illegal &lt;code&gt;auto&lt;/code&gt;, ensuring structural layout validity and preventing compilation blocks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The live dashboard integrates with standard GCC/Clang via the &lt;strong&gt;Wandbox API&lt;/strong&gt; to compile and run the generated C++ code instantly on the fly. &lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;check-it-out-here-&quot;&gt;Check it out here:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Live Web App:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://thespacetimedebugger.github.io/Astmize/&quot;&gt;https://thespacetimedebugger.github.io/Astmize/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/TheSpacetimeDebugger/Astmize.git&quot;&gt;https://github.com/TheSpacetimeDebugger/Astmize.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'd absolutely love to get your feedback on the generated C++ structure, how you handle strict type emission in metaprogramming/transpilers, and if there are any C++ edge cases I should guard against! &lt;/p&gt;
&lt;p&gt;Drop a ⭐ on GitHub if you find the concept interesting! Happy coding! &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6sh7etl6mxnim0pvo74g.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20036/i-built-an-open-source-python-to-c-ast-transpiler-entirely-from-my-phone</guid>
<pubDate>Tue, 09 Jun 2026 09:23:03 +0000</pubDate>
</item>
<item>
<title>How to Fix Xcode C++ Compiler Errors in React Native (Yoga / glog / constexpr)</title>
<link>https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</link>
<description>&lt;p&gt;You just upgraded your local development environment, updated Xcode, or bumped a minor patch version in your React Native project. You open your terminal, run your standard iOS build command, and instead of a clean bundling process, the terminal throws a massive wall of raw C++ compiler text at you.&lt;/p&gt;
&lt;p&gt;The error logs likely point to internal framework files like &lt;code&gt;Yoga.cpp&lt;/code&gt;, &lt;code&gt;glog&lt;/code&gt;, or the &lt;code&gt;fmt&lt;/code&gt; library, screaming about &lt;code&gt;constexpr&lt;/code&gt; or &lt;code&gt;consteval&lt;/code&gt; issues, or complaining that a specific language standard identifier is missing.&lt;/p&gt;
&lt;p&gt;This is one of the single most frustrating traps in mobile development. Your JavaScript is perfect. Your React components are clean. But your entire build is dead because an underlying C++ compilation standard is fighting your new Xcode tooling.&lt;/p&gt;
&lt;h2 id=&quot;why-this-happens-to-javascript-developers&quot;&gt;Why This Happens to JavaScript Developers&lt;/h2&gt;
&lt;p&gt;React Native relies heavily on a core C++ layout engine called Yoga under the hood. When Apple updates Xcode, they also update the underlying compiler tools (Clang) and shift the default C++ language dialect standard forward (for example, enforcing strict C++20 or C++23 rules).&lt;/p&gt;
&lt;p&gt;If an older version of a library in your &lt;code&gt;node_modules&lt;/code&gt; uses a syntax decoration that the new compiler now considers illegal or deprecated, the native compilation step fails instantly. Because most mobile developers do not actively write pure C++, looking at a raw Clang compiler failure feels like looking at alien code.&lt;/p&gt;
&lt;p&gt;Nuking &lt;code&gt;DerivedData&lt;/code&gt; or running &lt;code&gt;pod install&lt;/code&gt; twenty times will not change the fact that the compiler rules have changed.&lt;/p&gt;
&lt;h2 id=&quot;the-solution-force-the-c-language-standard-backwards&quot;&gt;The Solution: Force the C++ Language Standard Backwards&lt;/h2&gt;
&lt;p&gt;Instead of attempting to manually modify source code deep inside your native &lt;code&gt;node_modules&lt;/code&gt; dependencies (which will just get overwritten the next time you install packages), you can use a CocoaPods post-install hook to force the compiler to accept the specific language dialect your dependencies need to pass the check.&lt;/p&gt;
&lt;p&gt;Open the &lt;code&gt;Podfile&lt;/code&gt; located inside your project's native &lt;code&gt;ios&lt;/code&gt; directory. Scroll down to the bottom where your &lt;code&gt;post_install&lt;/code&gt; loop lives, and inject this compiler flag override block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # Force the compiler to use the stable C++17 standard for all sub-dependencies
      config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By adding this configuration script, CocoaPods will automatically modify the native build files for every single sub-dependency when you run your installation pipeline, forcing Xcode to evaluate the underlying C++ libraries under a compatible standard framework.&lt;/p&gt;
&lt;h2 id=&quot;clear-the-native-caches-and-rebuild&quot;&gt;Clear the Native Caches and Rebuild&lt;/h2&gt;
&lt;p&gt;Once the project configurations are updated, you must completely destroy the old build artifacts that were compiled under the conflicting settings, or Xcode will continue to throw the same syntax exception.&lt;/p&gt;
&lt;p&gt;Run this quick command chain in your project terminal to wipe the native cache layers and execute a clean compilation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clear Xcode's local compilation cache
rm -rf ~/Library/Developer/Xcode/DerivedData

# Re-evaluate the Podfile with the new compiler hook
cd ios
pod install --repo-update

# Return to root and boot the iOS simulator
cd ..
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as the native build pipelines evaluate your sub-dependencies through the consistent compiler standard, the cryptic layout exceptions will vanish and your bundle will build cleanly.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</guid>
<pubDate>Sat, 06 Jun 2026 09:39:12 +0000</pubDate>
</item>
<item>
<title>The 7-Zip RCE Flaw (CVE-2026-48095): Inside the Shift Loophole</title>
<link>https://coderlegion.com/19646/the-7-zip-rce-flaw-cve-2026-48095-inside-the-shift-loophole</link>
<description>&lt;p&gt;The security world was recently reminded that some of our most trusted, everyday open-source tools can harbor critical flaws. A major remote code execution (RCE) vulnerability, tracked as &lt;strong&gt;CVE-2026-48095&lt;/strong&gt;, was disclosed in the ubiquitous &lt;strong&gt;7-Zip&lt;/strong&gt; archive utility. &lt;/p&gt;
&lt;p&gt;Impactful up to &lt;strong&gt;version 26.00&lt;/strong&gt;, this flaw allows attackers to compromise a system simply by convincing a user to open or extract a maliciously crafted archive file. &lt;/p&gt;
&lt;p&gt;Let’s take a look under the hood at what went wrong in the source code, why it bypassed security controls, and how to protect your codebases and infrastructure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-anatomy-of-the-bug-ntfshandler-cpp&quot;&gt;The Anatomy of the Bug: &lt;code&gt;NtfsHandler.cpp&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The vulnerability lies within 7-Zip's internal NTFS archive handler (&lt;code&gt;NtfsHandler.cpp&lt;/code&gt;). 7-Zip doesn't just read basic metadata; it parses raw disk images and file system structures to extract files.&lt;/p&gt;
&lt;p&gt;The flaw boils down to a &lt;strong&gt;Heap Buffer Overflow&lt;/strong&gt; triggered by a faulty integer arithmetic operation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The 32-bit Shift Loophole&lt;/strong&gt;: When calculating memory allocations for compressed internal structures, the code utilizes a 32-bit shift calculation to estimate size limits. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Under-allocation&lt;/strong&gt;: An attacker can carefully craft an archive with anomalous data structures that cause this calculation to wrap around or truncate. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Hijack&lt;/strong&gt;: As a result, 7-Zip allocates a heap buffer that is significantly smaller than the incoming payload. When the data is written into memory, it triggers an out-of-bounds write. This corrupts neighboring memory objects, leading to a &quot;vtable hijack&quot; that redirects application control flow to malicious code execution.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;why-this-evades-standard-email-filters&quot;&gt;Why This Evades Standard Email Filters&lt;/h4&gt;
&lt;p&gt;What makes this particularly dangerous for end-users is that it is &lt;strong&gt;extension-agnostic&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;7-Zip identifies formats by reading the magic bytes (internal file signatures) rather than trusting the file extension. An attacker can rename a highly malicious, exploited NTFS disk image to &lt;code&gt;vacation_photos.zip&lt;/code&gt; or &lt;code&gt;invoice.7z&lt;/code&gt;. When a user attempts to open it, 7-Zip automatically routes it to the vulnerable NTFS parsing engine.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-the-devops-amp-devsecops-reality-check&quot;&gt;️ The DevOps &amp;amp; DevSecOps Reality Check&lt;/h3&gt;
&lt;p&gt;For developers and operations teams, bugs like this highlight a massive blind spot: &lt;strong&gt;Legacy third-party dependencies&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;Many enterprise servers, automated CI/CD pipelines, and background microservices rely on command-line utilities like 7-Zip to extract uploaded files, process logs, or ingest data packages. If a service account running an unpatched version of 7-Zip extracts an untrusted user upload, your entire backend container or server could be compromised.&lt;/p&gt;
&lt;h4 id=&quot;prevention-vs-detection&quot;&gt;Prevention vs. Detection&lt;/h4&gt;
&lt;p&gt;When it comes to building your own file-handling logic, catching these structural mathematical errors early is critical. Running a repository scanner—like &lt;strong&gt;Debuggix&lt;/strong&gt;, Semgrep, or SonarQube—as a casual part of your CI/CD pipeline helps catch integer overflows and path traversals in your &lt;em&gt;own&lt;/em&gt; code before it gets compiled. &lt;/p&gt;
&lt;p&gt;However, for third-party, pre-compiled desktop software like 7-Zip, repository scanners cannot intercept a compiled binary run by a user. For that, you need active patch management and software inventory monitoring.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-how-to-protect-your-systems&quot;&gt;️ How to Protect Your Systems&lt;/h3&gt;
&lt;p&gt;If you or your team use 7-Zip, you need to remediate this immediately:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Update to 7-Zip v26.01+&lt;/strong&gt;: The patch directly modifies the 32-bit memory allocation math in &lt;code&gt;NtfsHandler.cpp&lt;/code&gt; to prevent truncation and buffer overflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit Production Environments&lt;/strong&gt;: Check your deployment scripts, Dockerfiles, and build servers. Ensure any automated extraction scripts are executing the updated binary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sanitize User Uploads&lt;/strong&gt;: If your application allows users to upload &lt;code&gt;.zip&lt;/code&gt; or &lt;code&gt;.7z&lt;/code&gt; files, ensure they are unpacked in isolated, sandboxed environments with low-privilege service accounts to limit the blast radius of potential execution.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Have you audited your servers for 7-Zip versions yet? Let’s discuss in the comments how your team manages unmanaged desktop utility dependencies in production!&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/19646/the-7-zip-rce-flaw-cve-2026-48095-inside-the-shift-loophole</guid>
<pubDate>Thu, 04 Jun 2026 08:46:58 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 13 — Deleting problematic overloads</title>
<link>https://coderlegion.com/18836/tuesday-coding-tip-13-deleting-problematic-overloads</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Suppose you have a C++ function for opening a file stream that accepts a path to a file and a string (view) with open-mode specifications (like &lt;code&gt;fopen&lt;/code&gt;). The issue is, even though your API is strongly typed, users can still swap the arguments by accident and don’t get a compile time error, due to implicit conversions from string-like constants.&lt;/p&gt;
&lt;p&gt;Luckily, there is an easy hack with which you can disable implicit conversions for one particular function by explicitly deleting all overloads (via &lt;code&gt;auto&lt;/code&gt; template) and allowing just one particular version. The coolest thing is that even the return value doesn’t have to match!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;filesystem&amp;gt;

using path = std::filesystem::path;

void fopen2(const auto&amp;amp;, const auto&amp;amp;) = delete;

FILE* fopen2(const path&amp;amp; fp, const std::string&amp;amp; mode)
{
    /* dummy implementation */
    return nullptr;
}

int main()
{
    // error: call to deleted function 'fopen2'
    // note: candidate function [with auto:1 = char[11], auto:2 = char[2]] has been explicitly deleted
    fopen2(&quot;myfile.txt&quot;, &quot;r&quot;);

    // This is ok
    fopen2(path { &quot;myfile.txt&quot; }, std::string { &quot;r&quot; });
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18836/tuesday-coding-tip-13-deleting-problematic-overloads</guid>
<pubDate>Tue, 26 May 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>8 Arduino coding habits that separate hobby sketches from real products</title>
<link>https://coderlegion.com/18954/8-arduino-coding-habits-that-separate-hobby-sketches-from-real-products</link>
<description>&lt;p&gt;Most Arduino tutorials teach you to blink an LED and stop there. The jump from &quot;it works on my desk&quot; to &quot;it ran for 6 months without crashing&quot; is where most hobby projects die. Here are eight habits that get you across that gap - most of them I learned the hard way.&lt;/p&gt;
&lt;h3 id=&quot;1-stop-using-delay-&quot;&gt;1. Stop using &lt;code&gt;delay()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;delay(1000)&lt;/code&gt; halts your entire program for a full second. Nothing else runs - not your buttons, not your sensor reads, not your serial output. Use &lt;code&gt;millis()&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 1000;

void loop() {
  if (millis() - lastBlink &amp;gt;= BLINK_INTERVAL) {
    lastBlink = millis();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
  // your other code keeps running here
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you internalize this pattern, every project gets better.&lt;/p&gt;
&lt;h3 id=&quot;2-avoid-the-string-class-on-avr-boards&quot;&gt;2. Avoid the &lt;code&gt;String&lt;/code&gt; class on AVR boards&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;String&lt;/code&gt; fragments the tiny heap on Uno/Nano/Mega. Your sketch runs fine for an hour, then crashes mysteriously at 3 AM. Use fixed-size &lt;code&gt;char&lt;/code&gt; buffers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;char buf[32];
snprintf(buf, sizeof(buf), &quot;Temp: %d C&quot;, temperature);
Serial.println(buf);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ESP32 with megabytes of RAM, &lt;code&gt;String&lt;/code&gt; is fine. On a 2KB AVR, it's a timebomb.&lt;/p&gt;
&lt;h3 id=&quot;3-wrap-string-literals-in-f-&quot;&gt;3. Wrap string literals in &lt;code&gt;F()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Every &lt;code&gt;Serial.println(&quot;Hello world&quot;)&lt;/code&gt; copies that string into precious RAM at boot. The &lt;code&gt;F()&lt;/code&gt; macro keeps it in flash:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Serial.println(F(&quot;System ready&quot;));  // costs 0 bytes of RAM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a 2KB Uno, this single change can free hundreds of bytes.&lt;/p&gt;
&lt;h3 id=&quot;4-be-explicit-about-integer-sizes&quot;&gt;4. Be explicit about integer sizes&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;int&lt;/code&gt; is &lt;strong&gt;16 bits on AVR&lt;/strong&gt; and &lt;strong&gt;32 bits on ESP32/SAMD&lt;/strong&gt;. Code that works on an Uno silently breaks on an ESP32 (or vice versa) because of overflow. Use &lt;code&gt;&amp;lt;stdint.h&amp;gt;&lt;/code&gt; types:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;uint8_t  pin = 7;
uint16_t adcValue;
uint32_t timestamp;
int16_t  signedTemp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your future self porting between boards will thank you.&lt;/p&gt;
&lt;h3 id=&quot;5-use-input-pullup-instead-of-external-resistors&quot;&gt;5. Use &lt;code&gt;INPUT_PULLUP&lt;/code&gt; instead of external resistors&lt;/h3&gt;
&lt;p&gt;You almost never need a physical pull-up resistor for a button. The AVR/ESP32 has them built in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;pinMode(BUTTON_PIN, INPUT_PULLUP);
// button pressed = LOW, released = HIGH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Less wiring, fewer parts, fewer things to debug.&lt;/p&gt;
&lt;h3 id=&quot;6-keep-isrs-tiny-and-mark-shared-variables-volatile&quot;&gt;6. Keep ISRs tiny and mark shared variables &lt;code&gt;volatile&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Interrupt service routines should set a flag and exit. Do the real work in &lt;code&gt;loop()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;volatile bool buttonPressed = false;

void IRAM_ATTR onButton() {   // IRAM_ATTR for ESP32; omit on AVR
  buttonPressed = true;
}

void loop() {
  if (buttonPressed) {
    buttonPressed = false;
    handleButton();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calling &lt;code&gt;Serial.print()&lt;/code&gt; inside an ISR is a common rookie mistake - it can deadlock the whole board.&lt;/p&gt;
&lt;h3 id=&quot;7-model-state-machines-explicitly&quot;&gt;7. Model state machines explicitly&lt;/h3&gt;
&lt;p&gt;If your project has modes - idle, recording, transmitting, error - don't nest five &lt;code&gt;if&lt;/code&gt; statements. Use an &lt;code&gt;enum&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class State { Idle, Recording, Transmitting, Error };
State state = State::Idle;

void loop() {
  switch (state) {
    case State::Idle:         handleIdle(); break;
    case State::Recording:    handleRecording(); break;
    case State::Transmitting: handleTransmitting(); break;
    case State::Error:        handleError(); break;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one habit makes debugging an order of magnitude easier.&lt;/p&gt;
&lt;h3 id=&quot;8-enable-the-watchdog-for-anything-deployed&quot;&gt;8. Enable the watchdog for anything deployed&lt;/h3&gt;
&lt;p&gt;A device sitting in your garage with no debugger needs to recover from its own bugs. On AVR:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;avr/wdt.h&amp;gt;
void setup() { wdt_enable(WDTO_8S); }
void loop()  { wdt_reset(); /* your code */ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;loop()&lt;/code&gt; hangs for more than 8 seconds, the chip reboots itself. Beats driving across town to power-cycle.&lt;/p&gt;
&lt;h3 id=&quot;a-bonus-setup-and-loop-are-just-main-&quot;&gt;A bonus: &lt;code&gt;setup()&lt;/code&gt; and &lt;code&gt;loop()&lt;/code&gt; are just &lt;code&gt;main()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The Arduino IDE hides it, but underneath it's plain C++. You can - and should - define classes, split code across &lt;code&gt;.h&lt;/code&gt;/&lt;code&gt;.cpp&lt;/code&gt; files, and use namespaces. The &lt;code&gt;.ino&lt;/code&gt; file is just convention.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you want to go deeper on the jump from hobby projects to real embedded products, that's what we write about over at &lt;a rel=&quot;nofollow&quot; href=&quot;https://make-it.ai&quot;&gt;Make-it.ai&lt;/a&gt;. Tools for hardware design, project breakdowns, and lessons from makers actually shipping things in the wild.&lt;/p&gt;
&lt;p&gt;What's the Arduino habit you wish you'd picked up earlier? Drop it in the comments - I'm curious which of these resonate and which ones people disagree with.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18954/8-arduino-coding-habits-that-separate-hobby-sketches-from-real-products</guid>
<pubDate>Tue, 26 May 2026 05:54:48 +0000</pubDate>
</item>
<item>
<title>The Compiler Maze</title>
<link>https://coderlegion.com/18778/the-compiler-maze</link>
<description>&lt;h3 id=&quot;why-compiler-is-a-very-breathtaking-project-for-devs-and-single-dev&quot;&gt;Why Compiler is a very breathtaking project for devs and single dev&lt;/h3&gt;
&lt;p&gt;I just got into the parser stage 2 days ago and the design was flowing like a breeze. Since my &lt;strong&gt;compiler&lt;/strong&gt; is focused as a &lt;strong&gt;system programming&lt;/strong&gt; compiler for my custom language, I was at the type checking (local type checking) phase.&lt;/p&gt;
&lt;p&gt;This is where I created a util.hpp file in which I wrote a namespace containing polymorphic code as it is a global type system my compiler will use to verify type at compile time.&lt;/p&gt;
&lt;p&gt;My compiler makes sure it does everything at compile time as it really benefits — creating a custom language means you're definitely tired of the scope of the current language compiler, so that's why we're here!&lt;/p&gt;
&lt;h4 id=&quot;type-system&quot;&gt;Type System&lt;/h4&gt;
&lt;p&gt;The compiler tries to be ambitious like Zig and more than it, as it supports variable width types from 1 to 65535 for both unsigned and signed. I won't lie, I learned the formula for signed and unsigned, but like I said, the compiler scope got in my way: how do I store 2^65535 - 1 for unsigned value range? As we famous graphics devs and codecs know, u8 is from 0 to 255. Wait, it's unrelated.&lt;/p&gt;
&lt;h4 id=&quot;my-ambitions&quot;&gt;My Ambitions&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5719992594881900651&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I'm a rising video codec engineer but no traditional DCT, motion vector in my vision. I did try to invent a proprietary upscaler called &lt;strong&gt;GVEST&lt;/strong&gt; (Geometry Vector Expansion Spatial Transform). It's better than bilinear by a few tens of dB in edge construction without blurring, and same dB on a noisy image. If you want to see the images, I will try to upload the graph and simulation results which AI did run Python code to generate, in another Non-Compiler Series of ours. So to cut the story short, I knew entropy coding and video codecs need not just runtime precision but memory precision. And don't get me wrong — Gemini AI helped me fix the formula, as fitting the above range results from the above formula is not my path and I didn't see it coming, and I don't want another hour of burnout. I want to add theoretical bit-size memory allocation to the value of that variable bits to memory, meaning if a u8 (which is 1 byte) and the value was 5 which doesn't take the whole 8 bits or byte, why waste memory? But it's non-trivial as unpacking them will be a big fight, but I'm still going that route.&lt;/p&gt;
&lt;h4 id=&quot;what-s-still-breathtaking&quot;&gt;What's still breathtaking&lt;/h4&gt;
&lt;p&gt;Won't lie — I'm very ambitious. I've got so much type system, DSA to optimize at compile time. I asked AI why no language optimizes tree-based &lt;strong&gt;DSA&lt;/strong&gt; as I knew little about them. All my life from JS to C++ wasn't more than structs and arrays (with vector in C++'s case). I did learn DSA in my days as a JS dev, but C++ is different in everything: containers abstraction, STL, iterators, the &lt;code&gt;.data() + index&lt;/code&gt;, end or the &lt;code&gt;.begin()&lt;/code&gt;, &lt;code&gt;.end()&lt;/code&gt; — it's everywhere in C++, and beginners and experienced devs definitely still go to the docs once in a while. And honestly, the docs aren't great too.&lt;/p&gt;
&lt;p&gt;I went through serious burnout today as I want my language, even if C++-like, to have ownership and lifetime and memory validation, if you've seen my ambition above. It will also be friendly and clear with error messages, and I'm already proud of my lexer stage error messages on my string and char warnings and recovery on unterminated literals. It's really a breathtaking journey.&lt;/p&gt;
&lt;h4 id=&quot;closing-it-all-up&quot;&gt;Closing it all up&lt;/h4&gt;
&lt;p&gt;If you liked the architecture and insights you've seen or haven't, I welcome you in the comments section, and if you want to subscribe to my Newsletter — though it's in early stage — become my subscriber on Substack: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ayndlr.substack.com&quot;&gt;Substack&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I'm happy building the project as I'm not looking at where I am but where it's going to be. I'm breaking the LLVM oath solo, and you can too. If you follow up, everything counts for me.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18778/the-compiler-maze</guid>
<pubDate>Sat, 23 May 2026 21:22:29 +0000</pubDate>
</item>
<item>
<title>Why These and That in Every System</title>
<link>https://coderlegion.com/18598/why-these-and-that-in-every-system</link>
<description>&lt;p&gt;We all know about DSA—Data Structures and Algorithms. But that's not all there is to programming, no matter the field. I emphasize this especially in software engineering: DSA is almost never the fix on its own.&lt;/p&gt;
&lt;p&gt;Today marks over a week and a half on my lexer since the beginning of May 2026, when I decided to build a compiler. I've gone through different choices, code patterns, rewrites. But you know what? I didn't read a single compiler book. No Compiler Dragon Book—the bible of the field. No PDF, no 2,000 pages just to experience someone else's experience into yours.&lt;/p&gt;
&lt;p&gt;This article isn't saying you shouldn't read books. But if you're a systems engineer like me—and my experience is pretty low, roughly 2.5 years in C++ and years in programming—we know tools aren't always the answer. In fact, DSA isn't either. This isn't about toy projects. We're talking about real production-grade projects where every performance detail and line of code must be understood perfectly, not just written pretty.&lt;/p&gt;
&lt;p&gt;While writing the lexer in C++, coming from Rust after ditching C++ for a while, I hated my comeback. Rust's ownership model and the compiler fighting me on every borrow? I ran from it. Went back to C++ where debugging a struct—which was my token object—meant just printing fields and moving on. No lifetime elision wars, no fighting the borrow checker because my token held a reference to source text that might outlive the lexer stream. In C++, I own my memory, I leak it, I fix it. The cost is visible immediately. In Rust, the cost was hidden behind compiler errors that were technically correct but solved someone else's problem, not mine. I didn't need zero-cost abstractions. I needed to see my token struct in a debugger without wrestling with the language first.&lt;/p&gt;
&lt;p&gt;Those questions turned out to be simple. Deceptively so.&lt;/p&gt;
&lt;p&gt;I noticed that while having only the basics of compilers—its stages, just the prior experience (in my case: Lexer → AST → IR → Codegen)—if I had actually asked the same questions as the first person who attempted it, about its architecture, what it solves, and the coding reality... because as we all know, design doesn't map well to code. In the worst case, if we try to find the ratio, we can use only the most abstract and subtle design—not overly architected, but rearchitected through code. This is a classic system design phase, but I realized it through grit.&lt;/p&gt;
&lt;p&gt;So the problem, which you've probably guessed, is we want to solve two problems. First: no manual assembly. Second: high-level code and its own problem—which is, how do they get back to their root?&lt;/p&gt;
&lt;p&gt;The best approach—not the only approach, but the best—was to use what we all call a lexer today: a stream-broken, tokenized, labeled source code.&lt;/p&gt;
&lt;p&gt;But why? Why not parse straight from the character stream? Why add a whole stage just to label things?&lt;/p&gt;
&lt;p&gt;Because the problem isn't &quot;how do I read code.&quot; The problem is how do I transform high-level intent into machine execution without writing machine code by hand. And that problem has two parts: the human writes symbols, the machine needs instructions. The gap between them is where every cost hides.&lt;/p&gt;
&lt;p&gt;So you ask the two questions. First: what do I actually need to know about this source? Not &quot;what data structure should I use&quot;—that's the DSA trap. You don't reach for a hash map because hash maps are fast. You reach for it when your problem is &quot;I need to check if I've seen this identifier before in O(1).&quot; The problem first. The structure second.&lt;/p&gt;
&lt;p&gt;Second: where does the cost arrive? Design won't show you. Design is clean boxes and arrows. Code reveals it. When I wrote my lexer, I didn't hit the cost in the diagram. I hit it when I realized a recursive descent parser trying to backtrack over raw character streams was burning CPU on re-lexing the same identifier five times. The cost wasn't visible in the &quot;Lexer → Parser&quot; box. It was visible in the profiler, in the branching, in the cache misses from string comparisons.&lt;/p&gt;
&lt;p&gt;That's when the engineering choice crystallized. The lexer isn't there because compilers &quot;should have one.&quot; It's there because tokenization is the point where you pay the string cost once, then never again. You transform the variable-length, unpredictable, cache-unfriendly character soup into fixed-size, predictable, cache-friendly labels. That's not a fancy design pattern. That's solving the first sub-problem: how do I make the rest of the pipeline fast enough to be usable?&lt;/p&gt;
&lt;p&gt;Now, why can't the parser live in the lexer? Why not just build the AST while tokenizing?&lt;/p&gt;
&lt;p&gt;Because the lexer solves a linear problem. It walks left-to-right, one pass, no memory of nesting depth. The parser solves a non-linear problem. Take a complex recursive function—nested lambdas, match arms, closures capturing environments. The lexer sees this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FN  IDENT  LPAREN  IDENT  COLON  IDENT  RPAREN  LBRACE  MATCH  IDENT  LBRACE...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flat. Stateless. A conveyor belt of labels. It has no stack to track &quot;this brace closes the lambda, not the match arm.&quot; It cannot, by design, handle recursion because recursion requires a tree, and trees require a builder that remembers where it is in the structure.&lt;/p&gt;
&lt;p&gt;The parser sees the same tokens, but at depth 3, inside a match arm, inside a closure, knowing exactly which brace closes which scope. The lexer sees &lt;code&gt;RBRACE&lt;/code&gt; and thinks &quot;end of something.&quot; The parser knows &quot;end of match arm, inside lambda, inside function.&quot; That's the difference between a label and a structure.&lt;/p&gt;
&lt;p&gt;Could you force the lexer to track a stack? Could you make it &quot;smart&quot;? Sure. Now you've merged two problems into one stage. And when that recursive function nests ten levels deep, your lexer isn't tokenizing anymore—it's predicting, it's branching, it's holding state that grows with input complexity. The cost arrives in CPU exhaustion, in stack overflows, in the maze of entry and exit points where you can't tell if you consumed the full path or just the happy path. Burnout in code, not in design.&lt;/p&gt;
&lt;p&gt;And here's what they don't tell you: the burnout hits the engineer before it hits the machine. The junior who reads three compiler books before writing a line of code. The team that designs for 1M users at 100 users. The developer who builds a distributed system because &quot;microservices are best practice&quot; when a monolith would have shipped in a week. The cost arrives in the human first—in the paralysis of premature abstraction, in the exhaustion of solving problems you don't have yet. That's why these stages exist. Not because a book prescribed them. Because that problem needed that solution.&lt;/p&gt;
&lt;p&gt;The AST exists because some problems are inherently non-linear, and pretending they're linear doesn't make them linear—it makes them expensive. The parser is where you accept that cost upfront, where you build the explicit tree, where you make recursion manageable by giving it a structure that matches its nature. The AST isn't standard because every language has different non-linear truths. SIRL's match arms with explicit types need different nodes than C's switch statements. The AST shape is dictated by what your language actually does, not by what some book says it should look like.&lt;/p&gt;
&lt;p&gt;Same for the IR. Same for the lexer. We know the stages—Lexer → Parser → AST → IR → Codegen—but the implementations diverge because the problems diverge. A JIT compiler skips the AST for hot paths because its problem is latency, not optimization. An embedded compiler uses a different IR because its problem is register pressure, not vectorization.&lt;/p&gt;
&lt;p&gt;This is the grit. The design phase gives you the illusion that you've solved it. The code phase reveals you haven't. Design doesn't map to code. It maps to intention. Code maps to reality. And reality is where the cost lives—in the branching, in the memory layout, in the cache lines, in the non-linear paths that design documents politely ignore.&lt;/p&gt;
&lt;p&gt;Books give you answers to questions you don't have yet. The problem is yours; the answer should be too. Not from blind DSA application. Not from following a chapter. From asking what you actually need to know, and having the guts to let the code show you where the cost lives.&lt;/p&gt;
&lt;p&gt;So the lexer stays dumb. The parser stays recursive. The AST stays language-specific. Not because it's elegant. Because each stage solves exactly one problem, pays exactly one cost, and exposes exactly one interface to the next stage. That's systems engineering. Not DSA for DSA's sake. Problem first. Cost second. Structure last.&lt;/p&gt;
&lt;p&gt;And when you finally lower to IR and codegen to assembly, you've answered both original problems. No manual ASM. High-level code returned to its roots. But the path there wasn't found in a book. It was found by asking what the problem actually is, where the cost actually arrives, and having the grit to let code—not design, not someone else's experience—tell you the truth.&lt;/p&gt;
&lt;p&gt;Two questions. Everything else is just typing.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18598/why-these-and-that-in-every-system</guid>
<pubDate>Thu, 21 May 2026 21:25:10 +0000</pubDate>
</item>
<item>
<title>I'm Building a Multi-Target Compiler Backend from Scratch — No LLVM, No Crutches</title>
<link>https://coderlegion.com/18588/im-building-a-multi-target-compiler-backend-from-scratch-no-llvm-no-crutches</link>
<description>&lt;p&gt;Hi there, i'm Gideon. Roughly 1.5 years of writing C++ from the ground up — ray tracers, video codecs, and now a compiler. No frameworks. No LLVM. Just me, the hardware manuals, and a lot of wrong turns.&lt;/p&gt;
&lt;p&gt;This post starts a series where I document the build in real time. I'm currently in the parser stage. By the end, I want a compiler that emits x86-64 and SPIR-V from a C++-like language, with SIMD vectorization and security-hardened codegen baked in.&lt;/p&gt;
&lt;p&gt;What I'm Actually Building&lt;/p&gt;
&lt;p&gt;Not a programming language. A compiler backend toolkit — the part that turns intermediate representation into fast machine code across multiple targets.&lt;/p&gt;
&lt;p&gt;The pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Source → Parser → AST → SSMOL (HIR) → MREL (LIR) → x86-64 / SPIR-V / ARM64 / RISC-V / WASM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MREL is my target-agnostic low-level IR. It knows about virtual registers, stack slots, and machine operations — but not physical register names. The backend handles that per-target.&lt;/p&gt;
&lt;p&gt;Why Not Just Use LLVM?&lt;/p&gt;
&lt;p&gt;LLVM is 4 million lines of code. It solves everyone's problem and no one's perfectly. I need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fine-grained control over SIMD width selection per target&lt;/li&gt;
&lt;li&gt;Constant-time crypto primitive emission with secret register annotations&lt;/li&gt;
&lt;li&gt;Security obfuscation passes (control flow flattening, opaque predicates)&lt;/li&gt;
&lt;li&gt;A codebase I fully understand and can license&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building from scratch is slower. But I own every decision.&lt;/p&gt;
&lt;p&gt;Where I Am Right Now&lt;/p&gt;
&lt;p&gt;Parser stage. Hand-written recursive descent. C++-like syntax with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Functions, structs, basic types&lt;/li&gt;
&lt;li&gt;Ownership semantics (borrowed from my Rust phase, simplified)&lt;/li&gt;
&lt;li&gt;Explicit SIMD types (&lt;code&gt;v128&lt;/code&gt;, &lt;code&gt;v256&lt;/code&gt;, &lt;code&gt;v512&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The parser emits an AST that gets lowered to SSMOL — my high-level IR that knows about types, ownership, and semantics.&lt;/p&gt;
&lt;p&gt;What's Next&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SSMOL → MREL lowering (types to sizes, structs to offsets, control flow to basic blocks)&lt;/li&gt;
&lt;li&gt;MREL → x86-64 backend (register allocation, instruction selection, ELF emission)&lt;/li&gt;
&lt;li&gt;One working program: compile, link, run &lt;code&gt;main()&lt;/code&gt; that returns 42&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then SPIR-V compute kernels. Then the rest.&lt;/p&gt;
&lt;p&gt;What I'll Write About&lt;/p&gt;
&lt;p&gt;Each stage, when I hit it. The problems that took me three days to solve. The specs I wrote to keep myself honest. The wrong assumptions that cost me a week.&lt;/p&gt;
&lt;p&gt;Not polished tutorials. Build logs from someone actually building.&lt;/p&gt;
&lt;p&gt;Follow This Series If&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You work in systems, compilers, or graphics&lt;/li&gt;
&lt;li&gt;You're curious what &quot;building from scratch&quot; actually looks like&lt;/li&gt;
&lt;li&gt;You want to see if I crash or ship&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My Specs (For the Curious)&lt;/p&gt;
&lt;p&gt;I write technical specifications to keep the design coherent across months of work. The MREL backend spec covers x86-64, ARM64, RISC-V, SPIR-V, and WebAssembly with calling conventions, opcode tables, and security passes. Link in bio.&lt;/p&gt;
&lt;p&gt;Substack: (&lt;a rel=&quot;nofollow&quot; href=&quot;https://ayndlr.substack.com&quot;&gt;https://ayndlr.substack.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Closing&lt;/p&gt;
&lt;p&gt;This is post 1 of however many it takes. Next post: parsing expressions with operator precedence and why I gave up on Pratt parsing.&lt;/p&gt;
&lt;p&gt;Follow for the crash or the ship. Either way, it's real.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18588/im-building-a-multi-target-compiler-backend-from-scratch-no-llvm-no-crutches</guid>
<pubDate>Thu, 21 May 2026 20:34:21 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 12 — Stack trace</title>
<link>https://coderlegion.com/17200/tuesday-coding-tip-12-stack-trace</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;C++23 brings a new feature that I always wanted since I’ve seen it in C# — stack trace.&lt;/p&gt;
&lt;p&gt;When combined with exceptions, it can immensely help with the analysis of user error reports, when you cannot simply attach a debugger. The code snippet shows how to create an exception class that can automatically capture stack trace at the point where it was constructed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;stacktrace&amp;gt;
#include &amp;lt;stdexcept&amp;gt;

class stacktrace_error : public std::runtime_error
{
public:
    stacktrace_error(std::stacktrace trace = std::stacktrace::current())
        : std::runtime_error(std::to_string(trace))
    {}
};

void bar() { throw stacktrace_error(); }
void foo() { bar(); }

int main()
{
    try
    {
        foo();
    }
    catch (std::exception&amp;amp; e)
    {
        std::cerr &amp;lt;&amp;lt; e.what() &amp;lt;&amp;lt; std::endl;
        /* OUTPUTS:
        0&amp;gt; C:\...\Main.cpp(19): Sandbox!bar+0x44
        1&amp;gt; C:\...\Main.cpp(25): Sandbox!foo+0x20
        2&amp;gt; C:\...\Main.cpp(31): Sandbox!main+0x32
        3&amp;gt; D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(79): Sandbox!invoke_main+0x39
        4&amp;gt; D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288): Sandbox!__scrt_common_main_seh+0x12E
        5&amp;gt; D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(331): Sandbox!__scrt_common_main+0xE
        6&amp;gt; D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp(17): Sandbox!mainCRTStartup+0xE
        7&amp;gt; KERNEL32!BaseThreadInitThunk+0x14
        8&amp;gt; ntdll!RtlUserThreadStart+0x21
        */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API even allows you to iterate over each stack entry, but honestly — who needs that?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Linux versions of GCC and Clang have this stupid idea of requiring you to manually link stacktrace (akin to math library), because &lt;code&gt;-std=c++23&lt;/code&gt; is apparently not enough of a feature flag. So don't forget to use &lt;code&gt;-lstdc++_libbacktrace&lt;/code&gt; argument for compilation.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17200/tuesday-coding-tip-12-stack-trace</guid>
<pubDate>Tue, 19 May 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Building RedxChess: A Neural-Network Chess Engine with Bitboard Move Generation</title>
<link>https://coderlegion.com/17946/building-redxchess-a-neural-network-chess-engine-with-bitboard-move-generation</link>
<description>&lt;p&gt;Chess engines sit at a fun intersection — they're a classic AI problem with decades of refinement, but they're also a playground for modern techniques. I wanted to build something that respected the old guard (Stockfish-style search) while leaning on neural-network evaluation for positional judgment. The result is RedxChess, a UCI-compatible engine you can play against in the browser: &lt;a rel=&quot;nofollow&quot; href=&quot;https://topstar-ai.github.io/play&quot;&gt;https://topstar-ai.github.io/play&lt;/a&gt;&lt;br&gt;
The stack&lt;/p&gt;
&lt;p&gt;Python + C++: Python for the orchestration, training, and UCI protocol layer. C++ for the hot path — move generation and search.&lt;br&gt;
Bitboards: Each piece type gets a 64-bit integer where set bits represent occupied squares. Move generation becomes bitwise math instead of array iteration, which is dramatically faster.&lt;br&gt;
Neural-network evaluation: Replaces hand-tuned material + positional heuristics. The network scores positions; the search tree uses those scores.&lt;br&gt;
UCI protocol: Standard interface, so the engine plugs into any UCI-compatible GUI (Arena, Cute Chess, lichess-bot, etc.).&lt;/p&gt;
&lt;p&gt;Search techniques&lt;br&gt;
The engine implements the modern toolkit:&lt;/p&gt;
&lt;p&gt;Alpha-beta pruning with quiescence search to avoid the horizon effect on tactical positions&lt;br&gt;
Null-move pruning — assume the opponent passes; if we're still winning, the branch is likely refutable&lt;br&gt;
Late-move reductions (LMR) — search later moves at shallower depth, since they're statistically less likely to be best&lt;/p&gt;
&lt;p&gt;What I learned&lt;/p&gt;
&lt;p&gt;Bitboards aren't just a performance trick — they make certain rules (attacks, pins, castling rights) cleaner to express than mailbox representations.&lt;br&gt;
Wiring a neural net into the search is mostly a latency problem. Evaluation calls dominate runtime, so batching and quantization matter as much as the architecture.&lt;br&gt;
UCI is delightfully boring. Once it works, every chess tool in the world becomes a debugger.&lt;/p&gt;
&lt;p&gt;Try it&lt;br&gt;
Play here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://topstar-ai.github.io/play&quot;&gt;https://topstar-ai.github.io/play&lt;/a&gt;&lt;br&gt;
Happy to dig into any of the search or evaluation details in the comments. If you've built an engine, I'd love to hear what you'd do differently.&lt;/p&gt;
&lt;p&gt;chess-engine, ai, python, cpp, neural-networks, bitboards, game-development&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17946/building-redxchess-a-neural-network-chess-engine-with-bitboard-move-generation</guid>
<pubDate>Fri, 15 May 2026 11:19:04 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 10— CTest</title>
<link>https://coderlegion.com/15918/tuesday-coding-tip-10-ctest</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;CMake can do many things and instrumenting your test suite is one of them. Use &lt;code&gt;add_test&lt;/code&gt; to tell cmake it should run a particular command with given parameters. If it returns zero, it succeeded. You can use it to simply run a testrunner binary or to make a matrix of input parameters that are expected to not crash the program.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmake_minimum_required ( VERSION 3.26 )

project ( testrunner )
add_executable ( ${PROJECT_NAME} Main.cpp )

enable_testing()
add_test(
   NAME &quot;Example with no params&quot;
   COMMAND ${PROJECT_NAME}
)

add_test(
   NAME &quot;Example with help param&quot;
   COMMAND ${PROJECT_NAME} -h
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to &lt;code&gt;add_test&lt;/code&gt; do anything, one has to also call &lt;code&gt;enable_testing()&lt;/code&gt;. Running the tests is just a matter of running ctest from command line. Just note that ctest requires you to specify a build configuration if you have more than one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4332370757284522768&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15918/tuesday-coding-tip-10-ctest</guid>
<pubDate>Tue, 05 May 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 09 — Source location</title>
<link>https://coderlegion.com/15917/tuesday-coding-tip-09-source-location</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you want to log a filename and line number prior to C++20, you need to use the likes of &lt;code&gt;__FILE__&lt;/code&gt; and &lt;code&gt;__LINE__&lt;/code&gt; macros. To use those easily, you have to create a wrapper macro for logging, which usually leads to an ugly macro bonanza.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;format&amp;gt;

void log(
    const std::string&amp;amp; message,
    const std::string&amp;amp; fileName = __FILE__,
    unsigned lineNo = __LINE__)
{
    std::cout &amp;lt;&amp;lt; std::format(
        &quot;{}({}): {}\n&quot;,
        fileName,
        lineNo,
        message);
}

#define LOG(msg) log(msg, __FILE__, __LINE__)

int main()
{
    log(&quot;hello world&quot;);
    // logs: example.cpp(7): hello world
    // Note that __FILE__ and __LINE__ are expanded at their
    // position, not at the callsite
    LOG(&quot;hello world2&quot;);
    // logs: example.cpp(22): hello world2
    // Wrapping the call in another macro with explicit placement
    // of __FILE__ and __LINE__ makes them expand at the correct place
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++20 introduces &lt;code&gt;std::source_location&lt;/code&gt;, a regular object that can be constructed as a default value of a logging function parameter and passed along as the user needs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;source_location&amp;gt;
#include &amp;lt;format&amp;gt;

void log(
    const std::string&amp;amp; message,
    std::source_location location = std::source_location::current())
{
    std::cout &amp;lt;&amp;lt; std::format(
        &quot;{}({}) {}: {}\n&quot;,
        location.file_name(),
        location.line(),
        location.function_name(),
        message);
}

int main()
{
    log(&quot;hello world&quot;);
    // logs: example.cpp(19) int main(): hello world
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15917/tuesday-coding-tip-09-source-location</guid>
<pubDate>Tue, 28 Apr 2026 09:59:00 +0000</pubDate>
</item>
<item>
<title>UltrafastSecp256k1 v3.67.0</title>
<link>https://coderlegion.com/15480/ultrafastsecp256k1-v3-67-0</link>
<description>&lt;p&gt;v3.67.0 — CAAS hardening, multi-CI reproducible builds, OpenSSF Scorecard cleanup, Zenodo + funding outreach&lt;br&gt;
This release closes the remaining CAAS gap-closure roadmap, lands the multi-CI reproducible-build attestation surface, brings the OpenSSF Scorecard code-scanning queue to zero open alerts, and pre-stages Zenodo metadata + a funding-outreach playbook. No public-API or ABI breakage; no behavioral changes to the crypto engine.&lt;/p&gt;
&lt;p&gt;Highlights&lt;br&gt;
Continuous Audit (CAAS) — roadmap complete&lt;br&gt;
All eleven CAAS hardening items (H-1 … H-11) closed in a single sweep.&lt;br&gt;
Gap-closure roadmap G-1 … G-10 closed: threat model, RNG entropy attestation, hardware side-channel methodology, compliance stance, INTEROP matrix, multi-CI reproducible builds, CT-tool independence, SPEC traceability matrix, protocol spec, traceability-join gate, RFC 9116.&lt;br&gt;
scripts/audit_gate.py now wires G-1 / G-1b / G-8 / G-10 as first-class sub-gates; caas_runner.py is fail-fast across all five stages.&lt;br&gt;
scripts/check_exploit_wiring.py enforces the Exploit / Audit Test Conversion Standard at CAAS Stage 0 — every audit/test&lt;em&gt;exploit&lt;/em&gt;*.cpp file must be wired into unified_audit_runner.cpp or the gate refuses the push.&lt;br&gt;
Audit dashboard refreshed: hardening progress 12/12 (100%).&lt;br&gt;
Multi-CI reproducible-build attestation&lt;br&gt;
.gitlab-ci.yml and .woodpecker.yml (Codeberg) added alongside the existing GitHub Actions surface — the same release artifact is now built on three independent CI providers and verified byte-identical via SHA-256.&lt;br&gt;
docs/MULTI_CI_REPRODUCIBLE_BUILD.md documents the protocol; docs/SUPPLY_CHAIN_LOCAL_PARITY.md describes the local-parity check.&lt;br&gt;
INTEROP differential testing&lt;br&gt;
First INTEROP §2 reference wired: OpenSSL 3.x random-vector differential PoC (audit/test_exploit_differential_openssl.cpp), advisory module, host-only.&lt;br&gt;
INTEROP §3 closure tracked for k256 (Rust), btcd (Go), BoringSSL, WolfSSL, NSS, MuSig2 wire, FROST wire.&lt;br&gt;
OpenSSF Scorecard — code-scanning queue cleared&lt;br&gt;
All open code-scanning alerts on main resolved.&lt;br&gt;
PinnedDependenciesID (6): every GitHub Action pinned by 40-character SHA in mutation-weekly.yml, rocm-smoke.yml, and formal-verification.yml; pip installs in those workflows switched to pip install --require-hashes -r ... against new hash-pinned requirement files.&lt;br&gt;
TokenPermissionsID (1 fixed in code): caas-evidence-refresh.yml top-level contents: write dropped to read; write retained only at the job level where the workflow actually pushes refreshed evidence back to dev.&lt;br&gt;
Six remaining TokenPermissionsID alerts dismissed as legitimate job-scoped writes (release sync-docs, audit-report publish to gh-pages, bench-regression baseline push, three ClusterFuzzLite SARIF uploads).&lt;br&gt;
BranchProtectionID resolved at config level: main-protection ruleset hardened — bypass_actors=, required_approving_review_count=2, require_code_owner_review=true, require_last_push_approval=true, dismiss_stale_reviews_on_push=true.&lt;br&gt;
Crypto bug-pattern scanner&lt;br&gt;
13 CVE-grounded pattern checkers added to scripts/dev_bug_scanner.py (timing-dependent branches on secret data, missing zeroization, RFC 6979 misuse patterns, ECDSA nonce reuse signatures, scalar-mod-n omission, …).&lt;br&gt;
False-positive reduction pass across eight checkers; investigation report at docs/SCANNER_INVESTIGATION_REPORT.md.&lt;br&gt;
GPU backend parity&lt;br&gt;
schnorr_snark_witness_batch parity gap closed via deterministic host-side CPU fallback in gpu/src/gpu_backend_fallback.cpp — CUDA, OpenCL, and Metal now all return correct byte-identical results. Native GPU kernels remain a future optimisation; public-data-only operation.&lt;br&gt;
Cross-compile / CI fixes&lt;br&gt;
arm64 + riscv64 cross-compile no longer picks up host OpenSSL headers via bare &lt;strong&gt;has_include. The OpenSSL gate is now driven by a CMake UFSECP_HAVE_OPENSSL=1 define that is only set when find_package(OpenSSL) actually links — the source guard requires both the define and &lt;/strong&gt;has_include.&lt;br&gt;
-Werror build no longer trips on OpenSSL EC_KEY deprecation warnings (pragma block scoped to the differential PoC).&lt;br&gt;
CAAS verdict accepts both PASS and PASS with advisory.&lt;br&gt;
Visibility surface&lt;br&gt;
.zenodo.json added — academic metadata staged for the next release; the next release-tag push will trigger Zenodo archival and DOI minting (Zenodo↔GitHub OAuth toggle is enabled).&lt;br&gt;
README.md &quot;Cite this work&quot; section + DOI badge placeholder.&lt;br&gt;
CITATION.cff linked from &quot;Where to Start&quot;; docs/ADOPTION.md and docs/FUNDING_TARGETS.md linked next to it.&lt;br&gt;
docs/FUNDING_TARGETS.md — funding playbook covering Bitcoin grant programmes (HRF, OpenSats, Brink, Spiral, Strike Catalyst, MIT DCI), Ethereum programmes (EF ESP, Protocol Guild, EF Academic, Optimism RetroPGF, Arbitrum, Coinbase / Base), EU / cross-cutting (NLnet NGI Zero, Sovereign Tech Fund, OSTIF, GitHub Accelerator, a16z crypto Open Source Grants), with 30-second + 5-minute pitches and an evidence-pointer column.&lt;br&gt;
README.md hero block now carries explicit CTAs: a one-line invitation for production users to PR themselves into docs/ADOPTION.md, and a one-line pointer for prospective sponsors to docs/FUNDING_TARGETS.md.&lt;br&gt;
Documentation reconciliation&lt;br&gt;
Exploit-PoC counts reconciled across all audit docs to the real number (189).&lt;br&gt;
Non-exploit module + CI workflow counts reconciled to reality.&lt;br&gt;
docs/SPEC_TRACEABILITY_MATRIX.md paths reconciled; traceability-join gate flipped to strict by default.&lt;br&gt;
Compatibility&lt;br&gt;
C ABI: unchanged.&lt;br&gt;
Public C++ API: unchanged.&lt;br&gt;
GPU backend GpuBackend virtual interface: one method (schnorr_snark_witness_batch) gained a deterministic CPU fallback; previously returned Unsupported on every backend.&lt;br&gt;
Reproducible build: byte-identical to v3.66.0 for the engine + library outputs; only CI / docs / audit-tooling surface changed.&lt;br&gt;
Adoption&lt;br&gt;
Sparrow Wallet Frigate ships UltrafastSecp256k1 by default since 1.4.0. See docs/ADOPTION.md for the integration details and Craig Raw's independent benchmarks.&lt;/p&gt;
&lt;p&gt;Cite this work&lt;br&gt;
This release will be archived on Zenodo with a DOI assigned automatically once the tag push completes. See .zenodo.json and CITATION.cff.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shrec/UltrafastSecp256k1/releases/tag/v3.67.0&quot;&gt;https://github.com/shrec/UltrafastSecp256k1/releases/tag/v3.67.0&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/shrec/UltrafastSecp256k1&quot;&gt;https://github.com/shrec/UltrafastSecp256k1&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15480/ultrafastsecp256k1-v3-67-0</guid>
<pubDate>Wed, 22 Apr 2026 09:10:44 +0000</pubDate>
</item>
<item>
<title>Tuesday Coding Tip 08 — Explicit template specialization</title>
<link>https://coderlegion.com/14710/tuesday-coding-tip-08-explicit-template-specialization</link>
<description>&lt;p&gt;Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;While template instantiation can be used to move your template implementation from header files to object files for a given subset of types, specialization allows you to override the default template implementation for a particular type.&lt;/p&gt;
&lt;p&gt;Specialization requires that you first define the template implementation and then you can implement any number of specializations for given types. This way, you can override free functions (while maintaining their overall signature) or you can override whole classes (and you don’t have to respect their interfaces). This way, you can create drop-in replacements for library containers specialized for particular types and try to optimize them for your use case.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;filesystem&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

template&amp;lt;class T&amp;gt;
T concat(T a, const T&amp;amp; b)
{
    return a + b;
}

namespace fs = std::filesystem;

fs::path concat(fs::path a, const fs::path&amp;amp; b)
{
    return a / b;
}

int main()
{
    using namespace std::string_literals;
    std::cout &amp;lt;&amp;lt; concat(&quot;aaa&quot;s, &quot;bbb&quot;s) &amp;lt;&amp;lt; std::endl;        // outputs: aaabbb
    std::cout &amp;lt;&amp;lt; concat(&quot;aaa&quot;, &quot;bbb&quot;).string() &amp;lt;&amp;lt; std::endl; // outputs: aaa\bbb
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The specialization can also be partial and it can be &lt;code&gt;inline&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt; whatever even if the original template is not.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

template&amp;lt;class T&amp;gt;
struct CustomType
{
    T foo() { return T {}; }
};

template&amp;lt;&amp;gt;
struct CustomType&amp;lt;std::string&amp;gt;
{
    std::string bar() { return &quot;hello world&quot;; }
};

int main()
{
    CustomType&amp;lt;int&amp;gt; ct1;
    std::cout &amp;lt;&amp;lt; ct1.foo() &amp;lt;&amp;lt; std::endl; // outputs: 0
    CustomType&amp;lt;std::string&amp;gt; ct2;
    std::cout &amp;lt;&amp;lt; ct2.bar() &amp;lt;&amp;lt; std::endl; // outputs: &quot;hello world&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last but not least, if you’re implementing a template and need to change only a few lines of implementation for one particular type, you don’t need specialization. Instead, use &lt;code&gt;constexpr&lt;/code&gt; conditions — they are evaluated in compile time and optimized away when the compiler emits its own specialized code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;concepts&amp;gt;
#include &amp;lt;filesystem&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

template&amp;lt;class T&amp;gt;
T concat(T a, const T&amp;amp; b)
{
    if constexpr (std::is_same_v&amp;lt;T, std::filesystem::path&amp;gt;)
        return a / b;
    else
        return a + b;
}

int main()
{
    using namespace std::string_literals;
    using path = std::filesystem::path;
    std::cout &amp;lt;&amp;lt; concat(&quot;aaa&quot;s, &quot;bbb&quot;s) &amp;lt;&amp;lt; std::endl; // outputs: aaabbb
    std::cout &amp;lt;&amp;lt; concat(path{ &quot;aaa&quot; }, path{ &quot;bbb&quot; }).string()
              &amp;lt;&amp;lt; std::endl; // outputs: aaa\bbb
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14710/tuesday-coding-tip-08-explicit-template-specialization</guid>
<pubDate>Tue, 21 Apr 2026 09:59:00 +0000</pubDate>
</item>
</channel>
</rss>