【问题标题】:Function template argument deduction with variadic class template as function call parameter以可变参数类模板作为函数调用参数的函数模板实参推导
【发布时间】:2015-10-29 23:19:40
【问题描述】:

所有示例均来自herehere

具体来说,

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() 将按照 cmets 中的说明进行解析。让我吃惊的是,如果我注释掉#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&lt;int, float&gt;()); 无法解析为#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 具有包含模板参数列表 &lt;T&gt;&lt;I&gt; 的形式之一,则该模板参数列表的每个元素 Pi 与其 A 的相应模板参数 Ai 匹配。如果最后一个 Pi 是一个包扩展,然后将其模式与 A 的模板参数列表中的每个剩余参数进行比较。没有以其他方式推导的尾随参数包被推导为一个空参数包。

【问题讨论】:

  • 如果目标类型之一不是引用 (float),您如何在 Types&amp;... 中推断出 Types
  • 没有Types... 的值会使Types&amp;... 等于float
  • @aschepler 我希望规则就这么简单。但是当 P 是 的形式时,它就像我引用的那样被分解。所以最终被扣除的是以下对 (T1/int) 和 (Types&.../float)。作为我引用的示例代码,可以使用 P=Types&... 和 A= float:结果是 Pack Types... 的第一个成员只是 float
  • @Columbo 如果您说的是正确的,示例代码 f(x,y,z) 是如何工作的?函数参数都是引用,但 x、y 和 z 都不是引用。
  • @aschepler @ Columbo 你们也可以对相关问题发表评论吗*.com/q/33426038/1021388

标签: c++ templates overload-resolution


【解决方案1】:

您的两个示例之间存在误解。在f 的第二个示例中,您将引用 arguments 推断为函数。在g 的第一个示例中,您将引用 模板参数 推断为函数的参数。后者必须完全匹配,但前者是根据引用的类型推导出来的。它们不一样。


在你的第一个例子中,

g(Tuple<int, float>());

无法呼叫g(Tuple&lt;T1, Types&amp;...&gt;)。模板推导过程是关于选择与被调用参数类型相同的推导参数类型。有一些例外(对于引用的 cv 限定、指针、派生类、数组、函数),但这些都不适用。我们只需要选择T1Types... 使得Tuple&lt;T1, Types&amp;...&gt;Tuple&lt;int, float&gt; 的类型相同。这是不可能的,因为没有这样的包Types...,其中Types&amp;...{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&amp;...实际上是参数本身的类型(而不是它的模板实参),所以(temp.deduct.call):

如果P是引用类型,则P引用的类型用于类型推导。

我们推断Types... 以匹配参数。这成功了,因为所有参数都是左值,我们只需选择{int, float, const int}

【讨论】:

  • 谢谢!您在 OP 下的上述评论中确认了我的重新解释。所以基本上模板parameter/argument对推导必须完全匹配,而函数调用parameter/argument对在匹配过程中会经历一些转换和异常。对吗?
  • @Rich 只有参数/参数对匹配。只是在您的一个示例中,您进行匹配的方式是匹配模板参数。