为什么这个函数模板调用有效

Why does this function template call work?

本文关键字:调用 有效 函数模板 为什么      更新时间:2023-10-16

编译以下代码:

template<int...>
struct Indices {};
template<int J, int ...I>
void foo(Indices<I...>) {}
int main(int argc, char **argv)
{
  foo<2>(Indices<3,4,5>()); //why does this work?
  return 0;
}

在函数调用中,在我看来,J参数变为2...I参数变成3,4,5

但为什么这样做呢?我只在foo<2>指定了2,这意味着我将J指定为2,而将...I指定为无。为什么我仍然可以通过Indices参数指定...I?这里使用的是什么模板机制?

更新:当前的答案并不能解释为什么我可以有一个未推导(明确指定)的参数,但其他参数都推导出来。这到底什么时候起作用?我希望我没有依赖于不明确的行为。这个标准允许我做上面的事情吗?

参数unpack ...I由编译器从函数参数中推导出。它被称为模板参数推导。

以下是一些简单但有用的例子:

template<typename T> 
void f(T const&) {}
f(10);   //T is deduced as int
f(10.0); //T is deduced as double
f("10"); //T is deduced as char[3]

标准库中的许多函数都是函数模板,通常会推导出模板参数。这里有一个例子:

std::vector<int> vi;
std::vector<std::string> vs;
//...
std::sort(vi.begin(), vi.end()); //template argument deduction
std::sort(vs.begin(), vs.end()); //template argument deduction

这里std::sort是一个函数模板,但正如您所看到的,我们没有显式传递模板参数。这是因为模板参数是由编译器自己从函数参数中推导出来的。

希望能有所帮助。

如果在编译时可以推导出其他参数,则只允许指定函数调用的部分参数(第一个)。示例:

template<typename Ret, typename Arg>
Ret cast(Arg x){
    return x;
}
cast<double>(5);

实际上,您甚至可以编译以下代码:

template<int...>
struct Indices {};
template<int J, int ...I>
void foo(Indices<I...>) {}
int main(int argc, char **argv)
{
  foo<2,3>(Indices<3,4,5>()); //ok 2,3,4,5 starts with 2,3
  return 0;
}

但不是这个:

template<int...>
struct Indices {};
template<int J, int ...I>
void foo(Indices<I...>) {}
int main(int argc, char **argv)
{
  foo<2,1>(Indices<3,4,5>()); //no way to make x,3,4,5 start with 2,1
  return 0;
}

请参阅§14.1.8 C++11标准第3部分(N3242草案)。

可以推导(14.8.2)或获得的尾随模板参数from默认模板参数可以从显式模板参数。尾部模板参数包(14.5.3)将被推导为模板参数。如果可以推导出所有的模板自变量,它们可以全部省略;在这种情况下,空模板参数列表<>也可以省略其本身。在演绎是完成和失败,或者在没有进行推理的情况下,如果模板参数列表是指定的,它以及任何默认值模板参数,标识单个函数模板specialization,则模板id是函数的左值模板专业化。

要添加到nawaz答案:必须提供无法推导的模板参数,并且提供的模板参数必须按照定义的顺序。这意味着,如果模板参数可能需要提供,最好将其放在模板参数列表的第一位。例如

template<typename A, typename B> A foo(B);
template<typename B, typename A> A bar(B);
auto x = foo<int>(0.0);       // A=int, B=double;
auto y = foo<int,double>(0);  // A=int, B=double, argument implicitly cast to double
auto z = bar<int>(0);         // error: cannot deduce A
auto w = bar<int,double>(0);  // A=double, B=int;

在这两种情况下,B都可以(从函数参数类型)推导出来,但A不能。因此foo更方便,因为只需要提供一个模板参数。对于bar,第一个模板参数是可推导的,但不能推导第二个。因此,两者都必须提供。(只是为了澄清,将auto更改为doubleint与手头的问题没有区别。)

相关文章: