DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Process Mining Key Elements
  • Implementation of the Raft Consensus Algorithm Using C++20 Coroutines
  • Doubly Linked List in Data Structures and Algorithms
  • What Is Encryption and How Does It Work?

Trending

  • The Modern Data Stack Is Overrated — Here’s What Works
  • Accelerating AI Inference With TensorRT
  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  • My LLM Journey as a Software Engineer Exploring a New Domain
  1. DZone
  2. Data Engineering
  3. AI/ML
  4. How to Use std::visit With Multiple Variants

How to Use std::visit With Multiple Variants

In this article, we'll show how you can use std::visit with many variants, a technique which might lead to various 'pattern matching' algorithms.

By 
Bartłomiej Filipek user avatar
Bartłomiej Filipek
·
Sep. 11, 18 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
11.8K Views

Join the DZone community and get the full member experience.

Join For Free

std::visit is a powerful utility that allows you to call a function over a currently active type in std::variant. It does some magic to select the proper overload, and what's more, it can support many variants at once.

Let's have a look at a few examples of how to use this functionality.

The Amazing std::visit

Here's a basic example with one variant:

struct Fluid { };
struct LightItem { };
struct HeavyItem { };
struct FragileItem { };

struct VisitPackage
{
    void operator()(Fluid& ) { cout << "fluid\n"; }
    void operator()(LightItem& ) { cout << "light item\n"; }
    void operator()(HeavyItem& ) { cout << "heavy item\n"; }
    void operator()(FragileItem& ) { cout << "fraile\n"; }
};

int main()
{
    std::variant<Fluid, LightItem, HeavyItem, FragileItem> package { 
        FragileItem() };
    std::visit(VisitPackage(), package);
}

Output:

fragile

We have a variant that represents a package with four various types, and then we use super-advanced VisitPackage structure to detect what's inside. The example is also quite interesting as you can invoke a polymorphic operation over a set of classes that are not sharing the same base type.

Just a reminder, you can read the introduction to std::variant in my article: Everything You Need to Know About std::variant from C++17.

We can also use the "overload pattern" to use several separate lambda expressions:

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

int main()
{
    std::variant<Fluid, LightItem, HeavyItem, FragileItem> package;

    std::visit(overload{
        [](Fluid& ) { cout << "fluid\n"; },
        [](LightItem& ) { cout << "light item\n"; },
        [](HeavyItem& ) { cout << "heavy item\n"; },
        [](FragileItem& ) { cout << "fraile\n"; }
    }, package);
}

In the above example, the code is much shorter, and there's no need to declare a separate structure that holds operator() overloads.

Do you know what's the expected output in the example above? What's the default value of package?

Many Variants

But std::visit can accept more variants!

If you look at its spec it's declared as:

template <class Visitor, class... Variants>
constexpr ReturnType visit(Visitor&& vis, Variants&&... vars);

and it calls std::invoke on all of the active types from the variants:

std::invoke(std::forward<Visitor>(vis), 
    std::get<is>(std::forward<Variants>(vars))...) 

// where `is...` is `vars.index()...`.

It returns the type from that selected overload.

For example we can call it on two packages:

std::variant<LightItem, HeavyItem> basicPackA;
std::variant<LightItem, HeavyItem> basicPackB;

std::visit(overload{
    [](LightItem&, LightItem& ) { cout << "2 light items\n"; },
    [](LightItem&, HeavyItem& ) { cout << "light & heavy items\n"; },
    [](HeavyItem&, LightItem& ) { cout << "heavy & light items\n"; },
    [](HeavyItem&, HeavyItem& ) { cout << "2 heavy items\n"; },
}, basicPackA, basicPackB);

The code will print:

2 light items 

As you see you have to provide overloads for all of the combinations (N-cartesian product) of the possible types that can appear in a function.

Here's a little diagram that shows this:

If you have two variants — std::variant<A, B, C> abc and std::variant<X, Y, Z> xyz  — then you have to provide overloads that takes 9 possible configurations:

func(A, X);
func(A, Y);
func(A, Z);

func(B, X);
func(B, Y);
func(B, Z);

func(C, X);
func(C, Y);
func(C, Z);

In the next section, we'll see how to leverage this functionality in an example that tries to match the item with a suitable package.

One Example

std::visit not only can take many variants, but also those variants might be of a different type.

To illustrate that functionality I came up with the following example.

