以可变 class 模板作为函数调用参数的函数模板参数推导

Function template argument deduction with variadic class template as function call parameter

所有示例均来自here and here.

具体来说,

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。是以下两条规则的方向应用:

If a parameter pack appears as the last P, then the type P is matched against the type A of each remaining argument of the call. Each match deduces the template arguments for the next position in the pack expansion.

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>

If P has one of the forms that include a template parameter list <T> or <I>, then each element Pi of that template argument list is matched against the corresponding template argument Ai of its A. If the last Pi is a pack expansion, then its pattern is compared against each remaining argument in the template argument list of A. A trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack.

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


在你的第一个例子中,

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

无法调用 g(Tuple<T1, Types&...>)。模板推导过程是关于选择一个推导的参数类型,该类型与被调用的参数类型相同。有一些例外(对于引用的 cv 限定、指针、派生 类、数组、函数),但其中 none 适用于此处。我们只需要选择 T1Types...,这样 Tuple<T1, Types&...>Tuple<int, float> 的类型相同。这是不可能的,因为没有 Types&...{float} 的包 Types...,因为 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):

If P is a reference type, the type referred to by P is used for type deduction.

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