16.1.5. 非类型模板形参
模板形参不必都是类型。本节将介绍函数模板使用的非类型形参。在介绍了类模板实现的更多内容之后,第 16.4.2 节将介绍类模板的非类型形参。在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。例如,下面的函数模板声明了 array_init 是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用(第 7.2.4节):
//initialize elements of an array to zero
template<class T, size_t N> void array_init(T (&parm)[N])
{
for(size_t i = 0; i != N; ++i) {
parm[i]= 0;
}
}
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。
790
当调用 array_init 时,编译器从数组实参计算非类型形参的值:
intx[42];
doubley[10];
array_init(x);// instantiates array_init(int(&)[42]
array_init(y);// instantiates array_init(double(&)[10]
编译器将为 array_init 调用中用到的每种数组实例化一个 array_init
版本。对于上面的程序,编译器将实例化 array_init 的两个版本:第一个实例
的形参绑定到 int[42],另一个实例中的形参绑定到 double[10]。
类型等价性与非类型形参
对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。下面的
两个 array_init 调用引用的是相同的实例—— array_init<int,42>:
intx[42];
constint sz = 40;
inty[sz + 2];
array_init(x);// instantiates array_init(int(&)[42])
array_init(y);// equivalent instantiation
Exercises Section 16.1.5
Exercise
16.15:
编写可以确定数组长度的函数模板。
Exercise
16.16:
将第 7.2.4 节的 printValues 函数重新编写为可用于
打印不同长度数组内容的函数模板。
16.1.6. 编写泛型程序
编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型
做一些假设。例如,虽然 compare 函数从技术上说任意类型都是有效的,但实
际上,实例化的版本可能是非法的。
791
产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作。
compare函数有三条语句:
if(v1 < v2) return -1; // < on two objects of type T
if(v2 < v1) return 1; // < on two objects of type T
return0; // return int; not dependent on T
前两条语句包含隐式依赖于形参类型的代码,if 测试对形参使用 < 操作
符,直到编译器看见 compare 调用并且 T 绑定到一个实际类型时,才知道形参
的类型,使用哪个 < 操作符完全取决于实参类型。
如果用不支持 < 操作符的对象调用 compare,则该调用将是无效的:
Sales_itemitem1, item2;
//error: no < on Sales_item
cout<< compare(item1, item2) << endl;
程序会出错。Sales_item 类型没有定义 < 操作符,所以该程序不能编译。在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是,保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用哪些操作的环境中那些操作运行正常。
编写独立于类型的代码
编写良好泛型代码的技巧超出了本书的范围,但是,有个一般原则值得注意。
编写模板代码时,对实参类型的要求尽可能少是很有益
的。
虽然简单,但它说明了编写泛型代码的两个重要原则:
• 模板的形参是 const 引用。
792
• 函数体中的测试只用 < 比较。
通过将形参设为 const 引用,就可以允许使用不允许复制的类型。大多数类型(包括内置类型和我们已使用过的除 IO 类型之外的所有标准库的类型)都允许复制。但是,也有不允许复制的类类型。将形参设为const 引用,保证这种类型可以用于 compare 函数,而且,如果有比较大的对象调用 compare,则这个设计还可以使函数运行得更快。一些读者可能认为使用 < 和 > 操作符两者进行比较会更加自然:
//expected comparison
if(v1 < v2) return -1;
if(v1 > v2) return 1;
return0;
但是,将代码编写为
//expected comparison
if(v1 < v2) return -1;
if(v2 < v1) return 1; // equivalent to v1 > v2
return0;
可以减少对可用于 compare 函数的类型的要求,这些类型必须支持 <,但不必
支持 >。