Visiting a std::variant with the Overload Pattern

Contents[Show]

Typically, you use the overload pattern for a std::variant. std::variant is a type-safe union. A std::variant (C++17) has one value from one of its types. std::visit allows you to apply a visitor to it. Exactly here comes the overload pattern convenient into play.

 templates

In my last post, "Smart Tricks with Parameter Packs and Fold Expressions", I introduced the overload pattern as a smart trick to create an overload set using lambdas. Typically, the overload pattern is used for visiting the value held by a std::variant.

I know from my C++ seminars that most developers don't know std::variant and std::visit and still, use a union. Therefore, let me give you a quick reminder about std::variant and std::visit.

std::variant (C++17)

A std::variant is a type-safe union. An instance of std::variant has a value from one of its types. The value must not be a reference, C-array, or void. A std::variant can have one type more than once. A default-initialized std::variant will be initialized with its first type. In this case, the first type must have a default constructor. Here is an example based on cppreference.com. 

// variant.cpp

#include <variant>
#include <string>
 
int main(){

  std::variant<int, float> v, w;
  v = 12;                              // (1)
  int i = std::get<int>(v);
  w = std::get<int>(v);                // (2)
  w = std::get<0>(v);                  // (3)
  w = v;                               // (4)
 
  //  std::get<double>(v);             // (5) ERROR
  //  std::get<3>(v);                  // (6) ERROR
 
  try{
    std::get<float>(w);                // (7)
  }
  catch (std::bad_variant_access&) {}
 
  std::variant<std::string> v("abc");  // (8)
  v = "def";                           // (9)

}

 

I define both variants v and w. They can have an int and a float value. Their initial value is 0. v becomes 12 (line 1). std::get<int>(v) returns the value. In lines (2) - (3), you see three possibilities to assign variant v the variant w. But you have to keep a few rules in mind. You can ask for the value of a variant by type (line 5) or index (line 6). The type must be unique and the index valid. On line 7, the variant w holds an int value. Therefore, I get a std::bad_variant_access exception. If the constructor or assignment call is unambiguous, a simple conversion occurs. That is the reason that it's possible to construct a std::variant<std::string> in line (8) with a C-string or assign a new C-string to the variant (line 9).

Of course, there is way more about std::variant. Read the post "Everything You Need to Know About std::variant from C++17" by Bartlomiej Filipek.

Thanks to the function std::visit, C++17 provides a convenient way to visit the elements of a std::variant.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

std::visit

According to the classical design patterns, what sounds like the visitor pattern is a kind of visitor for a container of variants.

std::visit allows you to apply a visitor to a container of variants. The visitor must be callable. A callable is something that you can invoke. Typical callables are functions, function objects, or lambdas. I use lambdas in my example.

// visitVariants.cpp

#include <iostream>
#include <vector>
#include <typeinfo>
#include <variant>

  
int main(){
  
    std::cout << '\n';
  
    std::vector<std::variant<char, long, float, int, double, long long>>      // 1
               vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017};
  
    for (auto& v: vecVariant){
        std::visit([](auto arg){std::cout << arg << " ";}, v);                // 2
    }
  
    std::cout << '\n';
  
    for (auto& v: vecVariant){
        std::visit([](auto arg){std::cout << typeid(arg).name() << " ";}, v); // 3
    }
  
    std::cout << "\n\n";
  
}

 

I create in (1) a std::vector of variants and initialize each variant. Each variant can hold a char, long, float, int, double, or long long value. It's pretty easy to traverse the vector of variants and apply the lambda (lines (2) and (3) to it. First, I display the current value (2), and second, thanks to the call typeid(arg).name() (3), I get a string representation of the type of the current value.

visitVariants

Fine? No!. I used it in the program visitVariant.cpp generic lambda. Consequently, the string representations of the types are pretty unreadable using GCC: "i c d x l f i". Honestly, I want to apply a specific lambda to each type of the variant. Now, the overload pattern comes to my rescue.

Overload Pattern

Thanks to the overload pattern, I can display each type with a readable string and display each value in an appropriate way.

// visitVariantsOverloadPattern.cpp

#include <iostream>
#include <vector>
#include <typeinfo>
#include <variant>
#include <string>

