<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Rosito</title>
    <description>The latest articles on DEV Community by Alex Rosito (@alexrosito67).</description>
    <link>https://dev.to/alexrosito67</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3903394%2F8e90cfff-2f31-4948-ad06-ae6c14594087.png</url>
      <title>DEV Community: Alex Rosito</title>
      <link>https://dev.to/alexrosito67</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexrosito67"/>
    <language>en</language>
    <item>
      <title>I Built a Useless Data Structure in C++17 and Learned More Than Any Tutorial Taught Me</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Wed, 29 Apr 2026 05:39:57 +0000</pubDate>
      <link>https://dev.to/alexrosito67/i-built-a-useless-data-structure-in-c17-and-learned-more-than-any-tutorial-taught-me-e0i</link>
      <guid>https://dev.to/alexrosito67/i-built-a-useless-data-structure-in-c17-and-learned-more-than-any-tutorial-taught-me-e0i</guid>
      <description>&lt;p&gt;&lt;em&gt;I built this because I was bored. No real use case, no production target — just a C++17 exercise to shake off the rust and explore techniques I knew existed but had never actually used. What I didn't expect was how much the implementation taught me about things I thought I already understood.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Homogeneous Lists
&lt;/h2&gt;

&lt;p&gt;Every linked list tutorial shows you the same thing: a list of integers, or a list of strings. The node holds one type, the list holds nodes of that type, done.&lt;/p&gt;

&lt;p&gt;That works — until you want a single list to hold an integer, a float, a string, and a custom struct at the same time. Then the standard approach falls apart immediately, because the type is baked into the node at compile time.&lt;/p&gt;

&lt;p&gt;The question becomes: how do you build a node that doesn't care what it holds?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Polymorphic Base — The "Wow" Moment
&lt;/h2&gt;

&lt;p&gt;The answer is a polymorphic base class. Instead of a templated node that holds &lt;code&gt;T&lt;/code&gt; directly, you separate the concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base class — type-erased, polymorphic&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Derived class — holds the actual value&lt;/span&gt;
&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;explicit&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;otherNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;dynamic_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;otherNode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;otherNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Object&lt;/code&gt; knows nothing about the type it holds — it only defines the interface. &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; knows the type, stores the value, and implements the interface.&lt;/p&gt;

&lt;p&gt;The list holds &lt;code&gt;Object*&lt;/code&gt; pointers. From the list's perspective, every node is an &lt;code&gt;Object&lt;/code&gt;. The actual type is invisible at that level — which is exactly what allows the list to be heterogeneous.&lt;/p&gt;

&lt;p&gt;When I saw this working for the first time — a single list printing an integer, then a float, then a string, then a struct, all through the same &lt;code&gt;listPrint()&lt;/code&gt; call — it was genuinely surprising. Not because it's magic, but because the abstraction is so clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;dynamic_cast&lt;/code&gt; — Type-Safe Downcasting at Runtime
&lt;/h2&gt;

&lt;p&gt;This is the part that was completely new to me, and worth documenting carefully.&lt;/p&gt;

&lt;p&gt;Once you have a list of &lt;code&gt;Object*&lt;/code&gt; pointers, you lose type information at the list level. That's fine for printing — &lt;code&gt;print()&lt;/code&gt; is virtual, it dispatches correctly. But what about searching? If I want to find the integer &lt;code&gt;42&lt;/code&gt; in a mixed list, I need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Skip nodes that aren't integers&lt;/li&gt;
&lt;li&gt;Compare only against nodes that actually hold &lt;code&gt;int&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is exactly what &lt;code&gt;dynamic_cast&lt;/code&gt; does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;searchNodeInList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;typedNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;dynamic_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typedNode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;typedNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dynamic_cast&amp;lt;Node&amp;lt;T&amp;gt;*&amp;gt;(current)&lt;/code&gt; attempts to cast the &lt;code&gt;Object*&lt;/code&gt; pointer to a &lt;code&gt;Node&amp;lt;T&amp;gt;*&lt;/code&gt;. If the actual runtime type of the object is &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; — it succeeds and returns a valid pointer. If the actual type is something else — &lt;code&gt;Node&amp;lt;float&amp;gt;&lt;/code&gt;, &lt;code&gt;Node&amp;lt;std::string&amp;gt;&lt;/code&gt; — it returns &lt;code&gt;nullptr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;nullptr&lt;/code&gt; check is the type filter. No exceptions, no undefined behavior — just a null pointer when the types don't match.&lt;/p&gt;

&lt;p&gt;This is the critical distinction from &lt;code&gt;static_cast&lt;/code&gt;: &lt;code&gt;static_cast&lt;/code&gt; trusts you at compile time. &lt;code&gt;dynamic_cast&lt;/code&gt; verifies at runtime. In a heterogeneous container where you genuinely don't know the runtime type of a node, &lt;code&gt;dynamic_cast&lt;/code&gt; is the correct tool — &lt;code&gt;static_cast&lt;/code&gt; would be undefined behavior waiting to happen.&lt;/p&gt;

&lt;p&gt;One requirement: the base class must have at least one virtual function. Without it, the compiler doesn't generate the runtime type information (RTTI) needed for &lt;code&gt;dynamic_cast&lt;/code&gt; to work. The virtual destructor in &lt;code&gt;Object&lt;/code&gt; satisfies this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operator Overloading for Custom Types — Still Somewhat Esoteric
&lt;/h2&gt;

&lt;p&gt;For built-in types like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, and &lt;code&gt;std::string&lt;/code&gt;, everything works out of the box — &lt;code&gt;operator&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;operator==&lt;/code&gt; are already defined. But for a custom struct, you have to teach the compiler how to handle it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Teach std::ostream how to print myStruct&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Teach the compiler how to compare two myStructs&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;operator&amp;lt;&amp;lt;&lt;/code&gt; overload is what allows &lt;code&gt;Node&amp;lt;myStruct&amp;gt;::print()&lt;/code&gt; to call &lt;code&gt;os &amp;lt;&amp;lt; data&lt;/code&gt; without knowing anything about &lt;code&gt;myStruct&lt;/code&gt; at compile time — as long as that overload exists, the template resolves correctly.&lt;/p&gt;

&lt;p&gt;The stream-based syntax (&lt;code&gt;os &amp;lt;&amp;lt; s.x &amp;lt;&amp;lt; " " &amp;lt;&amp;lt; s.y&lt;/code&gt;) still feels somewhat counter-intuitive — you're chaining calls on an object that represents an output stream, returning a reference to itself at each step so the chain can continue. It works, it's idiomatic C++, but it takes some time before it stops feeling like incantation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;operator==&lt;/code&gt; overload is what allows &lt;code&gt;dynamic_cast&lt;/code&gt; plus value comparison to work in &lt;code&gt;searchNodeInList&lt;/code&gt;. Without it, &lt;code&gt;typedNode-&amp;gt;data == value&lt;/code&gt; wouldn't compile for custom types.&lt;/p&gt;

&lt;p&gt;The requirement is clear: any type you want to store in &lt;code&gt;dllist&lt;/code&gt; must implement both. This is enforced at compile time — if you try to store a type that doesn't have them, the compiler tells you exactly which operator is missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory Management — Clearer Than Its Reputation
&lt;/h2&gt;

&lt;p&gt;Manual memory management in C++ has a reputation for being treacherous. In practice, for a data structure with a clear ownership model, it's straightforward — as long as you follow one rule: whoever allocates, deallocates.&lt;/p&gt;

