2.3 模板参数
函数模板有两种类型的参数。
1.模板参数:位于函数模板名称的前面,在一对尖括号内部进行声明:
template <typename T> //T是模板参数
2.调用参数:位于函数模板名称之后,在一对圆括号内部进行声明:
…max (T const& a, T const& b) //a和b都是调用参数
你可以根据需要声明任意数量的模板参数。然而,在函数模板内部(这一点和类模板有区别),不能指定缺省的模板实参[4]。例如,你可以定义一个“两个调用参数的类型可以不同的”max()模板:
template <typename T1, typename T2>
inline T1 max (T1 const& a, T2 const& b)
{
return a < b ? b: a;
}
…
max(4,4.2) //OK, 但第1个模板实参的类型定义了返回类型
这看起来是一种能够给max()模板传递两个不同类型调用参数的好方法,但在这个例子中,这种方法是有缺点的。主要问题是:必须声明返回类型。对于返回类型,如果你使用的是其中的一个参数类型,那么另一个参数的实参就可能要转型为返回类型,而不会在意调用者的意图。C++并没有提供一种“指定并且选择一个‘最强大类型’”的途径(然而,你可以使用一些tricky模板编程来提供这个特性,详见15.2.4小节)。于是,取决于调用实参的顺序,42和66.66的最大值可以是浮点数66.66,也可以是整数66。另一个缺点是:把第2个参数转型为返回类型的过程将会创建一个新的局部临时对象,这导致了你不能通过引用[]来返回结果。因此,在我们的例子里,返回类型必须是T1,而不能是T1 const&。
因为调用参数的类型构造自模板参数,所以模板参数和调用参数通常是相关的。我们把这个概念称为:函数模板的实参演绎。它让你可以像调用普通函数那样调用函数模板。
然而,如前所述,针对某些特定的类型,你还可以显式地实例化该模板:
template <typename T>
inline T const& max (T const& a, T const& b);
…
max<double>(4,4.2) //用double来实例化T
当模板参数和调用参数没有发生关联,或者不能由调用参数来决定模板参数的时候,你在调用时就必须显式指定模板实参。例如,你可以引入第3个模板实参类型,来定义函数模板的返回类型:
template <typename T1, typename T2, typename RT>
inline RT max (T1 const& a, T2 const& b);
然而,模板实参演绎并不适合返回类型[6],因为RT不会出现在函数调用参数的类型里面。因此,函数调用并不能演绎出RT。于是,你必须显式地指定模板实参列表。例如:
template <typename T1, typename T2, typename RT>
inline RT max (T1 const& a, T2 const& b);
…
max<int, double, double>(4,4.2) //OK, 但是很麻烦
到目前为止,我们只是考察了显式指定所有函数模板实参的例子,和不显式指定函数任何模板实参的例子。另一种情况是只显式指定第一个实参,而让演绎过程推导出其余的实参。通常而言,你必须指定“最后一个不能被隐式演绎的模板实参之前的”所有实参类型。因此,在上面的例子里,如果你改变模板参数的声明顺序,那么调用者就只需要指定返回类型:
template <typename RT, typename T1, typename T2>
inline RT max (T1 const& a, T2 const& b);
…
max<double>(4,4.2) //OK: 返回类型是double
在这个例子里,调用max< double >时显式地把RT指定为double,但其他两个参数T1和T2可以根据调用实参分别演绎为int和double。
可以看出,所有这些修改后的max()版本都不能得到很大的改进。由于在单(模板)参数版本里,如果传递进来的是两个不同类型的实参,你已经可以指定参数的类型(和返回类型)。因此,尽量保持简洁并且使用单参数版本的max()就是一个不错的主意(在接下来的几节里,当讨论其他模板话题的时候,我们将使用这种方法)。