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

时间:2017-10-06 11:43:43

标签: c++ templates operator-keyword c++17 variant

我试图从cppreference了解std::visit的示例,其中我看到以下代码行:

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

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

3 个答案:

答案 0 :(得分:9)

我想通过一些历史课来增加这里的好答案。

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

  • 可变参数模板(C ++ 11)
    • 参数包
    • pack expansion
  • using声明
    • 介绍基类成员
    • variadic using声明(C ++ 17)
  • 模板演绎指南(C ++ 17)

Variadic模板

Pre-C ++ 11,我们受限于函数可以通过程序员愿意输入的数量来获得的模板参数的数量。

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

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

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

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

此&#34;无限数量&#34;模板参数class... T被称为&#34;参数包&#34;因为它代表一组参数并不令人惊讶。

To&#34; unpack&#34;将这些参数放入逗号分隔列表中,我们在函数参数列表中再次使用省略号:void foo(T... args){}。这又称为 pack扩展,这也不是一个令人惊讶的名字。

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

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

可以这样想:

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

Arguments是一种(intdoublechar)的异构数组。

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

template<class... Ts> struct overloaded

声明一个类overload,可以在“无限制”的情况下模板化。类型数量。

&#39;:Ts ......部分:

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

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

using声明

Pre-C ++ 11我们可以使用typedef声明类型别名,如下所示:

typedef unsigned int uint;

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

using uint = unsigned int;

但是,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);

}

请注意,客户端必须编写一些时髦的语法来引用他们想要用于调用derived的{​​{1}}个基数。如果我们利用do_a_thing

,这看起来会更好

使用using

using

清洁工,对吧?

Variadic 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 } 声明

所以C ++ 11问世了,我们对这些新功能印象深刻,但using语句有一个小差距没有得到解决; &#34;如果我希望每个基类都有using,那些基类是模板参数怎么办?&#34;

这样的事情:

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 } 一个:

derived

当时template<class... Ts> struct derived : Ts... { //using ? }; 由于缺乏可变支持而受到妨碍,因此我们无法轻易做到这一点。

然后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
}

我们有一个名为template<class... Ts> struct overloaded : Ts... { using Ts::operator()...;}; 的课程,该课程在&#34;无限制&#34;类型数量。它源于每种类型。它还允许您使用每种父类型的overload方法。方便,对吧? (请注意,如果任何基类&#39; operator()看起来相同,我们就会收到错误。)

模板扣除指南

另一个让C ++开发人员陷入困境的事情是,如果你有一个模板化的类也有一个模板化的构造函数,你必须明确指定模板参数,即使你认为这对你自己和你的客户来说是显而易见的模板类型应该是。

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

operator()

现在,如果我作为调用者想要创建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++ << " "; } } }; 的实例,我必须做一些额外的工作来消除IteratorWrapper的确切消歧,因为它不包含在签名中构造函数:

T

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

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

其中模仿template<template<class...> class Container, class... Args> IteratorWrapper(const Container<Args...>& c) -> IteratorWrapper<typename Container<Args...>::const_iterator>; 构造函数的签名,然后使用尾随箭头(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);

为我们的类template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; 声明一个模板推导指南,它说当使用参数包调用其构造函数时,该类也应该模仿这些相同的类型。

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

overloaded

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

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:5)

此处的语法为<tokens>...

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

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

一个论点:

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

两个论点:

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

三个论点:

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