reference:《 thinking in C++》
模板的参数
(1) 类型
(2) 编译时常量
(3) 类模板
以上几个参数中,前两个参数已经比较熟悉了,关于第三个参数,会在之后给出解释。
无类型模板参数
编译时常量,如:
template<class T, size_t N >
默认模板参数
特别注意:只有类模板才支持默认参数,函数模板不支持!但是函数模板可以把类模板作为参数。
(1) template<class T, size_t N = 100>
(2) template<class T = int, size_t N = 100> // 都使用默认参数时,设类为A,调用方式为A<> a;
(3) template<class T, class Allocator = allocator<T> >
(4) template<class T> T sum(T* a, T* b, T init = T())
类模板作为模板的参数
(1) template<class T, template<class> class seq>
(2) template<class T, template<class U> class seq>
(3) template<class T, size_t N, template<class, size_t> class Seq >
(4) template<class T,size_t = 10>
template<class T, template<class, size_t = 10> class Seq> // 重复声明默认参数
(5) template<class T, template<class U, class = allocator<U> > class seq>
typename
作用:
(1) 等价于声明模板时的class
(2) 可用于声明类型,详情见下
假如我们的模板类有一个嵌套类,如果我们不做特别说明,那么编译器会认为这是一个对象(变量),而不会认为它是一个类型,为了告诉编译器这是一个类型,我们可以加上typename关键词。比如模板类为T,嵌套类为A,则写作:
typename T::A variable;
以下两种情况不用写typename:
(1) 类型在基类中声明过
(2) 已经出现在同一作用域的初始化列表中
成员模板
(1) 函数作为成员模板 (不能声明为virtual!)
声明:
template<typename T> class complex {
public:
template<class X> complex(const complex<X>&)
实现:
template<typename T>
template<typename X>
complex<T>::complex(const complex<X>& c) { ... }
(2) 类作为成员模板
声明:
template<class A>
class Out {
public:
template<class B>
class In {
public:
void fun();
}
};
实现:
template<class A>
template<class B>
void Out<A>::In<B>::fun() { };
函数模板调用
template< typename T> const T& min (const T&a, const T&b) {
return ( a < b ) ? a : b;
}
(1) 带参调用
int ans = min <int> ( i , j );
(2) 类型推断
int ans = min ( i , j );
但要注意:
(1) 类型推断不支持标准转换,如果希望用到,需要指明类型。
(2) 模板参数作为返回值时,无法使用类型推断,需要指明类型。
函数模板重载
如果编译器能够区分,可以重载模板函数。
使用类型推断时,如果有非模板函数作为最优匹配,就会优先匹配非模板函数。用<>标记会强制匹配模板函数。
以函数模板作为参数
template <typename T>
void f(T*) { }
template < typename T>
void g ( void (*pf) (T*)) { }
调用方式:
(1) g<int>(&f<int>)
(2) g(&f<int>)
(3) g<int>(&f);
以上三种方式编译器都能够获取(推断)类型。
函数模板的半有序
一般而言,会优先匹配特化程度最高的模板:
所谓特化程度高,也就是说当一个调用可以匹配多个模板函数的时候,能匹配对象更少而又恰好能匹配当前参数的那个模板是最佳的,如下:
template<class T> void f(T); //(1)
template<class T> void f(T*);//(2)
template<class T> void f(const T*); //(3)
对于指向const的指针而言,(3)是最佳匹配,对于普通指针而言,(2)是最佳匹配,(1)是一个通用的匹配。
这时候存在这么一个问题,如果我们没有办法区分出哪个是特化程度最高的匹配,会发生什么呢?
事实上在这种不明确的情况下,编译器会报错。
模板特化
(1) 显式特化
template<class T>const T& min(const T& a, const T& b) {
return (a < b) > a : b;
}
template<>
const char* const& min<const char*> (const char* const& a,const char* const& b) {
return strcmp(a,b) < 0 ? a : b;
}
vector类包含了一个bool显式特化,将bool信息用二进制存储:
template<class T, class Allocator = allocator<T> >
class vector { ... };
template<>
class vector<bool,allocator<bool> > { ... } ;
(2) 半特化
也就是只特化一般分参数。
template<class T,class U> class C {
public :
void f() { cout << "全特化\n"; }
};
template<class U> class C<int, U> {
public:
void f() { cout << "特化T为int\n"; }
};
template<class T> class C<t,double> {
public:
void f() { cout << "特化U为double\n"; }
};
template<class T, class U> class C<T*,U> {
public:
void f() { cout << "特化T为指针类型\n"; }
};
template<class T,class U> class C<T, U*> {
public:
void f() { cout << "特化U为指针类型\n"; }
};
template<class T,class U> class C<T*, U*> {
public:
void f() { cout << "特化T,U为指针类型\n"; }
};
但要注意的是,很多半特化可以匹配重叠的参数,可能会引起二义性,导致编译器报错。
简化的模板编写方式
我们的模板为了匹配各种各样的类型,通常需要我们手动写很多特化合半特化模板来匹配所有的情况,最终编译器将会在原有代码基础上生成大量的代码,使得代码体积越来越大,这个时候,我们有必要使用void*指针来提供统一接口,并减小最终实际的代码体积。比如对于栈而言,我们提供以下几种:
template<class T> class Stack {...} // 这是主要的模板
template<> class Stack<void*> {...} // 一个void*的全特化
template<class T> class Stack<T*> {...} //一个其它指针类型的半特化
第一个主要模板匹配大多数参数,第二个void*匹配大部分指针,第三个半特化主要用于匹配Stack<void*>私有派生的指针类型。
限定关联、非限定关联、非关联
模板编译包含两个阶段。
第一阶段,解析模板定义以及一些不依赖于模板参数的名称;第二阶段,进行模板实例化。
void g() { cout << "global g()" << endl;}
template<class T> class Y {
public :
void g() { cout << "Y g()" << endl;
void h() { cout << "Y h()" << endl;
typedef int E;
}
typedef double E;
template<class T> void swap(T& t1,T& t2) {...}
template<class T> class X : public Y<T> {
E f() { // E:非关联,所以在第一阶段处理,类型为double
g(); // g() : 非关联,所以在第一阶段处理,调用的是全局声明
this->h(); // 限定关联,对象为X
T t1 = T(), t2 = T(1); // t1,t2 : 关联
cout << t1 << endl; // operator << (cout,t1) 关联
swap (t1,t2); // 非限定关联,参数是T,也就引起了swap(int&,int&)实例化
std::swap(t1,t2); // 限定非关联
return E(t2);
}
}
什么是限定与非限定?是否有指明类名前缀。
什么是关联?在这里指与模板空间匹配。
关联名称: 在实例化时才查找(第二阶段)
非限定的关联名称 : 普通查找,在定义时进行
非关联名称 : 模板定义解析时查找(第一阶段)
模板与友元
在模板函数中声明友元函数可以有两种写法:
(1) 友元函数也是模板:
friend void f<>(const Friendly<T>&);// 在这里<>代表这是一个模板函数
这时候如果非内联定义需要提前声明友元函数;因为模板本身没有提供,它不支持隐式转换。
此外,对于每个实例化的模板而言,它所对应的友元函数是唯一的,就是和它类型相同的那个函数。f<int>(const Friendly<T>&)对应Friendly<int>,如果在类中写一个特化的摸板友元函数,它将成为所有实例化的类的友元函数;同样,不依赖T的非模板友元函数也是所有实例化类的友元函数。
如果希望f的所有特化都成为Friendly特化的友元函数,可以加一个新的模板参数的特化:
template<class U> friend void f<>(const Friendly<U>&);
(2) 友元函数是含T的普通函数
friend void f(const Friendly<T>&);//这时不含有<>
每实例化一个类模板,就会生成一个Friendly参数的f() 的重载函数,因为不是模板,所以支持隐式转换。