我试图理解 std::visit
的例子来自 cppreference ,在那里我看到了以下代码行:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
我不明白。什么
operator()...
在代码中是什么意思?
最佳答案
我想用一些历史课来补充这里的好答案。
这里有很多层,所以让我们一层一层地剥开它们。
using
声明using
声明 (C++17) 可变模板
在 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
在 C++11 中,我们终于收到了“可变参数模板”,这意味着我们可以通过使用省略号 (...) 来接收“无限”(由编译器确定的实际限制)数量的模板参数,所以现在我们可以编写template<class... T>
void foo(T... args){}
这个“无限数量”的模板参数,class... T
, 被称为“参数包”,因为它毫不奇怪地表示一组参数。为了将这些参数“解包”到一个逗号分隔的列表中,我们再次在函数参数列表中使用省略号:
void foo(T... args){}
.这再次被称为包扩展,这并不奇怪。函数调用的包扩展结果如下:
int a = /*...*/;
double b = /*...*/;
char c = /*...*/;
foo(a, b, c);
可以这样想:template<int, double, char>
void foo(Arguments[3] args){}
哪里Arguments
是一种 ( int
, double
, char
) 的异构数组。这些可变参数模板也适用于
class
和 struct
模板,所以这里的模拟是template<class... Ts> struct overloaded
声明一个类 overloaded
可以在“无限”数量的类型上进行模板化。: Ts...
其中的一部分:template<class... Ts> struct overloaded : Ts...
使用包扩展来声明类 overloaded
从这些类型中的每一种派生(可能通过多重继承)。using
宣言在 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
的基数。他们想用于拨打 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
}
更干净,对吧?可变参数
using
宣言所以C++11出来了,这些新特性给我们留下了深刻的印象,但
using
还有一个小差距。 Unresolved 陈述; “如果我想为每个基类设置一个 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 ?
};
当时,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()...;};
我们有一个名为 overloaded
的类以“无限”数量的类型为模板。它派生自这些类型中的每一种。并且它还允许您使用 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++ << " ";
}
}
};
现在,如果我作为调用者想要创建一个 IteratorWrapper
的实例,我不得不做一些额外的跑腿工作来消除歧义 T
是因为它没有包含在构造函数的签名中:std::vector<int> vec{1, 2, 3};
IteratorWrapper<typename std::vector<int>::const_iterator> iter_wrapper(vec);
没有人想写那个怪物。因此,C++17 引入了演绎指南,我们作为类(class)编写者可以做一些额外的工作,这样客户就不必这样做了。现在我作为类(class)作者可以这样写: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...>;
宣布我们类(class)的模板推导指南overloaded
也就是说,当使用参数包调用其构造函数时,该类也应该在这些相同类型上进行模板化。这听起来可能没有必要,但如果您有一个带有模板化构造函数的模板化类,您可能会想要它:
template<class... T>
struct templated_ctor{
template<class... U>
overloaded(U...){}
};
*我知道我在这里过火了,但是写下来并真正彻底地回答这个问题很有趣:-)
关于c++ - `operator()...`在C++代码中是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46604950/