模板与泛型编程
--模板定义[续]
四、模板类型形参
类型形参由关键字class或 typename后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。
模板类型形参可作为类型说明符在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。具体而言,它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。
templateT calc(const T &a,const T &b) { T tmp = a; //... return tmp; }
1、typename与class的区别
在函数模板形参表中,关键字typename和 class具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:
templatecalc (const T&, const U&);
使用关键字typename代替关键字class指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚地指明后面的名字是一个类型名。但是,关键字typename是作为标准C++的组成部分加入到C++中的,因此旧的程序更有可能只用关键字class。
2、在模板定义内部指定类型
除了定义数据成员或成员函数外,类还可以定义类型成员。例如,标准库的容器类定义了不同的类型,如size_type,使我们能够以独立于机器的方式使用容器。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数:
templateParm fcn(Parm *array,U value) { Parm: :size_type *p; //如果Parm::size_type是一个类型,则该语句是一个声明 //如果Parm::size_type是一个对象,则该语句是一个乘法运算 }
默认情况下,编译器假定这样的名字指定数据成员,而不是类型。
如果希望编译器将size_type当作类型,则必须显式告诉编译器这样做:
templateParm fcn(Parm *array,U value) { typename Parm::size_type *p; //OK }
通过在成员名前面加上typename作为前缀,可以告诉编译器将成员当做类型。通过编写typenameparm::size_type,指出绑定到Parm的类型的size_type成员是类型的名字。当然,这一声明给用实例化fcn的类型增加了一个职责:那些类型必须具有名为size_type的成员,而且该成员是一个类型。
【最佳实践】
如果拿不准是否需要以typename指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定typename没有害处,因此,即使typename是不必要的,也没有关系。
//P533 习题16.12 templatetypename IterType::value_type mostApr(IterType first,IterType last) { map count; while (first != last) { ++ count[*first]; ++first; } int apper = 0; typename IterType::value_type maxAprItem; for (typename map ::iterator iter = count.begin(); iter != count.end(); ++iter) { if (iter -> second > apper) { maxAprItem = iter -> first; apper = iter -> second; } } return maxAprItem; } int main() { vector ivec; int val; ifstream inFile("input"); while (inFile >> val) { ivec.push_back(val); } cout << mostApr(ivec.begin(),ivec.end()) << endl; }
//习题16.13 templatevoid printCntanr(ContainerType contnr) { typename ContainerType::size_type n = 0; while (n != contnr.size()) { cout << contnr[n ++] << endl; } }
//习题16.14 templatevoid printCntanr(IterType first,IterType last) { while (first != last) { cout << *first << endl; ++first; } }
五、非类型模板形参
模板形参不必都是类型。
在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。
templatevoid array_init(T (&parm)[N]) { for (size_t i = 0; i != N; ++i) { parm[i] = 0; } }
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参指定数组的长度。
当调用array_init时,编译器从数组实参计算非类型形参的值:
int x[42];
double y[10];
array_init(x); //T被int替代,N被42替代
array_init(y); //T被double替代,N被10替代
编译器将为array_init调用中用到的每种数组实例化一个array_init版本。
类型等价性与非类型形参
对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。比如下面的两个array_init调用引用的是相同的实例 array_init
int x[42];
const int sz = 40;
int y[sz + 2];
//两个的函数实例相同
array_init(x);
array_init(y);
//P534 习题16.15/16 templatestd::size_t getArrSize(Type (&arr)[N]) { return N; } template void printArr(ArrType (&arr)[N]) { for (std::size_t i = 0; i != N; ++ i) { cout << arr[i] << endl; } } int main() { int X[] = {3,34,5,65,67,7,7}; cout << "array size is: " << getArrSize(X) << endl; cout << "array items:" << endl; printArr(X); }
六、编写泛型程序
编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型做一些假设。例如,虽然compare函数从技术上说任意类型都是有效的,但实际上,实例化的版本可能是非法的。
产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作:
if (v1 < v2)
return -1; // T类型的对象必须支持 <
if (v1 > v2)
return 1; // T类型的对象必须支持 >
如果用不支持<操作符的对象调用compare,则调用是无效的:
Sales_item item1, item2;
cout << compare(item1, item2) << endl; //Error:该程序不能编译
【小心地雷】
在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是:保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用那些操作的环境中那些操作运行正常!
编写独立于类型的代码
【最佳实践】
编写模板代码时,对实参类型的要求尽可能少是很有益的。
虽然简单,但它说明了编