38

When looking at std::visit() page in cppreference, https://en.cppreference.com/w/cpp/utility/variant/visit, I encountered the code I can't make sense of...

Here's the abbreviated version:

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

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

int main() {
    std::vector<std::variant<int,long,double,std::string>> vec = { 10, 15l, 1.5, "hello" };
    for (auto& v : vec) {
        std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
            }, v);
    }
}

What do the two lines declaring overloaded, just above int main(), mean?

Thank you for explaining!

2019 Addition
After the two gentlemen below provided detailed explanations (thank you so much!), I've stumbled upon the same code in the very fine book C++17 in Detail - Learn the Exciting Features of The New C++ Standard! by Bartłomiej Filipek. Such a well written book!

Boris
  • 748
  • 6
  • 18
  • Anybody can shine some light on the initialization here? Why doesn't `overloads(/* some lambdas */);` work, but `overloads{/* some lambdas */};` does? Notice the uniform initialization vs parentheses initialization. – Fureeish Sep 26 '18 at 19:04
  • 1
    @Fureeish [aggregate initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization) is available by default (on qualifying classes) and is different from a normal constructor call. Its worth noting that this only works in C++17 since inheritance would disallow aggregate initialization prior to that. – kmdreko Sep 26 '18 at 21:09
  • @kmdreko well, the fact that is different is obvious, since I observed that it behaves differently. I appreciate the link, but I would like to know why the normal constructor call doesn't work. This doesn't answer the question. Maybe I should ask one, if my research fails – Fureeish Sep 26 '18 at 21:12
  • I've improved my answer trying to show another interesting point that I've initially missed but Nemo pointed to me in a comment. – max66 Sep 28 '18 at 13:50
  • The same code can also be found in [Functional Programming in C++](https://livebook.manning.com/book/functional-programming-in-c-plus-plus/about-this-book/). – Enlico Sep 03 '20 at 17:42

2 Answers2

36

What are the two lines declaring overloaded, just above int main(), mean?

The first one

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

is a classic class/struct declaration/definition/implementation. Valid from C++11 (because use variadic templates).

In this case, overloaded inherits from all template parameters and enables (using row) all inherited operator(). This is an example of Variadic CRTP.

Unfortunately the variadic using is available only starting from C++17.

The second one

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

is a "deduction guide" (see this page for more details) and it's a new C++17 feature.

In your case, the deduction guide says that when you write something as

auto ov = overloaded{ arg1, arg2, arg3, arg4 };

or also

overloaded ov{ arg1, args, arg3, arg4 };

ov becomes an overloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>

This permits you to write something as

overloaded
{
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}

that in C++14 was

auto l1 = [](auto arg) { std::cout << arg << ' '; };
auto l2 = [](double arg) { std::cout << std::fixed << arg << ' '; };
auto l3 = [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }

overloaded<decltype(l1), decltype(l2), decltype(l3)> ov{l1, l2, l3};

-- EDIT --

As pointed by Nemo (thanks!) in the example code in your question there is another interesting new C++17 feature: the aggregate initialization of base classes.

I mean... when you write

overloaded
{
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
 }

you're passing three lambda functions to initialize three base classes of overloaded.

Before C++17, you could do this only if you wrote an explicit constructor to do it. Starting from C++17, it works automatically.

At this point, it seems to me that it can be useful to show a simplified full example of your overloaded in C++17 and a corresponding C++14 example.

I propose the following C++17 program

#include <iostream>

template <typename ... Ts>
struct overloaded : public Ts ...
 { using Ts::operator()...; };

template <typename ... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main ()
{
    overloaded ov
    {
        [](auto arg) { std::cout << "generic: " << arg << std::endl; },
        [](double arg) { std::cout << "double: " << arg << std::endl; },
        [](long arg) { std::cout << "long: " << arg << std::endl; }
    };
    ov(2.1);
    ov(3l);
    ov("foo");      
 }

and the best C++14 alternative (following also the bolov's suggestion of a "make" function and his recursive overloaded example) that I can imagine.

#include <iostream>

template <typename ...>
struct overloaded;

template <typename T0>
struct overloaded<T0> : public T0
{
    template <typename U0>
    overloaded (U0 && u0) : T0 { std::forward<U0>(u0) }
    { }
};

template <typename T0, typename ... Ts>
struct overloaded<T0, Ts...> : public T0, public overloaded<Ts ...>
{
    using T0::operator();
    using overloaded<Ts...>::operator();

    template <typename U0, typename ... Us>
    overloaded (U0 && u0, Us && ... us)
      : T0{std::forward<U0>(u0)}, overloaded<Ts...> { std::forward<Us>(us)... }
    { }
 };

template <typename ... Ts>
auto makeOverloaded (Ts && ... ts)
{
    return overloaded<Ts...>{std::forward<Ts>(ts)...};
}

int main ()
{
    auto  ov
    {
        makeOverloaded
        (
            [](auto arg) { std::cout << "generic: " << arg << std::endl; },
            [](double arg) { std::cout << "double: " << arg << std::endl; },
            [](long arg) { std::cout << "long: " << arg << std::endl; }
        )
    };
    ov(2.1);
    ov(3l);
    ov("foo");      
 }

I suppose that it's matter of opinion, but it seems to me that the C++17 version is a lot simpler and more elegant.

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80
max66
  • 65,235
  • 10
  • 71
  • 111
21

Ahh, I love this.

It's a way to concisely declare a struct with a call operator overloaded on the set of the template arguments call operators.

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

overloaded inherits from Ts... and uses all of their operator()

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

This is a deduction guide so you don't specify the template parameters

The usage is as you see in the example.

It's a nice utility to create an overloaded set of multiple lambdas (and other function types).


Previous to C++17 you would have to use recursion to create overload. Not pretty:

template <class... Fs> struct Overload : Fs...
{
};

template <class Head, class... Tail>
struct Overload<Head, Tail...> : Head, Overload<Tail...>
{
    Overload(Head head, Tail... tail)
        : Head{head}, Overload<Tail...>{tail...}
    {}

    using Head::operator();
    using Overload<Tail...>::operator();
};


template <class F> struct Overload<F> : F
{
    Overload(F f) : F{f} {}

    using F::operator();
};


template <class... Fs> auto make_overload_set(Fs... fs)
{
    return Overload<Fs...>{fs...};
}

auto test()
{
    auto o = make_overload_set(
         [] (int) { return 24; },
         [] (char) { return 11; });

    o(2); // returns 24
    o('a'); // return 11
}

The main nuisance is that Overload because inherits is not an aggregate, so you need to do the recursion trick to create a constructor with all the types. In C++17 overloaded is an aggregate (yey) so constructing one works out of the box :). You also need to specify using::operator() for each of them.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • You missed the worst part! `using Head::operator(); using Overload::operator();` – Barry Sep 26 '18 at 18:42
  • @Barry clang compiles it without `using`, so I just assumed it's something about inheriting one by one that makes that possible. After your comment I double checked and `gcc` complains about ambiguity, so maybe it's a clang "feature". Will correct in answer. – bolov Sep 26 '18 at 19:00
  • Yeah, alternatively a clang bug :-) – Barry Sep 26 '18 at 19:03
  • Right, the "using" lines are necessary in both the OP and this `Overload` because the rules for class member lookup say that if a member is found declared in multiple different base classes (and they can only be found if not hidden by other ordinary or "using" declarations), then the name lookup is ambiguous, even if all the members are all functions with different signatures. – aschepler Sep 26 '18 at 22:06
  • Nice answer (I've partially copied part of it in the last version of my answer) but can be simplified a little the `overloaded` specialization (the ground case of the recursion): the `using F::operator();` is superfluous because there isn't a `overloaded`, with an `operator()`, that can interfere. – max66 Sep 28 '18 at 13:48