&lt;p&gt;Insertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;listAppend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// heap allocation&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion of a single node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;deleteNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// heap deallocation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Destructor — cleans up everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::~&lt;/span&gt;&lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail in the destructor: save &lt;code&gt;current-&amp;gt;next&lt;/code&gt; before deleting &lt;code&gt;current&lt;/code&gt;. After &lt;code&gt;delete current&lt;/code&gt;, that memory is gone — accessing &lt;code&gt;current-&amp;gt;next&lt;/code&gt; afterward is undefined behavior. One line of discipline prevents the entire class of use-after-free errors.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;delete&lt;/code&gt; on an &lt;code&gt;Object*&lt;/code&gt; that points to a &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; calls the correct destructor because &lt;code&gt;~Object()&lt;/code&gt; is virtual. Without the virtual destructor, &lt;code&gt;delete&lt;/code&gt; on a base pointer would only call the base destructor — leaking whatever the derived class allocated. This is another reason the virtual destructor in &lt;code&gt;Object&lt;/code&gt; is not optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Exercise Actually Taught Me
&lt;/h2&gt;

&lt;p&gt;Building something redundant — &lt;code&gt;std::list&lt;/code&gt; and &lt;code&gt;std::variant&lt;/code&gt; already cover this problem more efficiently — turned out to be more instructive than using the standard library version would have been.&lt;/p&gt;

&lt;p&gt;The standard library hides the machinery. Building it yourself forces you to confront exactly why each piece exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The polymorphic base exists because templates alone can't provide runtime type erasure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamic_cast&lt;/code&gt; exists because downcasting without runtime verification is unsafe by definition&lt;/li&gt;
&lt;li&gt;The virtual destructor exists because &lt;code&gt;delete&lt;/code&gt; on a base pointer without it is undefined behavior&lt;/li&gt;
&lt;li&gt;Operator overloading exists because templates need the compiler to resolve operations on &lt;code&gt;T&lt;/code&gt; — and for custom types, you have to tell it how&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is new knowledge in the sense that it's in every C++ book. But there's a difference between reading about it and building something where removing any one of these pieces breaks the entire structure in a specific and instructive way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/AlexRosito67/heterogeneous-double-linked-list" rel="noopener noreferrer"&gt;https://github.com/AlexRosito67/heterogeneous-double-linked-list&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pure C++17, no dependencies, CMake build&lt;/li&gt;
&lt;li&gt;Intended as a learning reference, not a production library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this was useful, there's a &lt;a href="https://buymeacoffee.com/AlexRosito67" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; link on the GitHub page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer and C++ developer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>programming</category>
      <category>computerscience</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why Most 74HC595 Display Drivers Flicker (And How QUAD7SHIFT Avoids It Without Trying)</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Wed, 29 Apr 2026 02:51:26 +0000</pubDate>
      <link>https://dev.to/alexrosito67/why-most-74hc595-display-drivers-flicker-and-how-quad7shift-avoids-it-without-trying-50eb</link>
      <guid>https://dev.to/alexrosito67/why-most-74hc595-display-drivers-flicker-and-how-quad7shift-avoids-it-without-trying-50eb</guid>
      <description>&lt;p&gt;&lt;em&gt;A Chinese technical site cited QUAD7SHIFT as a flicker-free reference implementation. I didn't design it to solve flicker. I just built it correctly.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Search for "74HC595 7-segment display Arduino" and you'll find dozens of tutorials. Most of them work — in the sense that the display shows numbers. But watch closely under fluorescent lighting, or point a camera at it, and you'll see it: a faint but persistent flicker, sometimes a ghost of the previous digit bleeding into the next.&lt;/p&gt;

&lt;p&gt;This isn't a power supply issue. It isn't a loose connection. It's a structural problem in how most drivers handle the latch.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Naive Pattern Works
&lt;/h2&gt;

&lt;p&gt;The typical approach looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Naive pattern — found in most tutorials&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;showDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;digitSelect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCH_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;shiftOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATA_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLOCK_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// first transfer&lt;/span&gt;
    &lt;span class="n"&gt;shiftOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATA_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLOCK_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digitSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// second transfer&lt;/span&gt;
    &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCH_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two separate &lt;code&gt;shiftOut()&lt;/code&gt; calls, then a single latch pulse. Looks fine. The problem is what happens &lt;em&gt;between&lt;/em&gt; those two calls.&lt;/p&gt;

&lt;p&gt;When the first &lt;code&gt;shiftOut()&lt;/code&gt; completes, the shift register holds the new segment data — but the latch hasn't fired yet. At that exact moment, the &lt;em&gt;previous&lt;/em&gt; digit select is still active in the storage register. If anything delays the second &lt;code&gt;shiftOut()&lt;/code&gt; — an interrupt, a timer ISR, a millisecond of jitter — the display briefly shows the new segment pattern on the wrong digit.&lt;/p&gt;

&lt;p&gt;That's ghosting. And even without interrupts, the asymmetry in timing between two sequential software calls is enough to produce uneven brightness across digits.&lt;/p&gt;

&lt;p&gt;There's a deeper issue underneath this: &lt;code&gt;shiftOut()&lt;/code&gt; is pure software bit-banging. &lt;br&gt;
It drives the clock and data pins manually, one bit at a time, in a tight loop — with no hardware assistance and no atomicity guarantees. On any AVR running with interrupts enabled (which is the default — &lt;code&gt;millis()&lt;/code&gt; depends on it), a timer ISR can fire between any two clock pulses. The transfer is not protected. This makes the window between the two &lt;code&gt;shiftOut()&lt;/code&gt; calls not just a timing inconvenience but a structural vulnerability: the longer and more interrupt-prone your environment, the more likely you are to see artifacts.&lt;/p&gt;

&lt;p&gt;Hardware SPI — used by QUAD7SHIFT via &lt;code&gt;SPI.transfer16()&lt;/code&gt; — is handled by a dedicated peripheral that runs independently of the CPU. It cannot be interrupted mid-transfer by software. The 16 bits go out clean, every time.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Timing Problem Visualized
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Naive approach — two 74HC595 chips, sequential transfers:

Time →

LATCH:   ___LOW_________________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑  ↑↑↑↑↑↑↑↑
DATA:    [segments byte ] [digit select byte ]
                         ↑
                    HERE: shift reg 1 has new segments.
                    Storage reg still holds OLD digit select.
                    Any interrupt here = ghost on wrong digit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUAD7SHIFT — single 16-bit atomic transfer:

Time →

LATCH:   ___LOW___________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
DATA:    [segments byte | digit select byte]
                                 ↑
                    Both bytes shift as one unit.
                    Latch fires once. No intermediate state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What QUAD7SHIFT Does Instead
&lt;/h2&gt;

&lt;p&gt;The hardware is two 74HC595s in cascade — one controls the segment lines (a–g, dp), the other controls which digit is active (digit select bits). The key is that both chips share a single latch line (RCLK).&lt;/p&gt;

&lt;p&gt;The entire transfer is packed into a single &lt;code&gt;uint16_t&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;transferDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cp"&gt;#if defined(__AVR_ATmega328P__) || (__AVR_ATmega168__)
&lt;/span&gt;        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPISettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SPI_MODE0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transfer16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// both bytes in one SPI transaction&lt;/span&gt;
        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="cp"&gt;#endif
&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// single latch pulse — both registers update simultaneously&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SPI.transfer16()&lt;/code&gt; shifts all 16 bits in one continuous hardware transaction. The latch fires exactly once. There is no intermediate state where one register has new data and the other doesn't.&lt;/p&gt;

&lt;p&gt;For ATtiny85, which lacks hardware SPI and uses the USI (Universal Serial Interface), the same principle applies — two &lt;code&gt;usiTransferByte()&lt;/code&gt; calls back to back with the latch held LOW throughout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;transferDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;usiTransferByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverseBits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;         &lt;span class="c1"&gt;// segments&lt;/span&gt;
    &lt;span class="n"&gt;usiTransferByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverseBits&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// digit select&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The latch is LOW for the entire duration of both transfers. The storage registers don't see anything until the latch goes HIGH — at which point both update atomically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Works: The 74HC595 Latch Mechanism
&lt;/h2&gt;

&lt;p&gt;The 74HC595 has two internal registers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift register&lt;/strong&gt; — receives serial data on each SRCLK rising edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage register&lt;/strong&gt; — holds the parallel output, only updates on RCLK rising edge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When LATCH (RCLK) is LOW, data shifting into the shift register has zero effect on the outputs. The outputs are frozen. You can shift as much data as you want — through one chip, through ten chips in cascade — and nothing changes on the output pins until you pulse the latch HIGH.&lt;/p&gt;

