具体地,
template<class...> struct Tuple { };
template< class... Types> void g(Tuple<Types ...>); // #1
// template<class T1, class... Types> void g(Tuple<T1, Types ...>); // #2
template<class T1, class... Types> void g(Tuple<T1, Types& ...>); // #3
g(Tuple<>()); // calls #1
g(Tuple<int, float>()); // calls #2
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>()); // calls #3
如果取消注释#2
,则会按照评论中的说明解析g()&。让我感到惊讶的是,如果我注释掉#2
行,g()的调用将按如下方式解析:
g(Tuple<>()); // calls #1
g(Tuple<int, float>()); // calls **#1 ???? why not #3????**
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>()); // calls #3
通过以下示例和说明,我无法理解为什么g(Tuple<int, float>());
无法解析为#3
。它是以下两条规则的方向应用:
如果参数包显示为最后一个P,则类型P与呼叫的每个剩余参数的类型A匹配。每个匹配都会推断包扩展中下一个位置的模板参数。
template<class ... Types> void f(Types& ...);
void h(int x, float& y) {
const int z = x;
f(x, y, z); // P=Types&..., A1=x: deduces the first member of Types... to int
// P=Types&..., A2=y: deduces the second member of Types... to float
// P=Types&..., A3=z: deduces the third member of Types... to const int
// calls f<int, float, const int>
如果P具有包含模板参数列表
<T>
或<I>
的表单之一,则该模板参数列表的每个元素Pi与其A的对应模板参数Ai匹配。最后一个Pi是一个包扩展,然后将它的模式与A的模板参数列表中的每个剩余参数进行比较。一个未被推导出的尾随参数包,推断为空参数包。
答案 0 :(得分:2)
你的两个例子之间存在误解。在使用f
的第二个示例中,您将向函数推断引用参数。在带有g
的第一个示例中,您将参考模板参数推断为函数的参数。后者必须完全匹配,但前者是根据引用的类型推断出来的。它们不一样。
在你的第一个例子中,
g(Tuple<int, float>());
无法致电g(Tuple<T1, Types&...>)
。模板推导过程是关于选择与被调参数类型相同的推导出的参数类型。有一些例外(对于引用的cv资格,指针,派生类,数组,函数),但这些都不适用于此。我们只需选择T1
和Types...
,Tuple<T1, Types&...>
与Tuple<int, float>
的类型相同。这是不可能的,因为Types...
Types&...
不是{float}
,因为float
不是参考!
所以一旦你评论出(2),只有一个可行的候选人:(1)。
另一方面,
template<class ... Types> void f(Types& ...);
void h(int x, float& y) {
const int z = x;
f(x, y, z);
}
这里,Types&...
实际上是参数本身的类型(而不是它的模板参数),所以(temp.deduct.call):
如果P是引用类型,则P引用的类型用于类型推导。
我们推导出Types...
以匹配参数。这是成功的,因为所有参数都是左值,我们只需选择{int, float, const int}
。