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!
In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With