Let's say we have an item (fluid, heavy, light, or something fragile) and we'd like to match it with a proper box (glass, cardboard, reinforced box, a box with amortization).

In C++17, with variants and std::visit, we can try the following implementation:

struct Fluid { };
struct LightItem { };
struct HeavyItem { };
struct FragileItem { };

struct GlassBox { };
struct CardboardBox { };
struct ReinforcedBox { };
struct AmortisedBox { };

variant<Fluid, LightItem, HeavyItem, FragileItem> item { 
    Fluid() };
variant<GlassBox, CardboardBox, ReinforcedBox, AmortisedBox> box { 
    CardboardBox() };

std::visit(overload{
    [](Fluid&, GlassBox& ) { 
        cout << "fluid in a glass box\n"; },
    [](Fluid&, auto ) { 
        cout << "warning! fluid in a wrong container!\n"; },
    [](LightItem&, CardboardBox& ) { 
        cout << "a light item in a cardboard box\n"; },
    [](LightItem&, auto ) { 
        cout << "a light item can be stored in any type of box, "
                "but cardboard is good enough\n"; },
    [](HeavyItem&, ReinforcedBox& ) { 
        cout << "a heavy item in a reinforced box\n"; },
    [](HeavyItem&, auto ) { 
        cout << "warning! a heavy item should be stored "
                "in a reinforced box\n"; },
    [](FragileItem&, AmortisedBox& ) { 
        cout << "fragile item in an amortised box\n"; },
    [](FragileItem&, auto ) { 
        cout << "warning! a fragile item should be stored "
                "in an amortised box\n"; },
}, item, box);

The code will output:

warning! fluid in a wrong container! 

You can play with the code here @Coliru

We have four types of items and four types of boxes. We'd like to match the correct box with the item.

std::visit takes two variants, item and box, and then invokes a proper overload and shows if the types are compatible or not.

The types are very simple, but there's no problem with extending them and adding features like wight, size, or other important members.

In theory, we should write all combinations of overloads: it means 4*4 = 16 functions... but I used a trick to limit it. The code implements only 8 "valid" and "interesting" overloads.

So how you can "skip" such an overload?

How to Skip Overloads in std::visit

It appears that you can use the concept of generic lambdas to implement a "default" overload function!

For example:

std::variant<int, float, char> v1 { 's' };
std::variant<int, float, char> v2 { 10 };

std::visit(overloaded{
        [](int a, int b) { },
        [](int a, float b) { },
        [](int a, char b) { },
        [](float a, int b) { },
        [](auto a, auto b) { }, // << default!
    }, v1, v2);

In the example above, you can see that only four overloads have specific types — let's say those are the "valid" (or "meaningful") overloads. The rest is handled by generic lambdas (available since C++14).

A generic lambda resolves to a template function. It has less priority than "concrete" function overloads when the compiler creates the final overload resolution set.

By the way, I wrote about this technique in the recent update of my book.

If your visitor is implemented as a separate type, then you can use the full expansion of a generic lambda and use:

template <typename A, typename B>
auto operator()(A, B) { }

I think the pattern might be handy when you call std::visit on variants that lead to more than 5...7 or more overloads, and when some overloads repeat the code...

In our main example with items and boxes I also use this technique in a different form. For example:

[](FragileItem&, auto ) { 
    cout << "warning! a fragile item should be stored "
            "in an amortised box\n"; },

The generic lambda will handle all overloads taking one concrete argument, FragileItem, and then the second argument is not "important."

Summary

In this article, I've shown how you can use std::visit with many variants. Such a technique might lead to various "pattern matching" algorithms. You have a set of types, and you want to perform some algorithm based on the currently active types. It's like doing polymorphic operations, but differently — as std::visit doesn't use any v-tables.

Also, if you'd like to know how std::visit works underneath, then you might want to check out this post: Variant Visitation by Michael Park.

Have you used std::visit with many variants?  Can you share some examples?

More from the Author:

Bartek recently published a book - "C++17 In Detail"- rather than reading the papers and C++ specification drafts, you can use this book to learn the new Standard in an efficient and practical way.

Book c++ IT Algorithm Concept (generic programming) Template Leverage (statistics)

Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Process Mining Key Elements
  • Implementation of the Raft Consensus Algorithm Using C++20 Coroutines
  • Doubly Linked List in Data Structures and Algorithms
  • What Is Encryption and How Does It Work?

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!