template<typename ... Ts>                                                 // (7) 
struct Overload : Ts ... { 
    using Ts::operator() ...;
};
template<class... Ts> Overload(Ts...) -> Overload<Ts...>;

int main(){
  
    std::cout << '\n';
  
    std::vector<std::variant<char, long, float, int, double, long long>>  // (1)    
               vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017};

    auto TypeOfIntegral = Overload {                                      // (2)
        [](char) { return "char"; },
        [](int) { return "int"; },
        [](unsigned int) { return "unsigned int"; },
        [](long int) { return "long int"; },
        [](long long int) { return "long long int"; },
        [](auto) { return "unknown type"; },
    };
  
    for (auto v : vecVariant) {                                           // (3)
        std::cout << std::visit(TypeOfIntegral, v) << '\n';
    }

    std::cout << '\n';

    std::vector<std::variant<std::vector<int>, double, std::string>>      // (4)
        vecVariant2 = { 1.5, std::vector<int>{1, 2, 3, 4, 5}, "Hello "};

    auto DisplayMe = Overload {                                           // (5)
        [](std::vector<int>& myVec) { 
                for (auto v: myVec) std::cout << v << " ";
                std::cout << '\n'; 
            },
        [](auto& arg) { std::cout << arg << '\n';},
    };

    for (auto v : vecVariant2) {                                         // (6)
        std::visit(DisplayMe, v);
    }

    std::cout << '\n';
  
}

 

Line (1) creates a vector of variants having integral types, and line (4) creates a vector of variants having a std::vector<int>, double, and a std::string.

Let me continue with the first variant vecVariant. TypeOfIntegral (2) is an overload set that returns for a few integral types a string representation. If the type is not handled by the overload set, I return the string "unknown type". In line (3), I apply the overload set to each variant v using std::visit.

The second variant vecVariant2 (4), has composed types. I create an overload set (5) to display their values. In general, I can just push the value onto std:.cout. For the std::vector<int>, I use a range-based for-loop to push its values to std::cout.

Finally, here is the output of the program.

visitVariantsOverloadPattern

I want to add a few words to the overload pattern used in this example (7). I already introduced in my last post, "Smart Tricks with Parameter Packs and Fold Expressions`.

template<typename ... Ts>                                  // (1)
struct Overload : Ts ... { 
    using Ts::operator() ...;
};
template<class... Ts> Overload(Ts...) -> Overload<Ts...>; // (2)

 

Line (1) is the overload pattern, and line (2) is the deduction guide. The struct Overload can have arbitrarily many base classes (Ts ...). It derives from each class public and brings the call operator (Ts::operator...) of each base class into its scope. The base classes need an overloaded call operator (Ts::operator()). Lambdas provide this call operator. The following example is as simple as it can be.

#include <variant>

template<typename ... Ts>                                                 
struct Overload : Ts ... { 
    using Ts::operator() ...;
};
template<class... Ts> Overload(Ts...) -> Overload<Ts...>;

int main(){
  
    std::variant<char, int, float> var = 2017;

    auto TypeOfIntegral = Overload {     // (1)                                  
        [](char) { return "char"; },
        [](int) { return "int"; },
        [](auto) { return "unknown type"; },
    };
  
}

 

Using this example in C++ Insights makes the magic transparent. First, call (1) causes the creation of a fully specialized class template.

OverloadPatternInstantiated

Second, the used lambdas in the overload pattern such as [](char) { return "char"; } cause the creation of a function object. In this case, the compiler gives the function object the name __lambda_15_9.

lambdaChar

Studying the auto-generate types show at least one interesting point. The call operator of __lambda_15_9 is overloaded for char: const char * operator() (char) const { return "char"; }

The deduction guide (template<class... Ts> Overload(Ts...) -> Overload<Ts...>;) (line 2) is only needed for C++17. The deduction guide tells the compiler how to create out-of-constructor arguments template parameters. C++20 can automatically deduce the template. 

What's next?

The friendship of templates is special. In my next post, I will explain why.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 
 

 

 

Comments   

0 #1 Chris 2022-06-21 13:35
Thanks for the overload pattern. One issue with the pattern as shown is that if you want to use a templated lambda, the compiler (g++) will complain.
Quote

Stay Informed about my Mentoring

 

Mentoring

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 4118

Yesterday 4344

Week 40996

Month 21242

All 12099451

Currently are 150 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments