模板参数
类似函数参数的名字,一个模板参数的名字也没有什么内在含义,通常将类型参数命名为T,但是实际上可以是任何名字。
模板参数与作用域:
模板参数遵循普通的作用域规则。一个模板参数的可用范围是在其声明后,至模板声明或定义结束之前。与其他名字一样,模板参数会隐藏外层作用域中声明的相同的名字。但是不同的是,在模板内不能重用模板参数名:
typedef double A;
template <typename A,typename B> void f(A a,B b){
A tmp = a; //隐藏外部的A,此处tmp不是double类型
double B; //错误
}
正常的名字隐藏规则使得外部的A的声明被隐藏,因此tmp不是一个double类型变量,其类型在使用f函数时才会确定,另外由于不能重用模板参数名,所以将变量名取为B是错误的。
因为参数名不能重用,因此一个模板参数名在一个特定的函数模板中也只能出现一次:
//错误,模板参数名重用
template <typename V,typename V> //...
使用类的类型成员:
通常可以通过::来访问static成员或者类型成员。在普通的代码中,编译器知道类的定义,因此它可以知道通过作用域运算符访问到的名字是类型还是static成员。但是对于模板代码就有些困难。例如:
//假定T是一个模板的类型参数
T::size_type * p;
从上面的代码无法知道是在定义一个名为p的变量,还是一个名为size_type的static成员变量与名为p的变量相乘。
默认情况下,c++假定通过作用域运算符访问的是名字而不是类型。因此当希望使用一个模板类型参数的类型成员时,需要显式的指定,通过关键typename来做到:
template <typename T>
typename T::value_type top(const T& c){
if(!c.empty())
return c.back();
else
return typename T::value_type();
}
使用了typename指明返回值是一个类型,并在c中没有元素的时候生成一个值初始化的元素返回给调用者。(看不懂就把T::value_type换成int看看)
当希望通知编译器一个名字表示类型时,必须使用关键字typename而不能使用class。
默认模板实参:
新标准中,可以为函数和类模板提供默认实参,更早的只能为类模板提供默认实参。
例如,使用标准库中的less函数对象模板编写compare函数:
template <typename T typename F = less<T>>
int compare(const T& t1,const T& t2,F f = F()){
if(f(t1,t2)) return -1;
if(f(t2,t1)) return 1;
return 0;
}
当实例化时的类型是less支持的类型时,就不用自己提供比较操作:
bool i = compare(0,42); //默认使用less
当调用函数传入的实参是自定义类型,第三个参数也可自己传值。
模板默认实参与类模板:
template <typename T = int> class Number{
public:
Number(T v = 0) : val(v){}
private:
T val;
};
//实例化
Number<double> d_num; //不使用默认类型
Number<> i_num; //使用默认类型
成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板。成员模板不能是虚函数。
普通类的成员模板:
作为例子,这里定义一个类,类似删除器的作用,类中包含一个重载的函数调用运算符,它接受一个指针并对此指针执行delete操作,这里将调用运算符定义为一个模板:
class DebugDelete{
public:
//构造函数,用于初始化输出流
DebugDelete(std::ostream &s = std::cerr):os(s){}
//T的类型由编译器推断
template <typename T> void operator()(T* p) const{
os<<"deleteing p"<<endl;
delete p;
}
private:
std::ostream &os;
};
//代替delete操作
double* p = new double;
DebugDelete d;
d(p); //释放p
与其他模板相同,成员模板也是以模板参数列表开始的,每个DebugDelete对象都有一个ostream成员,用于写入数据;还包含一个自身是模板的成员函数,这个类可以代替delete操作符。
类模板的成员模板:
对类模板也可以为其定义成员模板,在此情况下,类和成员各自有各自的、独立的模板参数。
例如为模板类Blob定义一个构造函数,接受两个迭代器参数,表示要拷贝的元素的范围。由于希望支持不同类型序列的迭代器,因此将构造函数定义为模板:
template <typename T> class Blob{
template <typename It> Blob(It a,It b);
};
构造函数有自己的模板类型参数It,与类模板的普通成员函数不同的是成员模板是函数模板,当在外部定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表:
template <typename T> //类的模板类型参数
template <typename It> //构造函数的类模板类型参数
Blob<T>::Blob(It a,It b){...}
实例化和成员模板:
为了实例化一个类模板的成员模板,须同时提供类和函数模板的实参。和往常一样,在哪个对象上调用成员模板,编译器就根据该对象的类型推断类模板参数的实参。与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参。
vector<long> v = {0,1,2,3,4};
//实例化对象和构造函数
Blob<int> s(v.begin(),v.end());
定义s时,显式的指出了编译器应该实例化出一个int版本的Blob,构造函数自己的类型参数则通过传递的参数来推断,这里是vector<long>::iterator,因此会实例化为以下版本:
Blob<int>::Blob(<vector<long>::iterator>,<vector<long>::iterator>);