Confusing templates in C++17 example of std::visit
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.
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.