What does the "operator()..." syntax mean in C++?
Asked Answered
K

3

12

I'm trying to understand the example of std::visit 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 don't understand. What does operator()... mean in the code?

Karoline answered 6/10, 2017 at 11:43 Comment(4)
It's not the ... operator, it's the call operator operator() with a variadic template expansion. It essentially means using T1::operator(); using T2::operator(); ... for all Ts in Ts.Vengeful
What part is confusing to you? operator() or ...?Deane
Three dot after operator is confusing?Karoline
en.cppreference.com/w/cpp/language/parameter_packDeane
P
24

I'd like to add to the great answers here with a bit of a history lesson.

There are a lot of layers going on here, so let's peel them back one by one.

  • variadic templates (C++11)
  • parameter packs
  • pack expansion
  • the using declaration
  • for introducing base class members
  • variadic using declaration (C++17)
  • template deduction guides (C++17)

Variadic Templates

Pre-C++11, we were limited on the number of template arguments a function could receive by how much the programmers were willing to type.

For example, if I wanted to write a function to sum up an "arbitrary" number of values for potentially different types, I needed to write a whole lot of boilerplate, 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's enough

In C++11 we finally received "variadic templates", which means we can receive an "unlimited" (actual limit determined by your compiler) number of template arguments by using an ellipsis (...), so now we can write

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

This "unlimited number" of template arguments, class... T, is called a "parameter pack" because it's unsurprisingly representing a pack of parameters.

To "unpack" those parameters into a comma-separated list, we use the ellipsis again in the function parameter list: void foo(T... args){}. This is called pack expansion, again, not a surprising name.

The result of pack expansion for a function call like this:

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

Can be thought of like this:

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

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

These variadic templates also apply to class and struct templates, so the analog here is that

template<class... Ts> struct overloaded

declares a class overloaded that can be templated on an "unlimited" number of types.

The : Ts... portion of it:

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

uses pack expansion to declare the class overloaded to derive (potentially via multiple inheritance) from each of those types.


The using declaration

Pre-C++11 we could declare type aliases with a typedef like so:

typedef unsigned int uint;

In C++11 we received the using statement that can do the same thing, perhaps a little more clearly (and so much more! just hang on)

using uint = unsigned int;

However, a using statement was originally used for something different (its usage has expanded greatly since the introduction of C++11). One of the main reasons it was created was so that we could re-use things from base classes in derived classes without forcing the client to disambiguate:

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's ambiguous, so there's 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 which base of derived they want to use for the call to do_a_thing. This looks nicer if we take advantage of 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 we were all impressed by these new features, but there was one small gap for using statements that wasn't addressed; "What if I want to have a using for each base class, where those 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 variadic templates, let's make derived one:

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

At the time, using was handicapped by its lack of variadic support, so we couldn't do this (easily).

Then C++17 came along and gave us variadic using support so that we could do it:

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
}

We can finally understand the first part of your code!

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

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

We have a class named overloaded that is templated on an "unlimited" number of types. It derives from each of those types. And it also allows you to use the operator() method of each of those parent types. Convenient, right? (Note that if any of the base class' operator() looked the same, we'd get an error.)


Template deduction guides

One other thing that has bugged C++ developers for a while is that if you had a templated class that also had a templated constructor, you had to explicitly specify template arguments even when you thought it was obvious to yourself and your client what the template type should be.

For example, I will want to write a lightweight iterator wrapper:

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 do some extra legwork to disambiguate exactly what T was because it's not included in the signature of the constructor:

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

Nobody wants to write that monstrosity. So C++17 introduced deduction guides where us as the class writer could do a little extra work so that the client wouldn't have to. Now I as the class author 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 IteratorWrappers constructor and then uses a trailing arrow (->) to indicate the type of ItearatorWrapper to deduce.

So now my clients can write code like this:

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?


We can now understand the second line of code

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

Declares a template deduction guide for our class overloaded that says that when its constructor is called with a parameter pack, then the class should also be templated on those same types.

That might sound unnecessary, but you might want it if you had a templated class with a templated constructor:

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

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

Perception answered 6/10, 2017 at 20:7 Comment(2)
using declaration inside struct is pre C++11.Hastate
@Eugene: Thanks! Updated for historical accuracy.Perception
M
10

To understand using Ts::operator()...;, first you must know that class... Ts is a parameter pack (of a variadic template). It is a sequence of 0 ... N template type parameters.

The ellipsis in using Ts::operator()... is syntax for parameter pack expansion. In the case of overloaded<Foo, Bar> for example, the using Ts::operator()...; declaration would be expanded to equivalent of:

using Foo::operator();
using Bar::operator();
Matte answered 6/10, 2017 at 11:52 Comment(0)
P
5

The syntax here is <tokens>....

In your particular case, here is how overloaded structure is expanded 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();
};
Predicant answered 6/10, 2017 at 11:47 Comment(2)
Perhaps a more obvious equivalent would be using A::operator(), B::operator(); (which is now also allowed in C++17), to be more obviously the expansion of a list within a single using-declaration.Horatia
@Caleth What do you mean? As defined by the grammar, "using Ts::operator()...;" is a single using-declaration. "using A::operator(), B::operator();" is a single using-declaration. "using A::operator(); using B::operator();" is two using-declarations. The meaning of the last two are exactly the same.Horatia

© 2022 - 2024 — McMap. All rights reserved.