简体   繁体   English

`operator()...` 在 C++ 代码中是什么意思?

[英]What does `operator()…` mean in code of C++?

I'm trying to understand the example of std::visit from cppreference , Where I saw the following line of code:我试图从cppreference理解std::visit的例子,在那里我看到了以下代码行:

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? operator()...在代码中是什么意思?

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)可变参数模板 (C++11)
  • parameter packs参数包
  • pack expansion包装扩展
  • the using declaration using声明
  • for introducing base class members用于引入基类成员
  • variadic using declaration (C++17)可变参数using声明 (C++17)
  • template deduction guides (C++17)模板推导指南 (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.在 C++11 之前,我们对函数可以接收的模板参数数量受到程序员愿意输入的数量的限制。

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在 C++11 中,我们终于收到了“可变参数模板”,这意味着我们可以通过使用省略号 (...) 来接收“无限”(由编译器确定的实际限制)数量的模板参数,所以现在我们可以编写

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.这个“无限数量”的模板参数, class... T ,被称为“参数包”,因为它毫不奇怪地表示一组参数。

To "unpack" those parameters into a comma-separated list, we use the ellipsis again in the function parameter list: void foo(T... args){} .为了将这些参数“解包”到一个逗号分隔的列表中,我们在函数参数列表中再次使用省略号: 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 ).其中Arguments是一种 ( int , double , char ) 的异构数组。

These variadic templates also apply to class and struct templates, so the analog here is that这些可变参数模板也适用于classstruct模板,所以这里的类比是

template<class... Ts> struct overloaded

declares a class overloaded that can be templated on an "unlimited" number of types.声明一个overloaded的类,可以在“无限”数量的类型上进行模板化。

The : Ts... portion of it: : Ts...部分:

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.使用包扩展来声明overloaded的类以从这些类型中的每一个派生(可能通过多重继承)。


The using declaration using声明

Pre-C++11 we could declare type aliases with a typedef like so:在 C++11 之前,我们可以使用typedef声明类型别名,如下所示:

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)在 C++11 中,我们收到了可以做同样事情的using语句,也许更清楚一点(还有更多!等一下)

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).但是, using语句最初用于不同的用途(自 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 usingusing

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 .请注意,客户端被迫编写一些时髦的语法来引用他们想要使用derived哪个基础来调用do_a_thing This looks nicer if we take advantage of using :如果我们利用using这看起来会更好:

With using :随着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可变参数using声明

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;所以 C++11 出现了,这些新特性给我们留下了深刻的印象,但是using语句有一个小差距没有解决; "What if I want to have a using for each base class, where those base classes are template arguments?" “如果我想为每个基类using一个using ,这些基类是模板参数怎么办?”

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:但既然我们了解了可变参数模板,让我们制作一个derived模板

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).当时, using由于缺乏可变参数支持而受到阻碍,因此我们无法(轻松)做到这一点。

Then C++17 came along and gave us variadic using support so that we could do it:然后 C++17 出现并为我们提供了可变参数 using 支持,以便我们可以做到:

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.我们有一个名为overloaded的类,它以“无限”数量的类型为模板。 It derives from each of those types.它派生自这些类型中的每一种。 And it also allows you to use the operator() method of each of those parent types.它还允许您使用每个父类型的operator()方法。 Convenient, right?方便吧? (Note that if any of the base class' operator() looked the same, we'd get an error.) (请注意,如果任何基类的operator()看起来相同,我们就会得到一个错误。)


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.另一件困扰 C++ 开发人员一段时间的事情是,如果您有一个模板化类,该类也有一个模板化构造函数,即使您认为模板类型对您自己和您的客户来说是显而易见的,您也必须明确指定模板参数是。

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:现在,如果我作为调用者想要创建一个IteratorWrapper的实例,我必须做一些额外的工作来明确T是什么,因为它没有包含在构造函数的签名中:

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.因此,C++17 引入了演绎指南,我们作为班级编写者可以做一些额外的工作,这样客户就不必这样做了。 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.它模仿IteratorWrappers构造函数的签名,然后使用尾随箭头 ( -> ) 来指示要推断的ItearatorWrapper的类型。

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.为我们overloaded的类声明模板推导指南,说明当使用参数包调用其构造函数时,该类也应该在这些相同类型上进行模板化。

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 :-) *我知道我在这里过火了,但是写下来并真正彻底地回答这个问题很有趣:-)

To understand using Ts::operator()...;理解using Ts::operator()...; , first you must know that class... Ts is a parameter pack (of a variadic template). ,首先你必须知道那个class... Ts是一个参数包(一个可变参数模板)。 It is a sequence of 0 ... N template type parameters.它是 0 ... N 个模板类型参数的序列。

The ellipsis in using Ts::operator()... is syntax for parameter pack expansion . using Ts::operator()...的省略号是参数包扩展的语法。 In the case of overloaded<Foo, Bar> for example, the using Ts::operator()...;例如,在overloaded<Foo, Bar>的情况下, using Ts::operator()...; declaration would be expanded to equivalent of:声明将扩展为等效于:

using Foo::operator();
using Bar::operator();

The syntax here is <tokens>... .这里的语法是<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();
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM