`operator()...`在C++代码中意味着什么?

msc*_*msc 6 c++ templates variant operator-keyword c++17

我试图理解std::visit来自cppreference的例子,我在哪里看到以下代码行:

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
Run Code Online (Sandbox Code Playgroud)

我不明白.operator()...代码中的含义是什么?

And*_*dyG 10

我想通过一些历史课在这里添加很好的答案.

这里有很多层,所以让我们一个接一个地剥离它们.

  • 可变参数模板(C++ 11)
    • 参数包
    • 包装扩张
  • using声明
    • 引入基类成员
    • 可变using宣言(C++ 17)
  • 模板演绎指南(C++ 17)

Variadic模板

在C++之前的版本11中,我们受限于函数可以通过程序员愿意输入多少来接收的模板参数的数量.

例如,如果我想编写一个函数来总结可能不同类型的"任意"数量的值,我需要编写大量的样板文件,即便如此我也受到限制:

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
Run Code Online (Sandbox Code Playgroud)

在C++ 11中,我们最终收到了"可变参数模板",这意味着我们可以通过使用省略号(...)来接收"无限制"(由编译器确定的实际限制)模板参数的数量,所以现在我们可以编写

template<class... T>
void foo(T... args){}
Run Code Online (Sandbox Code Playgroud)

这个"无限数量"的模板参数class... T被称为"参数包",因为它代表一组参数并不令人惊讶.

要将这些参数"解包"到以逗号分隔的列表中,我们在函数参数列表中再次使用省略号:void foo(T... args){}.这称为包扩展,同样不是一个令人惊讶的名称.

函数调用的包扩展结果如下:

int a = /*...*/;
double b = /*...*/;
char c = /*...*/;
foo(a, b, c);
Run Code Online (Sandbox Code Playgroud)

可以这样想:

template<int, double, char>
void foo(Arguments[3] args){}
Run Code Online (Sandbox Code Playgroud)

其中Arguments是一种(异质阵列int,double,char).

这些可变参数模板也适用于classstruct模板,因此这里的模拟就是这样

template<class... Ts> struct overloaded
Run Code Online (Sandbox Code Playgroud)

声明一个overload可以在"无限"数量类型上模板化的类.

它的':Ts ...'部分:

template<class... Ts> struct overloaded : Ts...
Run Code Online (Sandbox Code Playgroud)

使用包扩展来声明overloaded要从每个类型派生(可能通过多重继承)的类.


using声明

在Pre-C++ 11中我们可以用typedef类似的方式声明类型别名:

typedef unsigned int uint;
Run Code Online (Sandbox Code Playgroud)

在C++ 11中,我们收到的using声明可以做同样的事情,也许更清楚一点(还有更多!只是坚持下去)

using uint = unsigned int;
Run Code Online (Sandbox Code Playgroud)

但是,一个using声明最初用于不同的东西(自从引入C++ 11以来,它的使用已经大大扩展).创建它的主要原因之一是,我们可以在派生类中重用基类中的东西,而不必强制客户端消除歧义:

没有 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);

}
Run Code Online (Sandbox Code Playgroud)

请注意,客户端被迫编写一些时髦的语法来引用derived他们想要用于调用的基数do_a_thing.如果我们利用以下优势,这看起来更好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
}
Run Code Online (Sandbox Code Playgroud)

清洁,对吧?


可变using宣言

所以C++ 11问世,我们对这些新功能印象深刻,但是using没有解决的语句存在一个小差距; "如果我想using为每个基类创建一个,那些基类是模板参数怎么办?"

所以像这样:

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
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但是既然我们了解了可变参数模板,那就让我们做derived一个:

template<class... Ts>
struct derived : Ts...
{
   //using ?
};
Run Code Online (Sandbox Code Playgroud)

当时,using由于缺乏可变的支持而受到妨碍,所以我们不能这么做(很容易).

然后C++ 17出现并给了我们variadic使用支持,以便我们可以做到:

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
}
Run Code Online (Sandbox Code Playgroud)

我们终于可以理解你代码的第一部分了!

所以现在我们终于可以理解这部分问题的全部内容:

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...;};
Run Code Online (Sandbox Code Playgroud)

我们有一个名为overload"无限"类型的模板.它源于每种类型.它还允许您使用operator()每种父类型的方法.方便,对吧?(注意,如果任何基类operator()看起来相同,我们会收到错误.)


模板演绎指南

另一个让C++开发人员错过了一段时间的事情是,如果你有一个模板化的类也有一个模板化的构造函数,你必须显式指定模板参数,即使你认为对你自己和你的客户来说显然模板类型应该是什么是.

例如,我想编写一个轻量级迭代器包装器:

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++ << " ";
        } 
    } 
};
Run Code Online (Sandbox Code Playgroud)

现在,如果我作为调用者想要创建一个实例IteratorWrapper,我必须做一些额外的工作来消除究竟是什么T,因为它没有包含在构造函数的签名中:

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

没有人愿意写那些怪物.因此,C++ 17引入了演绎指南,我们作为班级作者可以做一些额外的工作,以便客户不必这样做.现在我作为班级作者可以写这个:

template<template<class...> class Container, class... Args>
IteratorWrapper(const Container<Args...>& c) -> IteratorWrapper<typename Container<Args...>::const_iterator>;
Run Code Online (Sandbox Code Playgroud)

其中模仿IteratorWrappers构造函数的签名,然后使用尾随箭头(->)来指示ItearatorWrapper要推导的类型.

所以现在我的客户可以编写如下代码:

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

std::list<double> lst{4.1, 5.2};
IteratorWrapper lst_wrapper(lst);
Run Code Online (Sandbox Code Playgroud)

漂亮吧?


我们现在可以理解第二行代码了

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
Run Code Online (Sandbox Code Playgroud)

为我们的类声明了一个模板推导指南,overloaded指南指出当使用参数包调用其构造函数时,该类也应该在这些类型上进行模板化.

这可能听起来没必要,但是如果你有一个带有模板化构造函数的模板化类,你可能会想要它:

template<class... T>
struct templated_ctor{
    template<class... U>
     overloaded(U...){}
};
Run Code Online (Sandbox Code Playgroud)

*我知道我在这里过火了,但写起来并且真正彻底回答这个问题很有趣:-)


eer*_*ika 8

要理解using Ts::operator()...;,首先您必须知道这class... Ts是一个参数包(可变参数模板).它是0 ... N模板类型参数的序列.

省略号using Ts::operator()...是参数包扩展的语法.在的情况下,overloaded<Foo, Bar>例如,该using Ts::operator()...;声明将扩大到相当于:

using Foo::operator();
using Bar::operator();
Run Code Online (Sandbox Code Playgroud)


utn*_*tim 5

这里的语法是<tokens>....

在您的特定情况下,以下是如何为一个,两个和三个参数扩展重载结构:

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
Run Code Online (Sandbox Code Playgroud)

一个论点:

template <class A> struct overloaded : A { using A::operator(); };
Run Code Online (Sandbox Code Playgroud)

两个论点:

template<typename A, typename B>
struct overloaded: A, B
{
    using A::operator(); using B::operator();
};
Run Code Online (Sandbox Code Playgroud)

三个论点:

template<typename A, typename B, typename C>
struct overloaded: A, B, C
{
    using A::operator(); using B::operator(); using C::operator();
};
Run Code Online (Sandbox Code Playgroud)