What does `operator () ...` mean in C ++ code?

I am trying to understand the std::visit example from cppreference , where I saw the following line of code:

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

I do not understand. What does operator()... mean in code?

+5
source share
3 answers

I would like to add a few history lessons to the great answers here.

There are many layers, so let them polish them one by one.

  • variadic templates (C ++ 11)
    • parameter packages
    • package extension
  • using declaration
    • for the introduction of base classes
    • variadic using Declaration (C ++ 17)
  • tutorials for subtracting patterns (C ++ 17)

Variadic Templates

Pre-C ++ 11, we were limited by the number of template arguments that a function could receive by the number of programmers willing.

For example, if I wanted to write a function to sum an β€œarbitrary” number of values ​​for potentially different types, I needed to write a lot of templates, and even then I was limited:

 template<class T> void foo(T){} template<class T, class U> void foo(T, U){} template<class T, class U, class V> void foo(T, U, V){} // ... and so on until I decide enough enough 

In C ++ 11, we finally got "variadic templates", which means that we can get the number of unlimited (actual limit defined by your compiler) template arguments using an ellipse (...), so now we can write

 template<class... T> void foo(T... args){} 

This "unlimited number" of template arguments, class... T , is called a "parameter package" because it is not surprising that it represents a parameter package.

To β€œunzip” these parameters into a comma-separated list, we again use the ellipsis in the function parameter list: void foo(T... args){} . This is called a package extension, again, not a surprising name.

The result of expanding the package to call the function as follows:

 int a = /*...*/; double b = /*...*/; char c = /*...*/; foo(a, b, c); 

You might think so:

 template<int, double, char> void foo(Arguments[3] args){} 

Where Arguments is a kind of heterogeneous array ( int , double , char ).

These variable patterns also apply to class and struct patterns, so the analog symbol is used here

 template<class... Ts> struct overloaded 

declares an overload class that can be configured with an "unlimited" number of types.

Part ": Ts ...":

 template<class... Ts> struct overloaded : Ts... 

uses a package extension to declare an overloaded class to output (potentially through multiple inheritance) from each of these types.


using declaration

Pre-C ++ 11, we could declare type aliases using typedef as follows:

 typedef unsigned int uint; 

In C ++ 11, we got a using statement that can do the same thing, maybe a little more clearly (and much more! Just hang)

 using uint = unsigned int; 

However, the using statement was originally used for something else (its use has expanded significantly since the introduction of C ++ 11). One of the main reasons it was created was that we could reuse things from base classes in derived classes without forcing the client to ambiguously:

Without using

 struct does_a_thing { void do_a_thing(double){} }; struct also_does_a_thing { void do_a_thing(int){} }; struct derived : does_a_thing, also_does_a_thing{}; int main(){ derived d; d.do_a_thing(1); // ? which "do_a_thing gets called? Neither, because it ambiguous, so there a compiler error d.does_a_thing::do_a_thing(42.0); d.also_does_a_thing::do_a_thing(1); } 

Note that the client is forced to write some funky syntax to refer to the derived base that they want to use to call do_a_thing . This looks better if we use using :

With using :

 struct derived : does_a_thing, also_does_a_thing { using does_a_thing::do_a_thing; using also_does_a_thing::do_a_thing; }; int main(){ derived d; d.do_a_thing(1); // calls also_does_a_thing::do_a_thing } 

Cleaner, right?


Variadic using declaration

So, C ++ 11 came out, and all these impressions were impressed with these new features, but there was one small gap for using statements that were not considered; "What if I want to have using for each base class, where these base classes are template arguments?"

So something like this:

 template<class T, class U> struct derived : T, U { using T::do_a_thing; using U::do_a_thing; }; int main(){ derived<does_a_thing, also_does_a_thing> d; d.do_a_thing(1); // calls also_does_a_thing::do_a_thing } 

So far so good. But since we learned about variation patterns , let's make derived one:

 template<class... Ts> struct derived : Ts... { //using ? }; 

Using at that time was a hindrance due to the lack of variational support, so we couldn’t do this (easily).

Then C ++ 17 appeared and gave us variational support so that we could do this:

 template<class... Ts> struct derived : Ts... { using Ts::do_a_thing...; }; int main(){ derived<does_a_thing, also_does_a_thing> d; d.do_a_thing(1); // calls also_does_a_thing::do_a_thing d.do_a_thing(42.0); //calls does_a_thing::do_a_thing } 

Finally, we can understand the first part of your code!

So now we can finally understand all this part of the question:

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

We have a class called overload , which is templated by an "unlimited" number of types. This comes from each of these types. It also allows you to use the operator() method for each of these parent types. Convenient, right? (Note that if any of the base class' operator() looked the same, we would get an error.)


Pattern Subtraction Guide

Another thing that annoys C ++ developers is that if you had a template class that also had a template constructor, you had to explicitly specify the template arguments, even if you thought it was obvious to you and your client what type of template should be.

For example, I want to write a small iterator shell:

 template<class T> struct IteratorWrapper { template<template<class...> class Container, class... Args> IteratorWrapper(const Container<Args...>& c) { // do something with an iterator on c T begin = c.begin(); T end = c.end(); while(begin != end) { std::cout << *begin++ << " "; } } }; 

Now, if I, as the caller, wanted to create an instance of IteratorWrapper , I had to perform some additional steps to unambiguously sort what T was because it was not included in the constructor signature:

 std::vector<int> vec{1, 2, 3}; IteratorWrapper<typename std::vector<int>::const_iterator> iter_wrapper(vec); 

No one wants to write this monster. Thus, C ++ 17 introduced instructions for subtraction, where we, as a writer, could do a little extra work so that the client does not have to. Now, as the author of the class, I can write this:

 template<template<class...> class Container, class... Args> IteratorWrapper(const Container<Args...>& c) -> IteratorWrapper<typename Container<Args...>::const_iterator>; 

Which mimics the signature of the IteratorWrappers constructor, and then uses the back arrow ( -> ) to indicate the type of ItearatorWrapper for output.

So, now my clients can write code as follows:

 std::vector<int> vec{1, 2, 3}; IteratorWrapper iter_wrapper(vec); std::list<double> lst{4.1, 5.2}; IteratorWrapper lst_wrapper(lst); 

Beautiful, right?


Now we can understand the second line of code

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

Announces a template subtraction guide for our overloaded class, which says that when its constructor is called with a package of parameters, then the class must also be templated on the same types.

This may seem unnecessary, but you might want it if you had a template template with a template constructor:

 template<class... T> struct templated_ctor{ template<class... U> overloaded(U...){} }; 

* I know that I went overboard here, but it was just interesting to write and really answer the question :-)

+7
source

To understand using Ts::operator()...; , first you should know that class... Ts is a package of parameters (variation template). This is a sequence of template type parameters 0 ... N.

The ellipsis in using Ts::operator()... is the syntax for extending the parameter package. For example, in the case of overloaded<Foo, Bar> declaration using Ts::operator()...; will be expanded to equivalent:

 using Foo::operator(); using Bar::operator(); 
+8
source

The syntax here is <tokens>...

In your particular case, this is how an overloaded structure expands for one, two, and three arguments:

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

one argument:

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

two arguments:

 template<typename A, typename B> struct overloaded: A, B { using A::operator(); using B::operator(); }; 

three arguments:

 template<typename A, typename B, typename C> struct overloaded: A, B, C { using A::operator(); using B::operator(); using C::operator(); }; 
+5
source

Source: https://habr.com/ru/post/1272391/


All Articles