What does "template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };" mean, and how is it being used with std::visit?

Viewed 222

This snippet of code is taken from https://en.cppreference.com/w/cpp/utility/variant/visit

using var_t = std::variant<int, long, double, std::string>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
std::vector<var_t> vec = {10, 15l, 1.5, "hello"};

for (auto& v: vec) {
// 4. another type-matching visitor: a class with 3 overloaded operator()'s
// Note: The `(auto arg)` template operator() will bind to `int` and `long`
//       in this case, but in its absence the `(double arg)` operator()
//       *will also* bind to `int` and `long` because both are implicitly
//       convertible to double. When using this form, care has to be taken
//       that implicit conversions are handled correctly.
    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);
}

Can someone explain what using Ts::operator()...; means here?
And in the following, what constructor is this calling? with the 3 lambda functions?

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

I think the concrete overloaded instance is deriving from all 3 of these function types, and then the visitor is picking the right one to use depending on the type of the variant. Is that right?

I just don't fully understand this example.

1 Answers

It creates a struct template called overloaded which inherits from all its template arguments. Next, it pulls all declared operator() functions from its base classes into its own scope, so these all take part in overload resolution when a user calls operator() on an instance of the overloaded struct.

The ... uses parameter pack expansion to perform the same operation for all template arguments.

And in the following, what constructor is this calling? with the 3 lambda functions?

It’s not a constructor, it’s aggregate initialisation. In this case it uses class template argument deduction (CTAD) to deduce the template arguments of overloaded and initialise its base class instances. Minus the CTAD, the aggregate initialisation is identical to this case:

struct A {};
struct B {};
struct C : A, B {};

void f() {
    C c{A{}, B{}};
}

In effect you’re creating an instance of the overloaded struct template, directly initialise an object for it with the given base class objects, and pass that to std::visit. The net effect is as if you had defined a struct with multiple operator() overloads (which is the normal way of using std::visit).

Related