我正在尝试从视频C++ Weekly - Ep 48 - C++17's Variadic using
重现结果,但失败了。问题可以简化为以下代码段。
假设我有这样的通用结构:
template <class... T>
struct Container {
template <class... U>
Container(U... us) {}
};
现在我可以使用任何参数初始化Container
,例如
auto d = Container(1,2,3);
但是,编译器永远不会知道d
的类型。要解决这个问题,我们应该提供一个扣除指南,例如
template <class... U>
Container(U...) -> Container<double, int, bool>
根据视频,编译器现在应该知道d
具有类型Container<double, int, bool>
。
但是,代码无法正常工作。打印typeid(d).name()
时,输出将始终为9ContainerIJEE
,无论我如何更改演绎指南中的返回类型,都会将其翻译为Container<>
,表明此指南不会指导编译器。
我正在使用gcc-7-snapshot-20170402
,视频中的编译器是gcc-7-snapshot-20170130
。
有人能告诉我这里有什么问题吗?
顺便说一下,如果我明确写了
Container<bool, int> d = Container(1,2,3);
Container<char, char, char> d = Container(1,2,3);
...
代码将始终编译,并提供9containerIJbiEE
和9containerIJcccEE
等输出。
答案 0 :(得分:4)
现在我可以使用任何参数初始化Container,例如
auto d = Container(1,2,3);
但是,编译器永远不会知道
d
的类型。
这不是完全准确。此处d
的类型为Container<>
。让我们远离构造函数的模板推导,转到简单的函数模板:
template <class... Ts, class... Us>
void foo(Us... );
foo(1, 2, 3);
该函数调用完全没问题 - 我们将Us
推导为{int,int,int}
,我们将Ts
推导为{}
。那是因为[temp.arg.explicit]/3:
未以其他方式推导出的尾随模板参数包将被推导为空的模板参数序列。如果可以推导出所有模板参数,则可以省略它们;在这种情况下,空模板参数列表
<>
本身也可以省略。
现在,什么是尾随模板参数包?它没有具体说明。但是这里Ts...
可以推断为空,所以它是。请注意,这有一些奇怪的含义,例如:
template <class... Ts> void g(std::tuple<Ts...> );
g({}); // ok, calls g<>?
回到原来的问题。声明
Container d(1, 2, 3);
没有任何演绎指南的格式正确,因为我们可以成功地进行模板演绎并将Ts...
推断为空。也就是说,它完全等同于:
Container<> d(1, 2, 3);
那么当我们添加演绎指南时会发生什么?现在,我们在以下各项之间有效地执行重载解析:
template <class... Ts, class... Us>
Container<Ts...> f(Us... ); // the constructor template
template <class... Us>
Container<double, int, bool> f(Us... ); // the deduction guide
f(1, 2, 3); // what does this return?
确定最佳候选人的决胜论者在[over.match.best],其中相关的两个是:
鉴于这些定义,如果[...]
,可行函数F1
被定义为更好函数而不是另一个可行函数F2
。
F1
和F2
是函数模板特化,F1
的函数模板比F2
的模板更专业,根据[[1}}中描述的部分排序规则[ temp.func.order],或者,如果不是,F1
是从演绎指南([over.match.class.deduct])生成的,F2
不是,或者,如果不是,[...]
功能模板偏序排序is really quite complicated并且编制者并不完全同意在所有情况下该怎么做。但在这种情况下,我会说这是一个gcc bug(我提交了80871)。根据{{3}}:
用于确定排序的类型取决于完成部分排序的上下文:
- 在函数调用的上下文中,使用的类型是函数调用具有参数的函数参数类型。
也就是说,第一个函数模板中的Ts...
(从构造函数合成的模板)不用于部分排序。这使得两个函数模板都相同,因此两者都不比另一个更专业。我们应该然后落入下一个项目符号,它告诉我们更喜欢构造函数的推导指南,最后得到Container<double, int, bool>
。然而,gcc认为,由于某种原因,第一个功能模板更加专业化,因此在进入演绎指南决胜局之前选择它,这就是为什么它以Container<>
结束。