1 第十一章 模板实参演绎
1.1 演绎的过程
每个实参-参数对的分析都是独立的;因此,如果最后得出的结果是矛盾的,那么演绎的过程将失败。
我们来看个例子:
template<typename T>
typename T::ElementT at(T const& a, int i)
{
return a[i];
}
void g1(int* p)
{
int x = at(p, 2);
}
void g2(int* p)
{
int x = at<int*>(p, 2);
}
在上面的例子中,g1和g2都会报错,但是错误的信息会有所不同。
g1报错是因为演绎失败,g2报错是因为指定的类型是个无效类型。S
下面给出实参-参数匹配的规则:匹配类型A(来自实参的类型)和参数化类型P(来自参数的声明)。如果被声明的参数是一个引用声明(即T&),那么P就是所引用的类型(即T),而A仍然是实参的类型。否则的话,P就是所声明的参数类型,而A则是实参的类型;如果这个实参的类型是数组或者函数类型,那么还会发生decay转型,转换为对应的指针类型,同时还会ihulue高层次的const和volatile限定符。
这里要记住的一点是:引用参数是不会进行decay的。
1.2 节接受的实参转型
在找不到匹配实参的时候,有一些转型是允许的;
l 从有const 或volatile限定符到没有;
l 从没有限定符到有const或volatile限定符;
l 当演绎的类型不涉及到转型运算符的时候,被替换的P类型可以是A类型的基类;或者当A是指针类型时,P可以是一个指针类型,它所指向的类型是A所指向的类型的基类。
这里要重点说明一下第三点。例如:
template<typename T>
class B{
public:
virtual void f(){
std::cout<<"B::f()"<<std::endl;
}
};
template<typename T>
class D: public B<T>{
public:
virtual void f(){
std::cout<<"D::f()"<<std::endl;
}
};
template<typename T>
void f(B<T>* b)
{
b->f();
}
template<typename T>
void f(B<T> b)
{
b.f();
}
int _tmain(int argc, _TCHAR* argv[])
{
D<int> d;
f(&d);//1, void f(B<T>* b)
f(d); //2, void f(B<T> b)
return 0;
}
在上面的例子中,2值得我们关注,这种传值的方式也可以实现子类到基类的匹配还是想出不来的,不过这里经过验证是如此,我们只需记住就好。
如果对f再加上下面这个重载。
template<typename T>
void f(D<T> d)
{
d.f();
}
那么,在上面的2处调用的就是这个版本,而不是void f(B<T> b)版。
原来我以为上面的规则是模板适用,经过实验发现,其实是通用的:
class BB{};
class DD:public BB{};
void f(BB b)
{
std::cout<<"void f(BB b)"<<std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
DD dd;
f(dd);//调用的是void f(BB b)
return 0;
}
但是如果将上面的代码改为:
class BB{};
class DD:public BB{};
template<typename T>
void f(T a)
{
std::cout<<"void f(T a)"<<std::endl;
}
void f(BB b)
{
std::cout<<"void f(BB b)"<<std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
DD dd;
f(dd);//调用的是void f(T a)
return 0;
}
此处调用的就是模板,这说明,只有在是在找不到匹配的类型时,c++编译器才会去转型以适应参数调用。
1.3 缺省实参调用
对于缺省调用实参而言,及时不是依赖型的,也不能用于演绎模板实参。来看例子:
template<typename T>
void h(T x = 42)
{}
int _tmain(int argc, _TCHAR* argv[])
{
h<int>();//ok
h(); //error,42不能用来演绎T
return 0;
}