包含模板参数包的函数模板的部分排序与这些模板参数包的推导参数的数量无关。
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
上述内容来自partial ordering of overload function templates。我不太明白为什么g(Tuple<int>()); // calls #3
。具体来说,为什么不能调用#2
?以下是我的推理,请指出任何错误:
注意:我会忽略#1
b / c它被解释得很清楚here
第1步:扣除和替换以及重载解决方案可以解决这些问题:
第2步:转换两个功能模板:
Step3 :这是一个函数调用上下文,类型是函数调用具有参数的函数参数类型:
3)如果P是引用类型,P引用的类型用于推断。This似乎没问题。
如果P具有包含模板参数列表的形式之一&lt; T&GT;或者&lt; I&gt;,然后该模板参数列表的每个元素Pi与其A的对应模板参数Ai匹配。如果最后一个Pi是包扩展,则将其模式与A的模板参数列表中的每个剩余参数进行比较。未以其他方式推导出的尾随参数包将推导为空参数包。
Step4 :如果最后一步是正确的。那么它意味着#3不比#2更专业。因此,应该解析哪个函数模板是不明确的。
更新:我想我误解了上面的相关引言。当我们将P中的模板参数与A中的模板参数匹配时,它们会逐字匹配,这意味着当我们匹配时,在函数调用参数和参数上完成的所有变换和分析都不会再次应用P / A中的模板参数/参数(实际上是函数调用参数/参数)。然后,它将在上面的第3步中失败,从Tuple< T1, Types&...>
中推断Tuple< C1, Pack1...>)
。所以#3
更专业。
答案 0 :(得分:3)
重载决议的第一步是找到所有候选人。对于电话g(Tuple<int>())
,有三个可行的候选人:
g(Tuple<int>); // #1: [Types = {int}]
g(Tuple<int>); // #2: [T1 = int, Types = {}]
g(Tuple<int>); // #3: [T1 = int, Types = {}]
从转换序列的角度来看,这三个都是同样可行的候选者(当然,因为它们都采用相同的参数,即输入的精确匹配)。它们都是功能模板专业化,因此我们也不能在此基础上进行区分。
所以我们留下了功能模板的部分排序。我们为每个重载合成类型,并尝试使用我们的合成类型对每个其他重载执行模板推导。更简单的比较是(1)vs(2)和(1)vs(3)。在那里,我们有[temp.deduct.partial]:
如果A是从函数参数包转换而P不是参数包,则类型推导失败。
由于我们将Types...
转换为UniqA...
,并且第一个参数为T1
,因此类型扣除失败。这使得(2)和(3)都比(1)更专业。所以现在我们比较(2)和(3)。
首先,我们可以从(3)中推断出(2)吗?
template <class T1, class... Types> void g(Tuple<T1, Types...> );
g(Tuple<Uniq3, Uniq3Pack&...>());
当然,没问题T1 == Uniq3
和Types... == Uniq3Pack&...
。接下来,我们尝试另一个方向:
template <class T1, class... Types> void g(Tuple<T1, Types&...> );
g(Tuple<Uniq2, Uniq2Pack...>());
此操作失败,因为Uniq2Pack
不是一组引用类型,而Types&...
是。{1}}。由于演绎仅在一个方向上成功,这使得(3)更加专业化的过载。
由于它是最后一个,(3)是最好的可行候选人。这似乎违反直觉,因为我们实际上并没有用引用类型来调用它 - 但它仍然是最好的选择。