1 第八章 深入模板基础
1.1 参数化声明
1.1.1 联合模板
联合模板也是允许的,比如:
template<typename T>
union AllocChunk{
T
object;
unsigned
char bytes[sizeof(T)];
};
1.1.2 函数模板的缺省调用实参
template< typename T>
void fill(Array<T>*, T const& = T());
该例子说明,缺省调用实参可以依赖于模板参数。
在调用fill时,如果提供了第二个函数调用参数的话,就不会实例化这个缺省实参。这同时也说明了:即使不能基于特定类型T来实例化缺省调用实参,也可能不会出现错误。比如:
class Value{
public:
Value(int){};
};
int _tmain(int
argc, _TCHAR*
argv[])
{
std::list<Value>* pList = new std::list<Value>();
Value
aValue(1);
//fill(pList); //错误
fill(pList, aValue); //正确
return
0;
}
除了类模板和函数模板之外,我们还应当注意:
(1) 类模板的成员函数的定义;
(2) 类模板的嵌套类的成员的定义;
(3) 类模板的静态数据成员的定义;
1.1.3 虚成员函数
成员函数模板不能被声明为虚函数。
注意,类模板的成员函数模板,而不是类模板的成员函数。
template<typename T1>
class Dynamic{
public:
virtual
~Dynamic();//OK,每个Dynamic都有一个析构函数
template<typename T2>
virtual
void copy(T2 const&);//ERROR,在确定Dynamic<T>实例的时候不知道copy()的个数
};
1.1.4 模板的链接
类模板不能和另一个实体共享一个名称。例如:
int C;
template<typename T>
class C;//错误,名称冲突
书上是这么说的,但是经过我在vs2005下验证,以下代码是不会报错的:
int C;
template<typename T>
class C
{
public:
C(){};
void
Handle(){
std::cout<<"has
error?"<<std::endl;
};
};
但是如果我试图对类C做如下实例化,则会报错:type 'int' unexpected
int _tmain(int
argc, _TCHAR*
argv[])
{
C<int> aC;
aC.Handle();
return
0;
}
我怀疑就是由于在实例化的时候,他把C当作那个变量了。
1.1.5 基本模板
如果模板声明的是一个普通声明,一就是模板名称后面没有一对尖括号,我们就称其为基本模板。
函数模板必须是基本模板,类模板的特化则不是基本模板。
1.2 模板参数
分为三类:
l 类型参数;
l 非类型参数;
l 模板的模板参数;
位于后面的模板参数声明可以引用前面的模板参数名称。
template<typename T, T ROOT, template<T> class Buf>
class Structure;
此处,第二个和第三个参数都引用了第一个参数。
第二个参数是非类型模板参数;
第三个参数是一个模板的模板参数,这个Buf是一个带有非类型模板参数的模板。
其实上面的这样的定义在vs2005中是有问题的,比如:
template<int NUM>
class Structure1{
};
template<typename T, T ROOT, template<T> class Buf>
class Structure{
public:
Structure(){
int
i = ROOT;
};
private:
T
firstMem;
Buf<ROOT> thirdMem;
};
int _tmain(int
argc, _TCHAR*
argv[])
{
Structure<int, 10, Structure1>
aStr;
return
0;
}
上面的这么一段代码,在vs2005中编译,则会报如下错误:
error C3201: the template parameter list for class template 'Structure1' does not match the template parameter list for template parameter 'Buf'
1.2.1 类型参数
书上说一下的形式会出错:
template<typename T>
class List{
class
T* alloctor;
friend
class T;
};
但是我在vs2005中,其实编译上面的代码并不会出错,但是编译下面这段代码则会报错:
template<typename T>
class List{
friend
class T;
private:
void
Handle(){
std::cout<<"has
error"<<std::endl;
};
};
class C
{
public:
C(){
};
public:
void
SetList(List<C>* p){
p->Handle();
};
};
int _tmain(int
argc, _TCHAR*
argv[])
{
List<C> aList;
C
aC;
aC.SetList(&aList);
return
0;
}
错误信息为:List<T>::Handle' : cannot access private member declared in class 'List<T>'
这说明vs2005内还是没有实现这种类型的友元。
1.2.2 非类型参数
非类型参数的表示是:在编译期或者链接期可以确定的常值。
非类型模板参数的声明和变量的声明很相似,但是不允许有static,mutable等修饰符。
非类型模板参数必须是以下的一种:
l 整形或者枚举类型
l 指针类型(可以是普通对象的指针类型、函数指针类型、指向成员的指针类型);
l 引用类型(指向对象或者指向函数的引用)。
1.2.3 模板的模板参数
模板的模板参数是代表类模板的占位符。
模板的模板参数可以具有缺省模板实参。
对于模板的模板参数而言,他的参数名称只能被其自身其他参数的声明使用,所以下面的声明是错误的。
template< template<typename T> class List>
class Node{
T* storage;
};
模板的模板参数的参数的名称(如上面的T)并不会在后面用到,因此,该参数也经常可以被省略不写,即没有命名。
1.2.4 缺省模板实参
只有类模板才有缺省模板实参。
只有在后面的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参。后面的缺省值通常是在同一个模板参数中提供的,但是也可以在前面的模板声明中提供。例如:
typename< typename T1, typename T2, typename T3, typename T4=char>
class Quintuple;
typename< typename T1, typename T2, typename T3=int, typename T4>
class Quintuple;//正确
typename< typename T1=std::string, typename T2, typename T3, typename T4>
class Quintuple;//错误
另外,缺省实参不能重复声明,所以下面的第二个声明是错误的,但是根据我在vs2005上的实验,只会发出警告,可能是警告级别设置的问题。
template<typename T=void>
class Value;
template<typename T=void>
class Value;
1.3
模板实参
可以有几种方式来确定模板实参。
l 显示模板实参;
l 注入式类名称,对于具有模板实参P1, P2…的类模板X,在他的作用域内,模板名称X等同于其template-id,即:X<P1, P2,…>。
l 缺省模板实参。这个只对类类模板有效,然而即使所有的模板参数缺省实参,一对尖括号也是不能省的。
l 实参演绎,这个只对函数模板有效,如果能够演绎,则后面的尖括号可不要。
1.3.1 函数模板实参
函数模板实参一般都可以通过演绎得到。
但是有一些模板实参永远得不到演绎的机会,于是,我们最好把这些实参所对应的参数模板参数列表的开始,从而可以显示指定这些参数,从而其他的参数仍然可以得到演绎。
template<typename destT, typename srcT>
inline destT implicit_cast(srcT const& x)
{
return
(destT)x;
}
int _tmain(int
argc, _TCHAR*
argv[])
{
int
i = implicit_cast<int>(1.0);
return
0;
}
我怀疑c++中的几个转型函数都是这么定义的。
到这里,我们要来看一个书中给的叫简单的元编程的例子:
typedef char RT1;
typedef struct{ char a[2]; } RT2;
template<typename T> RT1 test(typename T::X const*);
template<typename T> RT2 test(...);
#define type_has_member_type_X(T) (sizeof(test<T>(0))
== 1)
int _tmain(int
argc, _TCHAR*
argv[])
{
if(type_has_member_type_X(int))
std::cout<<"int has
member type X"<<std::endl;
else
std::cout<<"int
does not have member type X"<<std::endl;
return
0;
}
这个type_has_member_type_X 宏就可以用来确定指定的类型是否有X子类型。
这里正事SFINAE,即“替换失败并非错误(substitution failure is
not an error)”原则。
SFINAE原则允许试图创建无效的类型,但是并不允许试图计算无效的表达式。