我试图重现视频C++ Weekly - Ep 48 - C++ 17的Variadic的结果using,但失败了.问题可以简化为以下代码段.
假设我有这样的通用结构:
template <class... T>
struct Container {
template <class... U>
Container(U... us) {}
};
Run Code Online (Sandbox Code Playgroud)
现在我可以Container用任何参数初始化a ,比如
auto d = Container(1,2,3);
Run Code Online (Sandbox Code Playgroud)
但是,编译器永远不会知道它是什么类型d.要解决这个问题,我们应该提供一个扣除指南,例如
template <class... U>
Container(U...) -> Container<double, int, bool>
Run Code Online (Sandbox Code Playgroud)
根据视频,编译器现在应该知道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);
...
Run Code Online (Sandbox Code Playgroud)
代码将始终编译,并提供像9containerIJbiEE
和的输出9containerIJcccEE.
现在,我可以使用任何参数初始化Container,例如
Run Code Online (Sandbox Code Playgroud)auto d = Container(1,2,3);但是,编译器永远不会知道它是什么类型
d。
这并不完全正确。该类型d是Container<>在这里。让我们远离构造函数的模板推导,而转到简单的函数模板:
template <class... Ts, class... Us>
void foo(Us... );
foo(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)
该函数调用完全可以-我们推导出Us为be,{int,int,int}然后推导Ts为be {}。那是因为[temp.arg.explicit] / 3:
否则未推断出的尾随模板参数包将被推断为模板参数的空序列。如果可以推导出所有模板参数,则可以全部省略;在这种情况下,
<>也可以省略空的模板参数列表本身。
现在,什么是尾随模板参数包?未指定。但是这里Ts...可以推导出为空,就是这样。请注意,这有一些奇怪的含义,例如:
template <class... Ts> void g(std::tuple<Ts...> );
g({}); // ok, calls g<>?
Run Code Online (Sandbox Code Playgroud)
另请参阅此简短讨论。
回到原来的问题。报关单
Container d(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)
没有任何推导的格式是正确的,因为我们可以成功进行模板推导并推论Ts...为空。也就是说,它完全等同于:
Container<> d(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)
那么,当我们添加一个推导指南时会发生什么?现在,我们正在以下有效地执行重载解析:
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?
Run Code Online (Sandbox Code Playgroud)
确定最佳可行候选人的决胜局在[over.match.best]中,其中两个相关的是:
给定这些定义,如果[...]则将可行的功能
F1定义为比另一个可行的功能更好的功能F2。
F1和F2是功能模板的专业化,并且功能模板F1比F2根据[temp.func.order]中描述的部分排序规则的模板更专业,或者,如果不是的话,F1是从演绎指南([over.match.class.deduct])生成的F2,如果不是,则为[...]
函数模板部分排序是 真的 相当 复杂和编译器不太在所有情况下该怎么做达成一致。但是在这种情况下,我会说这是一个gcc错误(我提交了80871)。根据[temp.deduct.partial]:
用于确定排序的类型取决于完成部分排序的上下文:
- 在函数调用的上下文中,使用的类型是函数调用具有参数的那些函数参数类型。
也就是说,Ts...第一个函数模板(由构造函数合成的模板)中的并不用于部分排序。这使两个功能模板完全相同,因此两者都不比另一个更专业。然后,我们应该进入下一个项目符号,该项目符号告诉我们更喜欢推导指南,而不是构造函数,最后得到Container<double, int, bool>。但是,gcc出于某种原因认为第一个功能模板更加专业化,因此在到达推导指南决胜局之前就选择了它,这就是为什么它以结尾Container<>。