如果在每一个function template调用语句中明确指定template argument,例如,
concat<std::string, int>(s,3), 程序代码会显得笨拙又难看。幸运的是C++编译器通常可以自动判定你所需要的template argument类型,这是透过要给名为template argument deduction模板自变量推导的强大机制完成的。
本文详细解说template argument deduction的细节。很多推导规则所产生的结果都符合人们的直观认知。
1. 推导过程Dedcution Process,
编译器会比对调用语句内某自变量的类型和function template内对应的参数化类型,并试图总结出被推导的一个或多个参数的正确替代物。每一对自变量-参数独立分析。如果推导结束时结果有异常,则推导失败。考虑下面例子:
template <typename T>
T const& max(T const& a, T const& b)
{
return a< b? b: a;
}
int g = max(1, 1.0);
在这里,第一个call argument类型为int,因此max()的template parameter T被暂时推导为int。然而第二个call argument的类型为double,因此T应该为double:这与第一个推导结果冲突。注意是推导失败,不是程序非法:推导程序还是有可能成功找到另一个名为max的template(因为function template可以重载,就像常规functions那样)。
即使所有的template parameters推导结果前后一致,但如果以自变量替换进去后导致函数声明的其余部分变为非法,推导过程还是有可能失败。例如:
template <typename T>
typename T::ElementT at(T const& a , int i)
{
return a[i];
}
void f(int* p)
{
int x= at(p,7);
}
在这里T被推断为int*(T只在一个参数类型中出现,因此显然不会发生冲突)。然而,将返回类型T ::ElementT中的T替换为int* 显然是非法的,因此推导失败。
我们探究自变量-参数匹配是如何进行的。今后的描述将出现符号A和P,意义如下:
将类型A(由argument type导出)匹配至一个参数化类型P(有template parameter的声明导出);
如果参数被声明为以by reference 方式传递,则P表示被指涉reference类型,A为自变量类型;
否则P表示声明之参数类型,A是个由array或function退化而成并去除外围之const 和volatile饰词的pointer类型。例如:
template <typename T> void f(T); //P 为T
template <typename T> void g(T&); //P 仍为T
double x[20];
int const seven = 7;
f(x); //nonreferec parameter, T 为 double*
g(x); // reference parameter, T 为double[20]
f(seven); //nonreference parameter: T为 int
g(seven); //reference parameter: T 为int const
f(7); //nonreference parameter: T 为int
g(7); //reference paramter: T int =>错误,不能把7当作int& 传递
对于调用语句f(x),x由array类型退化为double*类型,T被推导为该类型。调用语句f(seven)中,const饰词被卸除,从而T被推导为int。
调用语句g(x)中,T被推导为double【20】(并没有发生类型退化);
类似情况,g(seven)的自变量是一个int const类型的左值lvalue,此外,由于const和volatile饰词在匹配reference paramters 时不被卸除,因此T被推导为 int const。
然而g(7)中T被推导为int(因为nonclass rvalue表达式不能含有带const或volatile饰词的类型),因此调用失败,因此自变量7不能传给一个int&类型的参数。
当字符串字面常量string literal类型的自变量被绑定到reference parameter时,不会发生退化。重新考虑先前的max()template:
template <typename T>
T const& max(T const & a, T const& b);
很多人希望在max("Apple", "Pear");表达式中,T被推导为 const char*,然而,“Apple”的类型却是char const【6】,“Pear”的类型式char const【5】。array类型至pointer类型的退化过程并未发生(因为max的参数使用by reference传递方式),因此如果推导成功,T必须又是char【6】又是char【5】,那当然不可能。
2.推导之前后脉络Deduced Contexts
比T更复杂的参数化类型也可以被匹配到一个给定的自变量类型上。下面是一些基本例子:
template <typename T>
void f1(T*);
template <typename E, int N>
void f2(E (&) [N]);
template <typename T1, typename T2, typenam T3>
void f3(T1 (T2::*)(T3*)); //f3的参数是pointer-to-member type
//f3的参数是个T2成员函数,该函数的返回类型为T1,接受一个T3*参数
class S {
public:
void f(double *);
};
void g(int *** ppp)
{
bool b[42];
f1(ppp); // 推导结果:T为 int**
f2(b); // 推导结果:E 为 bool, N=42
f3(&S::f); // 推导结果:T1=void, T2=S, T3=double
}
3.特殊推导情境Special Decution Situations
4. 可接受的自变量转型Allowable Argument Convertion
5. 类模板参数Class Template Parameters
6.默认的调用自变量Default Call Arguments
7. 受限模板扩展技术restricted template expansion
8. 小结 Summary