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
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

  • Implementation of the Raft Consensus Algorithm Using C++20 Coroutines
  • Top 10 Programming Languages for Software Development

Trending

  • How to Build Local LLM RAG Apps With Ollama, DeepSeek-R1, and SingleStore
  • How To Develop a Truly Performant Mobile Application in 2025: A Case for Android
  • Integrating Security as Code: A Necessity for DevSecOps
  • AI-Powered Professor Rating Assistant With RAG and Pinecone

Two Lines of Code and Three C++17 Features: The Overload Pattern

We take a look at one of the newer patterns in C++, the overload pattern, and how you can implement it into your code. Read on to get overloaded!

By 
Bartłomiej Filipek user avatar
Bartłomiej Filipek
·
Feb. 14, 19 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
14.6K Views

Join the DZone community and get the full member experience.

Join For Free

While I was doing research for my book (C++17 in Detail) and blog posts about C++17, several times I stumbled upon this pattern for visitation of std::variant:

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

std::variant<int, float> intFloat { 0.0f };
std::visit(overload(
    [](const int& i) { ... },
    [](const float& f) { ... },
  ),
  intFloat;
);

With the above pattern, you can provide separate lambdas "in-place" for visitation.

It's just two lines of compact C++ code, but it packs in a few interesting concepts.

Let's see how this thing works and go through the three new C++17 features that enable this, one by one.

This article was originally posted at the author's blog: bfilipek.com

Intro

The code mentioned above forms a pattern called overload (or sometimes overloaded), and it's mostly useful for std::variant visitation.

With such helper code you can write:

std::variant<int, float, std::string> intFloatString { "Hello" };
std::visit(overload
  {
    [](const int& i) { std::cout << "int: " << i; },
    [](const float& f) { std::cout << "float: " << f; },
    [](const std::string& s) { std::cout << "string: " << s; }
  },
  intFloatString
);

The output:

string: Hello

Little reminder: std::variant is a helper vocabulary type, a discriminated union. As a so-called sum-type, it can hold non-related types at runtime and can switch between them by reassignment. std::visit allows you to invoke an operation on the currently active type from the given variant. Read more in my blog post Everything You Need to Know About std::variant from C++17.

Without the overload you'd have to write a separate class or struct with three overloads for the () operator:

struct PrintVisitor
{
    void operator()(int& i) const {
        std::cout << "int: " << i;
    }

    void operator()(float& f) const {
        std::cout << "float: " << f;
    }

    void operator()(std::string& s) const {
    std::cout << "string: " << s;
    }
};

std::variant<int, float, std::string> intFloatString { "Hello" };

std::visit(PrintVisitor(), intFloatString);

As you might already know the compiler conceptually expands lambda expression into a uniquely-named type that has operator().

What we do in the overload pattern is create an object that inherits from several lambdas and then exposes their operator() for std::visit. That way you write overloads "in place."

What are the C++17 features that compose the pattern?

  • Pack expansions in using declarations - short and compact syntax with variadic templates.
  • Custom template argument deduction rules that allow for converting a list of lambdas into a list of base classes for the overloaded class.
  • Extension to aggregate initialization - the overload pattern uses a constructor to initialize, but we don't have to specify it in the class. Before C++17 this wasn't possible.

New Features

Let's explore, section by section, the new elements that compose the overload pattern. That way we can learn a few interesting things about the language.

Using Declarations

There are three features here, but it's hard to tell which one is the simplest to explain. So let's start with using. Why do we need it at all? To understand that, let's write a simple type that derives from two base classes:

#include <iostream>

struct BaseInt
{
    void Func(int) { std::cout << "BaseInt...\n"; }
};

struct BaseDouble
{
    void Func(double) { std::cout << "BaseDouble...\n"; }
};

struct Derived : public BaseInt, BaseDouble
{
    //using BaseInt::Func;
    //using BaseDouble::Func;
};

int main()
{
    Derived d;
    d.Func(10.0);
}

We have two bases classes that implement Func. We want to call that method from the derived object.

Will the code compile?

When doing the overload resolution set, C++ states that the Best Viable Function must be in the same scope.

So GCC reports the following error:

error: request for member 'Func' is ambiguous

See a demo here @Coliru.

That's why we have to bring the functions into the scope of the derived class.

We have solved one part, and it's not a feature of C++17. But how about the variadic syntax?

The issue here was that before C++17 using... was not supported.

In the paper Pack expansions in using-declarations P0195R2, there's a motivating example that shows how much extra code was needed to mitigate that limitation:

template <typename T, typename... Ts>
struct Overloader : T, Overloader<Ts...> {
    using T::operator();
    using Overloader<Ts...>::operator();
    // […]
};

template <typename T> struct Overloader<T> : T {
    using T::operator();
};

In the example above, which uses C++14, we had to create a recursive template definition to be able to use using. But now, with C++17, we can write:

template <typename... Ts>
struct Overloader : Ts... {
    using Ts::operator()...;
    // […]
};

Much simpler!