&lt;p&gt;This is the mechanism. The naive pattern doesn't violate it, but it &lt;em&gt;does&lt;/em&gt; use it sloppily — latching between two logical operations instead of after both are complete.&lt;/p&gt;

&lt;p&gt;QUAD7SHIFT latches once, after all 16 bits are in place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Multiplexing Loop
&lt;/h2&gt;

&lt;p&gt;Eliminating ghosting on each individual transfer is necessary but not sufficient. You also need a stable refresh cycle.&lt;/p&gt;

&lt;p&gt;The naive approach typically calls the display function from &lt;code&gt;loop()&lt;/code&gt;, which means refresh rate is coupled to whatever else &lt;code&gt;loop()&lt;/code&gt; is doing. If you add a sensor read or a serial print, the display dims or flickers because some digits get fewer refresh cycles per second.&lt;/p&gt;

&lt;p&gt;QUAD7SHIFT decouples refresh rate from &lt;code&gt;loop()&lt;/code&gt; timing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;printNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;decimalPointPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... digit extraction ...&lt;/span&gt;

    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;endtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_refreshRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;endtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digitsToPrint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;decimalPointPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;numberOfDigitsToDisplay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each call to &lt;code&gt;print()&lt;/code&gt; runs the multiplexing loop for exactly &lt;code&gt;_refreshRate&lt;/code&gt; milliseconds, cycling through all digits at a consistent rate regardless of what happened before or after. The display gets a guaranteed time slice.&lt;/p&gt;




&lt;h2&gt;
  
  
  See It Running
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2jVDQSVcXQ0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Naive pattern&lt;/th&gt;
&lt;th&gt;QUAD7SHIFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latch pulses per digit update&lt;/td&gt;
&lt;td&gt;1 (but between two transfers)&lt;/td&gt;
&lt;td&gt;1 (after both transfers complete)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intermediate state where ghost can appear&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh rate coupled to &lt;code&gt;loop()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No — fixed time slice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works on ATtiny85&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;td&gt;Yes (USI, bit-reversal handled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware SPI on AVR&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Yes (&lt;code&gt;SPI.transfer16&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  I Didn't Design This to Solve Flicker
&lt;/h2&gt;

&lt;p&gt;Worth being explicit about this: I didn't build QUAD7SHIFT by identifying the ghost problem and engineering a solution. I built it by reading the 74HC595 datasheet, understanding that the latch is what separates "data in transit" from "data on outputs," and constructing the transfer accordingly.&lt;/p&gt;

&lt;p&gt;The flicker never appeared because the design never created the conditions for it.&lt;/p&gt;

&lt;p&gt;A Chinese technical site (CSDN, April 2026) independently analyzed cascaded 74HC595 display drivers and cited QUAD7SHIFT as a reference implementation for eliminating flicker — specifically noting its "dynamic scan algorithm." They found the pattern by looking for the problem. I found it by not creating the problem in the first place.&lt;/p&gt;

&lt;p&gt;Both paths lead to the same place. But I think the second one is more instructive: &lt;strong&gt;correct hardware abstractions tend to be correct in ways you didn't plan for.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Library
&lt;/h2&gt;

&lt;p&gt;QUAD7SHIFT is available on GitHub and in the Arduino Library Manager.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/AlexRosito67/QUAD7SHIFT" rel="noopener noreferrer"&gt;https://github.com/AlexRosito67/QUAD7SHIFT&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supports Arduino Uno, Nano, and ATtiny85&lt;/li&gt;
&lt;li&gt;Common anode and common cathode displays&lt;/li&gt;
&lt;li&gt;Configurable refresh rate&lt;/li&gt;
&lt;li&gt;String display, float, and integer overloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this was useful, there's a &lt;a href="https://buymeacoffee.com/AlexRosito67" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; link on the GitHub page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>embeddedsystems</category>
      <category>cpp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
