1.使用说明符修饰模板的时候,需要将说明符放在模板形参之后,返回值之前。例如:
template<class T> inline T min(const T&, const T&); // ok
inline template<class T> T min(const T&, const T&); // error
2.模板的声明必须指出函数或类是一个模板,同一模板的声明和定义中,模板形参的名字不必相同。例如:
template<class T> T calc(const T&, const T&);
template<class U> U calc(const U&, const U&);
template<class Type> Type calc(const Type& a, const Type& b) {
// ...
}
3.不能省略关键字或类型说明符。例如:
template<typename T, U> T calc(const T& m, const U& n) { // error
// ...
}
5.在形如
template<class P, class U>
P fcn(P* array, U val) {
P::size_type * p; // strange
// ...
}
这段代码中的P::size_type * p可能会引发一些误会,如果size_type是一个类型,那么这将是一条声明;如果size_type是一个值那么这将是一个乘法表达式。在默认情况下编译器假定这样的名字指定数据成员,而不是类型。如果希望编译器将其作为类型,则必须使用typename显示的告诉编译器:
template<class P, class U>
P fcn(P* array, U val) {
typename P::size_type * p;
// ...
}
6.模板形参不必都是类型,同样可以是非类型形参。例如:
template<typename T, size_t N> size_t array_length(const T (&a)[N]) {
return N; //返回数组长度
}
7.模板的实参转换是很有限的,接受const引用或指针的函数可以分别用const或者非const的对象的引用或指针;如果模板参数不是引用类型,则对数组或函数类型的实参应用常规指针转换。除了这两种情况以外其他的兼容类型都视为不同类型,例如:
template<class T> bool comp(const T& a, const T& b) { return a > b; }
int a = 1;
short b = 1;
comp(a, b); // error
要解决这类问题可以显示的使用强制转换,或者为模板提供两个不同的形参。例如:
template<class T> bool comp(const T& a, const T& b) { return a > b; }
int a = 1;
short b = 1;
comp(a, static_case<int>(b));
template<class T> bool comp(const T& a, const T& b) { return a > b; }
comp(a, b);
8.为模板显示指定类型形参。最典型的例子就是指定返回值。
template<class T1, class T2, class T3>
T1 cmp(const T2& a, const T3& b) { return a > b; }
cmp<long>(a, b);
在显示指定类型参数的时候,括号内的顺序是严格匹配的,也就是说,long与T1匹配,如果还有其他的参数则一次与T2、T3……匹配。9.模板的编译分为包含编译模型和分别编译模型。在包含编译模型中我们可以使用include将实现模板的文件在最后包含进来就好了。例如:
// 头文件
#ifndef _U_H_
#define _U_H_
template<class T> int comp(const T&, const T&); // 声明
#include "U.cc"
#endif // _U_H_
// 实现文件
template<class T> int comp(const T& a, const T& b) {
if (a < b) {
return -1;
}
if (b < a) {
return 1;
}
return 0;
}
对于分别编译模型介于我没有实现,还是自己下去看primer吧。
10.在类模板的作用域内部,可以用它的非限定名字引用该类。例如:
template<typename T> class Queue {
public :
// ...
Queue(const Queue& q): head(0), tail(0) { // ... }
// ...
};
11.非类型形参的模板实参。
template<int hi, int wid>
class Screen {
public :
Screen(): screen(hi * wid, '#'), cursor(0), height(hi), width(wid) {}
private:
std::string screen;
std::string::size_type cursor;
std::string::size_type height, width;
};
Screen<24, 80> hp2621; // screen 24lines by 80 characters
非类型模板实参必须是编译时常量表达式。虽然这所有的所有我都理解,但是我不理解,为什么要用模板,书上说当用户定义对象时必须为每个形参提供常量表达式以供使用,但是这样做有什么意义吗,难道构造函数做不到吗?
12.模板的友元。
i)普通的函数与类可以是模板的友元:
template<class T> class Bar {
friend class FooBar;
friend void fcn();
// ...
};
ii)一般模板友元关系:
template<class T> class Bar {
template<class U> friend class Foo;
template<class U> friend void tem_fcn();
// ...
};
这种友元关系会形成一对多的关系,也就是说每一种Bar都会有不同类型的Foo和tem_fcn可以任意的访问Bar。
iii)特定的模板友元关系:
template<class T> class Foo2;
template<class T> void temp_func();
template<class T> class Bar {
friend class Foo2<T>;
friend void temp_func<char*>();
//...
};
这样就会形成与之前一对多不同的一对一了。当授予给定模板访问权限的时候,在作用域中不需要存在这个类或模板,因为编译器会将友元声明也当作是的类或函数的声明。这样就引发了一个问题,如果你需要声明模板的友元你就必须提前声明这是一个模板,不然编译器会默认的将其作为普通的函数或类。例如:
template<class T> class A;
template<class T> class B {
friend class A<T>; // ok, 模板类
friend class C; // ok, 普通类
template<class U> friend class D; // ok, 模板类
friend class R<int>; // error
friend class F<T>; // error
};
13.成员模板。顾名思义,成员是一个模板,成员模板不能是虚的。当类模板在作用域外定义模板成员的时候,需要两个模板形参表,第一个是类模板的,第二个是自身的:
template<typename T>
class B {
template<typename Iter> void func( … ){ … }
};
template<typename T> template<typename Iter>
void fun( … ) { … }
14.模板的static类型的数据是属于每种类型的实例的,并不属于模板。例如:
template<class T> F {
public :
static int count() { return c; }
private:
static int c;
};
template<class T>
int F<T>::c = 0; // 定义并初始化
F<int> f1, f2;
int a = F<int>::count();
a = f1.count(); // ok,F<int>::count();
a = f2.count(); // ok,F<int>::count();
a = F::count() // error
15.虽然模板是泛型编程,但是模板并不是万能的,因此我们需要在特殊情况下对模板进行特殊处理。例如对C风格的字符串进行比较就不能直接使用模板:
template<>
bool comp<const char*>(const char* a, const char* b) {
//...
}
这就是模板的特化。我们可以特化一个类,一个函数,或者是一个类的成员。当我们在类特化外部定义成员时,成员之前不能加template<>标记,类只是在外部定义了一个成员函数;成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参开头。
16.特化与重载。函数模板可以重载,可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。在同时包含有模板函数与重载的普通函数的情况下,确定函数调用步骤如下:
1)为这个函数名建立候选函数集合,包括:
a)与被调用函数名字相同的任意普通函数
b)任意可以与函数实参匹配的函数模板
2)确定普通函数是否可行的,然后确定模板是否可行。
3)如果需要转换来进行调用,根据转换的种类排列可行函数。记住,调用模板函数实例所允许的转换是有限的。
a)如果只有一个函数可选,就调用这个函数。
b)如果调用有二义性,从可行函数集合中去掉所有函数模板实例。
4)重新排列去掉函数模板实例的可行函数
a)如果只有一个函数可选,就调用这个函数
b)否则调用具有二义性。
其实只是两个原则:普通函数优先原则和完全匹配原则。习题16.63是一个很好的检测题。
设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为可能会使函数的用户感到奇怪,定义函数模板特化几乎总是比使用非模板版本更好。