Ok, but how about the rest of the code?

Custom Template Argument Deduction Rules

We derive from lambdas, and then we expose their operator() as we saw in the previous section. But how can we create objects of this overload type?

As you know, the type of a lambda is not known, so without the template deduction for classes it's hard to get it. What would you state in its declaration?

overload<LambdaType1, LambdaType2> myOverload { ... } // ???
// what is LambdaType1 and LambdaType2 ??

The only way that could work would be some make function (as template deductions have worked for function templates since, like, always):

template <typename... T>
constexpr auto make_overloader(T&&... t) {
    return Overloader<T...>{std::forward<T>(t)...};
}

With template deduction rules that were added in C++17, we can simplify the creation of common template types.

For example:

std::pair strDouble { std::string{"Hello"}, 10.0 };
// strDouble is std::pair<std::string, double>

There's also an option to define custom deduction guides. The Standard library uses a lot of them, for example for std::array:

template <class T, class... U>  
array(T, U...) -> array<T, 1 + sizeof...(U)>;

And the above rule allows us to write:

array test{1, 2, 3, 4, 5};
// test is std::array<int, 5>

For the overload patter we can write:

template<class... Ts> overload(Ts...) -> overload<Ts...>;

Now, we can type:

overload myOverload { [](int) { }, [](double) { } };

And the template arguments for overload will be correctly deduced.

Let's now go to the last missing part of the puzzle: aggregate initialization.

Extension to Aggregate Initialization

This functionality is relatively straightforward: we can now initialize a type that derives from other types.

As a reminder from dcl.init.aggr:

An aggregate is an array or a class with
no user-provided, explicit, or inherited constructors ([class.ctor])
no private or protected non-static data members (Clause [class.access]),
no virtual functions, and
no virtual, private, or protected base classes ([class.mi]).

For example (sample from the spec draft):

struct base1 { int b1, b2 = 42; };
struct base2 {
  base2() {
    b3 = 42;
  }
  int b3;
};
struct derived : base1, base2 {
  int d;
};

derived d1{{1, 2}, {}, 4};
derived d2{{}, {}, 4};

The above code initializes d1.b1 with 1, d1.b2 with 2, d1.b3 with 42, d1.d with 4, and d2.b1 with 0, d2.b2 with 42, d2.b3 with 42, d2.d with 4.

In our case, it has a more significant impact.

For our overload class, we could implement the following constructor.

struct overload : Fs... 
{
  template <class ...Ts>
  overload(Ts&& ...ts) : Fs{std::forward<Ts>(ts)}...
  {} 

  // ...
}

But here we have a lot of code to write, and it probably doesn't cover every case.

With aggregate initialization, we "directly" call the constructor of lambda from the base class list, so there's no need to write it and forward arguments to it explicitly.

Playground

Here's the full code:

#include <iostream>
#include <variant>

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

int main() 
{
    auto PrintVisitor = [](const auto& t) { std::cout << t << '\n'; };

    std::variant<int, float, std::string> intFloatString { "Hello" };
    std::visit(overloaded{
        [](int& i) { i*= 2; },
        [](float& f) { f*= 2.0f; },
        [](std::string& s) { s = s + s; }
    }, intFloatString);

    std::visit(PrintVisitor, intFloatString);

    // "custom" print:
    std::visit(overloaded{
        [](const int& i) { std::cout << "int: " << i << '\n'; },
        [](const float& f) { std::cout << "float: " << f << '\n'; },
        [](const std::string& s) { std::cout << "string: " << s << '\n';}
    }, intFloatString);

    return 0;

You can play around with this code @Coliru.

Summary

The overload pattern is a fascinating thing. It demonstrates several C++ techniques, gathers them together, and allows us to write shorter syntax.

In C++14, you could derive from lambdas and build similar helper types, but only with C++17 you can significantly reduce boilerplate code and limit potential errors.

You can read more in the proposal for overloadP0051 (not sure if that goes in C++20, but it's worth to read the discussions and concepts behind it).

The pattern presented in this blog post supports only lambdas and there's no option to handle regular function pointers. In the paper, you can see much more advanced implementations that try to handle all cases.

More from the Author:

Image title

Bartek recently published a book - "C++17 In Detail"- learn the new C++ Standard in an efficient and practical way. The book contains more than 300 pages filled with C++17 content!

Your Turn

  • Have you used std::variant and visitation mechanism?
  • Have you used overload pattern?

References

  • Everything You Need to Know About std::variant from C++17
  • How To Use std::visit With Multiple Variants
  • C++ Weekly - Ep 49 - Why Inherit From Lambdas?
  • C++ Weekly - Ep 48 - C++17’s Variadic using
  • C++ Weekly - Ep 40 - Inheriting From Lambdas
  • Overload: Build a Variant Visitor on the Fly - Simplify C++!
c++

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

  • Implementation of the Raft Consensus Algorithm Using C++20 Coroutines
  • Top 10 Programming Languages for Software Development

Partner Resources

×

Comments

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: