目录
第11章 模板实参演绎
在每个函数模板的调用中, 如果都显式地指定模板实参(例如,concat<std::string, int>(s,3) ) , 那么很快就会导致很繁琐的代码。 幸运的是, 借助于功能强大的模板实参演绎过程, C++编译器通常都可以自动地确定这些所需要的模板实参(即隐式实例化)。
11.1 演绎的过程
针对一个函数调用, 演绎过程会比较“调用实参的类型”和“函数模板对应的参数化类型(即T) ”, 然后针对要被演绎的一个或多个参数,分别推导出正确的替换。 我们应该记住: 每个实参-参数对的分析都是独立的; 因此, 如果最后所得出的结论发生矛盾, 那么演绎过程将失败。考虑下面的例子:
template<typename T>
T const& max(T const& a, T const& b)
{
return a < b ? b : a;
}
int g = max(1, 1.0);
即使所有被演绎的模板参数都可以一致性地确定(即不发生矛盾) , 演绎过程也可能会失败。 这种情况就是: 在函数声明中, 进行替换的模板实参可能会导致无效的构造。 请看下面的例子:
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中, 用int*来替换T之后, 显然会导致一个无效的C++构造, 从而也使这个演绎过程失败。
我们接下来需要描述实参-参数对是如何进行匹配的。 我们会使用下面的概念来进行描述: 匹配类型A(来自实参的类型) 和参数化类型P(来自参数的声明) 。 如果被声明的参数是一个引用声明(即T&) ,那么P就是所引用的类型(即T) , 而A仍然是实参的类型。 否则的话,P就是所声明的参数类型, 而A则是实参的类型; 如果这个实参的类型是数组或者函数类型, 那么还会发生decay转型, 转化为对应的指针类型, 同时还会忽略高层次的const和volatile限定符。 例如:
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); //非引用参数(针对f): T是double*
g(x); //引用参数(针对g): T是double[20]
f(seven); //非引用参数: T是int.
g(seven); //引用参数: T是int const
f(7); //非引用参数: T是int
g(7); //引用参数: T是int =>错误: 不能把7传递给int&