title: `std::variant` vs. inheritance vs. other ways (performance)
body: <p>I'm wondering about <code>std::variant</code> performance. When should I not use it? It seems like virtual functions are still much better than using <code>std::visit</code> which surprised me!</p>
  <p>In &quot;A Tour of C++&quot; Bjarne Stroustrup says this about <code>pattern checking</code> after explaining <code>std::holds_alternatives</code> and the <code>overloaded</code> methods:</p>
  <blockquote>
  <p>This is basically equivalent to a virtual function call, but potentially faster. As with all claims of
  performance, this ‘‘potentially faster’’ should be verified by measurements when performance is
  critical. For most uses, the difference in performance is insignificant.</p>
  </blockquote>
  <p>I've benchmark some methods that came in my mind and these are the results:</p>
  <p><a href="https://i.stack.imgur.com/1RASu.png" rel="noreferrer"><img src="https://i.stack.imgur.com/1RASu.png" alt="benchmark" /></a>
  <a href="http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg" rel="noreferrer">http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg</a></p>
  <p>You'll get a different result if you turn on the optimization:</p>
  <p><a href="https://i.stack.imgur.com/vFXCy.png" rel="noreferrer"><img src="https://i.stack.imgur.com/vFXCy.png" alt="benchmark with op enabled" /></a></p>
  <p><a href="http://quick-bench.com/p6KIUtRxZdHJeiFiGI8gjbOumoc" rel="noreferrer">http://quick-bench.com/p6KIUtRxZdHJeiFiGI8gjbOumoc</a></p>
  <p>Here's the code I've used for benchmarks; I'm sure there's better way to implement and use variants for using them instead of virtual keywords (<a href="http://cpptruths.blogspot.com/2018/02/inheritance-vs-stdvariant-based.html" rel="noreferrer">inheritance vs. std::variant</a>):</p>
  <p><em>removed the old code; look at the updates</em></p>
  <p>Can anyone explain what is the best way to implement this use case for <code>std::variant</code> that got me to testing and benchmarking:</p>
  <p>I'm currently implementing <a href="https://www.rfc-editor.org/rfc/rfc3986" rel="noreferrer">RFC 3986</a> which is 'URI' and for my use case this class will be used more as a const and probably won't be changed a lot and it's more likely for the user to use this class to find each specific portion of the URI rather than making a URI; so it made sense to make use of <code>std::string_view</code> and not separating each segment of the URI in its own <code>std::string</code>. The problem was I needed to implement two classes for it; one for when I only need a const version; and another one for when the user wants to create the URI rather than providing one and searching through it.</p>
  <p>So I used a <code>template</code> to fix that which had its own problems; but then I realized I could use <code>std::variant&lt;std::string, std::string_view&gt;</code> (or maybe <code>std::variant&lt;CustomStructHoldingAllThePieces, std::string_view&gt;</code>); so I started researching to see if it actually helps to use variants or not. From these results, it seems like using inheritance and <code>virtual</code> is my best bet if I don't want to implement two different <code>const_uri</code> and <code>uri</code> classes.</p>
  <p>What do you think should I do?</p>
  <hr />
  <h2>Update (2)</h2>
  <p>Thanks for @gan_ for mentioning and fixing the hoisting problem in my benchmark code.
  <a href="https://i.stack.imgur.com/HgZva.png" rel="noreferrer"><img src="https://i.stack.imgur.com/HgZva.png" alt="benchmark" /></a>
  <a href="http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY" rel="noreferrer">http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY</a></p>
  <p>I was surprised with the result of try-catch hell but thanks to <a href="https://stackoverflow.com/questions/57726401/stdvariant-vs-inheritance-vs-other-ways-performance#comment101894261_57726401">this comment</a> that makes sense now.</p>
  <h2>Update (3)</h2>
  <p>I removed the <code>try-catch</code> method as it was really bad; and also randomly changed the selected value and by the looks of it, I see more realistic benchmark. It seems like <code>virtual</code> is not the correct answer after all.
  <a href="https://i.stack.imgur.com/K1MV7.png" rel="noreferrer"><img src="https://i.stack.imgur.com/K1MV7.png" alt="random access" /></a>
  <a href="http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0" rel="noreferrer">http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0</a></p>
  <p><a href="http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs" rel="noreferrer">http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs</a> (without the memory leak lol)</p>
  <h1>Update (4)</h1>
  <p>I removed the overhead of generating random numbers (I've already did that in the last update but it seems like I had grabbed the wrong URL for benchmark) and added an EmptyRandom for understanding the baseline of generating random numbers. And also made some small changes in Virtual but I don't think it affected anything.
  <a href="https://i.stack.imgur.com/OGrUZ.png" rel="noreferrer"><img src="https://i.stack.imgur.com/OGrUZ.png" alt="empty random added" /></a>
  <a href="http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI" rel="noreferrer">http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI</a></p>
  <p><a href="http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw" rel="noreferrer">http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw</a> (removed the Virtual so you could compare the rest of them better)</p>
  <hr />
  <h1>Update (5)</h1>
  <p>as Jorge Bellon <a href="http://#comment102321465_57726401" rel="noreferrer">said</a> in the comments, I wasn't thinking about the cost of allocation; so I converted every benchmark to use pointers. This indirection has an impact on performance of course but it's more fair now. So right now there's no allocation in the loops.</p>
  <p>Here's the code:</p>
  <p><em>removed the old code; look at the updates</em></p>
  <p>I ran some benchmarks so far. It seems like g++ does a better job of optimizing the code:</p>
  <pre><code>-------------------------------------------------------------------
  Benchmark                         Time             CPU   Iterations
  -------------------------------------------------------------------
  EmptyRandom                   0.756 ns        0.748 ns    746067433
  TradeSpaceForPerformance       2.87 ns         2.86 ns    243756914
  Virtual                        12.5 ns         12.4 ns     60757698
  Index                          7.85 ns         7.81 ns     99243512
  GetIf                          8.20 ns         8.18 ns     92393200
  HoldsAlternative               7.08 ns         7.07 ns     96959764
  ConstexprVisitor               11.3 ns         11.2 ns     60152725
  StructVisitor                  10.7 ns         10.6 ns     60254088
  Overload                       10.3 ns         10.3 ns     58591608
  </code></pre>
  <p>And for clang:</p>
  <pre><code>-------------------------------------------------------------------
  Benchmark                         Time             CPU   Iterations
  -------------------------------------------------------------------
  EmptyRandom                    1.99 ns         1.99 ns    310094223
  TradeSpaceForPerformance       8.82 ns         8.79 ns     87695977
  Virtual                        12.9 ns         12.8 ns     51913962
  Index                          13.9 ns         13.8 ns     52987698
  GetIf                          15.1 ns         15.0 ns     48578587
  HoldsAlternative               13.1 ns         13.1 ns     51711783
  ConstexprVisitor               13.8 ns         13.8 ns     49120024
  StructVisitor                  14.5 ns         14.5 ns     52679532
  Overload                       17.1 ns         17.1 ns     42553366
  </code></pre>
  <p>Right now, for clang, it's better to use virtual inheritance but for g++ it's better to use <code>holds_alternative</code> or <code>get_if</code> but in overall, <code>std::visit</code> seems to be not a good choice for almost all of my benchmarks so far.</p>
  <p>I'm thinking it'll be a good idea if pattern matching (switch statements capable of checking more stuff than just integer literals) would be added to the c++, we would be writing cleaner and more maintainable code.</p>
  <p>I'm wondering about the <code>package.index()</code> results. Shouldn't it be faster? what does it do?</p>
  <p>Clang version: <a href="http://quick-bench.com/cl0HFmUes2GCSE1w04qt4Rqj6aI" rel="noreferrer">http://quick-bench.com/cl0HFmUes2GCSE1w04qt4Rqj6aI</a></p>
  <p>The version that uses <code>One one</code> instead of <code>auto one = new One</code> based on <a href="https://stackoverflow.com/questions/57726401/stdvariant-vs-inheritance-vs-other-ways-performance?noredirect=1#comment102329947_57726401">Maxim Egorushkin's comment</a>: <a href="http://quick-bench.com/KAeT00__i2zbmpmUHDutAfiD6-Q" rel="noreferrer">http://quick-bench.com/KAeT00__i2zbmpmUHDutAfiD6-Q</a> (not changing the outcome much)</p>
  <hr />
  <h1>Update (6)</h1>
  <p>I made some changes and the results are very different from compiler to compiler now. But it seems like <code>std::get_if</code> and <code>std::holds_alternatives</code> are the best solutions. <code>virtual</code> seems to work best for unknown reasons with clang now. That really surprises me there because I remember <code>virtual</code> being better in gcc. And also <code>std::visit</code> is totally out of competition; in this last benchmark it's even worse than vtable lookup.</p>
  <p>Here's the benchmark (run it with GCC/Clang and also with libstdc++ and libc++):</p>
  <p><a href="http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y" rel="noreferrer">http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y</a></p>
  <pre class="lang-cpp prettyprint-override"><code>#include &lt;benchmark/benchmark.h&gt;
  
  #include &lt;array&gt;
  #include &lt;variant&gt;
  #include &lt;random&gt;
  #include &lt;functional&gt;
  #include &lt;algorithm&gt;
  
  using namespace std;
  
  struct One {
    auto get () const { return 1; }
   };
  struct Two {
    auto get() const { return 2; }
   };
  struct Three { 
    auto get() const { return 3; }
  };
  struct Four {
    auto get() const { return 4; }
   };
  
  template&lt;class... Ts&gt; struct overload : Ts... { using Ts::operator()...; };
  template&lt;class... Ts&gt; overload(Ts...) -&gt; overload&lt;Ts...&gt;;
  
  
  std::random_device dev;
  std::mt19937 rng(dev());
  std::uniform_int_distribution&lt;std::mt19937::result_type&gt; random_pick(0,3); // distribution in range [1, 6]
  
  template &lt;std::size_t N&gt;
  std::array&lt;int, N&gt; get_random_array() {
    std::array&lt;int, N&gt; item;
    for (int i = 0 ; i &lt; N; i++)
      item[i] = random_pick(rng);
    return item;
  }
  
  template &lt;typename T, std::size_t N&gt;
  std::array&lt;T, N&gt; get_random_objects(std::function&lt;T(decltype(random_pick(rng)))&gt; func) {
      std::array&lt;T, N&gt; a;
      std::generate(a.begin(), a.end(), [&amp;] {
          return func(random_pick(rng));
      });
      return a;
  }
  
  
  static void TradeSpaceForPerformance(benchmark::State&amp; state) {
      One one;
      Two two;
      Three three;
      Four four;
  
    int index = 0;
  
    auto ran_arr = get_random_array&lt;50&gt;();
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      index = ran_arr[r++ % ran_arr.size()];
    };
  
    pick_randomly();
  
  
    for (auto _ : state) {
  
      int res;
      switch (index) {
        case 0:
          res = one.get();
          break;
        case 1:
          res = two.get();
          break;
        case 2:
          res = three.get();
          break;
        case 3:
          res = four.get();
          break;
      }
      
      benchmark::DoNotOptimize(index);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  
  }
  // Register the function as a benchmark
  BENCHMARK(TradeSpaceForPerformance);
  
  
  static void Virtual(benchmark::State&amp; state) {
  
    struct Base {
      virtual int get() const noexcept = 0;
      virtual ~Base() {}
    };
  
    struct A final: public Base {
      int get()  const noexcept override { return 1; }
    };
  
    struct B final : public Base {
      int get() const noexcept override { return 2; }
    };
  
    struct C final : public Base {
      int get() const noexcept override { return 3; }
    };
  
    struct D final : public Base {
      int get() const noexcept override { return 4; }
    };
  
    Base* package = nullptr;
    int r = 0;
    auto packages = get_random_objects&lt;Base*, 50&gt;([&amp;] (auto r) -&gt; Base* {
            switch(r) {
                case 0: return new A;
                case 1: return new B;
                case 3: return new C;
                case 4: return new D;
                default: return new C;
            }
      });
  
    auto pick_randomly = [&amp;] () {
      package = packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    for (auto _ : state) {
  
      int res = package-&gt;get();
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  
    for (auto &amp;i : packages)
      delete i;
  
  }
  BENCHMARK(Virtual);
  
  
  
  
  static void FunctionPointerList(benchmark::State&amp; state) {
  
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::function&lt;int()&gt;;
    std::size_t index;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
          case 0: return std::bind(&amp;One::get, one);
          case 1: return std::bind(&amp;Two::get, two);
          case 2: return std::bind(&amp;Three::get, three);
          case 3: return std::bind(&amp;Four::get, four);
          default: return std::bind(&amp;Three::get, three);
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      index = r++ % packages.size();
    };
  
  
    pick_randomly();
  
    for (auto _ : state) {
  
      int res = packages[index]();
  
      benchmark::DoNotOptimize(index);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(FunctionPointerList);
  
  
  
  static void Index(benchmark::State&amp; state) {
  
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
  
    pick_randomly();
  
    for (auto _ : state) {
  
      int res;
      switch (package-&gt;index()) {
        case 0: 
          res = std::get&lt;One&gt;(*package).get();
          break;
        case 1:
          res = std::get&lt;Two&gt;(*package).get();
          break;
        case 2:
          res = std::get&lt;Three&gt;(*package).get();
          break;
        case 3:
          res = std::get&lt;Four&gt;(*package).get();
          break;
      }
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(Index);
  
  
  
  static void GetIf(benchmark::State&amp; state) {
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    for (auto _ : state) {
  
      int res;
      if (auto item = std::get_if&lt;One&gt;(package)) {
        res = item-&gt;get();
      } else if (auto item = std::get_if&lt;Two&gt;(package)) {
        res = item-&gt;get();
      } else if (auto item = std::get_if&lt;Three&gt;(package)) {
        res = item-&gt;get();
      } else if (auto item = std::get_if&lt;Four&gt;(package)) {
        res = item-&gt;get();
      }
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
    
  
  }
  BENCHMARK(GetIf);
  
  static void HoldsAlternative(benchmark::State&amp; state) {
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    for (auto _ : state) {
  
      int res;
      if (std::holds_alternative&lt;One&gt;(*package)) {
        res = std::get&lt;One&gt;(*package).get();
      } else if (std::holds_alternative&lt;Two&gt;(*package)) {
        res = std::get&lt;Two&gt;(*package).get();
      } else if (std::holds_alternative&lt;Three&gt;(*package)) {
        res = std::get&lt;Three&gt;(*package).get();
      } else if (std::holds_alternative&lt;Four&gt;(*package)) {
        res = std::get&lt;Four&gt;(*package).get();
      }
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(HoldsAlternative);
  
  
  static void ConstexprVisitor(benchmark::State&amp; state) {
  
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    auto func = [] (auto const&amp; ref) {
          using type = std::decay_t&lt;decltype(ref)&gt;;
          if constexpr (std::is_same&lt;type, One&gt;::value) {
              return ref.get();
          } else if constexpr (std::is_same&lt;type, Two&gt;::value) {
              return ref.get();
          } else if constexpr (std::is_same&lt;type, Three&gt;::value)  {
            return ref.get();
          } else if constexpr (std::is_same&lt;type, Four&gt;::value) {
              return ref.get();
          } else {
            return 0;
          }
      };
  
    for (auto _ : state) {
  
      auto res = std::visit(func, *package);
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(ConstexprVisitor);
  
  static void StructVisitor(benchmark::State&amp; state) {
  
    
  
    struct VisitPackage
    {
        auto operator()(One const&amp; r) { return r.get(); }
        auto operator()(Two const&amp; r) { return r.get(); }
        auto operator()(Three const&amp; r) { return r.get(); }
        auto operator()(Four const&amp; r) { return r.get(); }
    };
  
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    auto vs = VisitPackage();
  
    for (auto _ : state) {
  
      auto res = std::visit(vs, *package);
  
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(StructVisitor);
  
  
  static void Overload(benchmark::State&amp; state) {
  
  
      One one;
      Two two;
      Three three;
      Four four;
    using type = std::variant&lt;One, Two, Three, Four&gt;;
    type* package = nullptr;
  
    auto packages = get_random_objects&lt;type, 50&gt;([&amp;] (auto r) -&gt; type {
          switch(r) {
              case 0: return one;
              case 1: return two;
              case 2: return three;
              case 3: return four;
              default: return three;
          }
      });
    int r = 0;
  
    auto pick_randomly = [&amp;] () {
      package = &amp;packages[r++ % packages.size()];
    };
  
    pick_randomly();
  
    auto ov = overload {
        [] (One const&amp; r) { return r.get(); },
        [] (Two const&amp; r) { return r.get(); },
        [] (Three const&amp; r) { return r.get(); },
        [] (Four const&amp; r) { return r.get(); }
      };
  
    for (auto _ : state) {
  
      auto res = std::visit(ov, *package);
  
    
      benchmark::DoNotOptimize(package);
      benchmark::DoNotOptimize(res);
  
      pick_randomly();
    }
  
  }
  BENCHMARK(Overload);
  
  
  // BENCHMARK_MAIN();
  </code></pre>
  <p>Results for GCC compiler:</p>
  <pre><code>-------------------------------------------------------------------
  Benchmark                         Time             CPU   Iterations
  -------------------------------------------------------------------
  TradeSpaceForPerformance       3.71 ns         3.61 ns    170515835
  Virtual                       12.20 ns        12.10 ns     55911685
  FunctionPointerList           13.00 ns        12.90 ns     50763964
  Index                          7.40 ns         7.38 ns    136228156
  GetIf                          4.04 ns         4.02 ns    205214632
  HoldsAlternative               3.74 ns         3.73 ns    200278724
  ConstexprVisitor              12.50 ns        12.40 ns     56373704
  StructVisitor                 12.00 ns        12.00 ns     60866510
  Overload                      13.20 ns        13.20 ns     56128558
  </code></pre>
  <p>Results for clang compiler (which I'm surprised by it):</p>
  <pre><code>-------------------------------------------------------------------
  Benchmark                         Time             CPU   Iterations
  -------------------------------------------------------------------
  TradeSpaceForPerformance       8.07 ns         7.99 ns     77530258
  Virtual                        7.80 ns         7.77 ns     77301370
  FunctionPointerList            12.1 ns         12.1 ns     56363372
  Index                          11.1 ns         11.1 ns     69582297
  GetIf                          10.4 ns         10.4 ns     80923874
  HoldsAlternative               9.98 ns         9.96 ns     71313572
  ConstexprVisitor               11.4 ns         11.3 ns     63267967
  StructVisitor                  10.8 ns         10.7 ns     65477522
  Overload                       11.4 ns         11.4 ns     64880956
  </code></pre>
  <hr />
  <p>Best benchmark so far (will be updated):
  <a href="http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y" rel="noreferrer">http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y</a> (also check out the GCC)</p>