template和class或者function的区别在于templates声明语句有一个参数化子句:
template <…parameters here…>
或者:
export template <…parameters here>
如下展示两种templates:一种在class之内,即member templates,另一种在class之外且namespace scope之内(global scope也被当成一种namespace scope):
template <typename T>
class List{//namespace scope class template
public:
template <typename T2>
List(List<T2> const&);//member function template
...
};
template <typename T>
template <typename T2>
List<T>::List(List<T2> const& b){
...
}
template <typename T>
int Length(List<T> const&);//namespace scope function template
class Collection{
template<typename T>
class Node{//member class template
...
};
template <typename T>
class Handle;//member class template,无定义
template <typename T>
T* alloc(){//member function template,隐寓为inline函数
...
}
...
};
template <typename T>
class Collection::Handle{//member class template
...
};
定义域class外的member templates可有多重template <<…>…>参数化子句,其中一个代表template本身,其余各个子句代表外围的每一层class template。这些子句必须从最外层的class templates开始写起。
function template可以有预设的调用自变量,和一般function一样:
template <typename T>
void report_top(Stack<T> const&,int number=10);
template <typename T>
void fill(Array<T>*,T const&=T());//若T为内建类型,T()为0或者false
当fill()被调用时,如果调用者提供了第二自变量值,预设自变量便不会被实例化,这可确保如果预设自变量无法别某个特定类型T实例化的时候,不会引发编译错误。举例如下:
class value{
public:
Value(int);
};
void init(Array<Value>* array){
Value zero(0);
fill(array,zero);//OK
fill(array);//ERROR:Value没有default构造函数,所以调用失败
}
除了两种template基本类型,另有三种声明也可以被参数化,三者均相当于class template的成员定义:
1)class templates的成员函数定义;
2)class templates的nested class members(嵌套类别成员)定义;
3)class templates的static成员变量定义。
虽然它们也可以被参数化,但是它们并不是第一级templates。它们的参数完全由它们所隶属的template决定。示例如下:
template <int I>
class CupBoard{
void open();
class Shelf;
static double total_weight;
...
}
template <int I>
void CupBoard<T>::open(){
...
}
template <int I>
class CupBoard<I>::Shelf{
...
};
template <int I>
double CupBoard<I>::total_weight=0.0;
member function templates不能被声明为virtual,这个限制的原因在于:虚拟函数调用机制使用一个大小固定的表格,其中每一笔条目记录一个虚拟函数入口,然而直到整个程序编译完成后才能知道有多少个member function templates需要被实例化,因此和虚拟函数调用机制冲突。
但是class template members却可以是virtual函数,因为class被实例化时候,member function的数量早就确定了,因此可以为虚拟函数。
template <typename T>
class Dynamic{
public:
//class template的member function,可以被声明为virtual
virtual ~Dynamic();
//member function template,不可以被声明为virtual
template <typename T2>
virtual void copy(T2 const&);
};
每个template在其作用域内必须有一个独一无二的名称,除非是被重载的function templates。需要特别注意的是class template不能喝其他不同种类的物体共享同一个名称,这点与一般的non-template class不同。
int C;
class C;//class名称和nonclass名称处在不同的空间内
int X;
template <typename T>
class X;//ERROR:名称与上述变量X冲突
struct S;
template <typename T>
class S;//ERROR:名称与上述struct S冲突
template通常使用外部链接,但不能使用C链接方式,惟一例外是static namespace scope function templates,函数内部不能再声明template,默认为:
extern "C++" tempalte <typename T>
void normal();
还有一种非标准形式的链接
extern "Xroma" template <typename T>
void Xroma_link();
tempalte <typename T>
void external();//直射另一个文件中同名且作用域相同的物体
tempalte <typename T>
static void internal();//与另一个文件中的同名template无关
主模板的声明语句在template名称之后并不添加由角括号括起来的template argument list:
template <typename T> class Box;//OK:primary template
template <typename T> class Box<T>;//error:non-primary template
template <typename T> void translate(T*);//ok:primary template
template <tyepname T> void translate<T>(T*);//error:non-primary template
一旦我们声明一个偏特化的template,就产生了一个non-primary template。
template parameters有三种类型:
1)Type parameters(类型参数):
2)Nontype parameters(非类型参数):
3)template template parameters(双重模板参数)。
template parameters是在template声明语句的参数化子句中生命的变量,template parameters不一定得具名:
template <typename,int>
class X;
但是当template程序代码中需要用到某个template parameter时,后者必须具名,注意:后面声明的template parameters可以用到前面声明的template parameters的名称:
template <typename T,T* Root,template <T*> class Buf>
class Structure;
下面我们各个击破。
类别参数由关键字typename或者class导入,两者完全等价,声明方式是:关键词typename或class后面跟一个简单的标识符,该符号后面可以跟一个逗号以便区隔下一参数,也可以使用一个右角括号结束子句,或者跟一个等号表示预设模板自变量。
template声明语句中type parameter的作用非常类似typedef的名称。例如,你不能使用class T这样的名称,及时T确实表示一个class。
template <typename Allocator>
class List{
class Allocator* allocator;//ERROR
friend class Allocator;//ERROR
...
};
非类型参数实质可以在编译期或者链接期就可以确定其值的常数。这种参数的类型必须是如下三者之一:
整数(int)或者enum类型;
pointers:指向常规objects、执行functions和指向members;
reference:指向objects和指向functions。
你可能惊喜的发现,nontype parameter前面也可以有typename,示例如下:
template <typename T,typename T::Allocator* Allocator>
class List;
Nontype parameters也可以是function类型或者array类型,但它们都会退化为对应的pointer类型:
template<int buf[5]>
class Lexer;
tempalte <int* buf>
class Lexer;
Nontype template parameters的声明防护四非常类似于变量声明,但你不能加上诸如static、mutable之类的修饰,但是却可以加上const或者volatile,但如果这些修饰词出现在参数类型的最外层,编译器会忽略它们:
template <int const length> //const被忽略
class Buffer;
template <int length>
class Buffer;//与上一行声明语句等价
最后,强调一下,non type parameters总是右值:不能被取址,也不能被赋值。
Template Template Parameters(双重模板参数)
Template Template Parameter是一种class template占位符号,其声明方式和class templates类似,只是不能够使用关键词struct和union。
template <template<typename X> class C>//OK
void f(C<int>* p);
template <template<typename X> struct C>//ERROR:不能使用关键词struct
void f(C<int>* p);
template <template<typename X> union C>//ERROR:不能使用关键词 union
void f(C<int>* p);
在它们的作用域内,你可以像使用class template那样地使用template template parameters。
Template template paramters的参数也可以有default template arguments(预设模板自变量)。如果客户端没有为相应的参数指定自变量,编译器就会使用这些预设自变量:
template <template <typename T,typename A=MyAllocator> class Container>
class Adaptation{
Container<int> storage;//等价于Container<int,MyAllocator>
...
};
在template template parameters中,template parameter的名称只能被用于template template parameter的其他参数声明中,示例如下:
template <template<typename T,T*> class Buf>
class Lexer{
static char storage[5];
Buf<char,&lexer<Buf>::storage[0]> buf;
...
};
template <template<typename T> class List>
class Node{
static T* storage;//ERROR:这里不能使用template template parameters的参数T
...
};
为了防止上面的问题出现,通常template template parameter中template parameters名称并不会在其他地方别用到,因此,未被用到的template parameter可以不具名,示例如下:
template <template <typename,typename=MyAllocator> class Container>
class Adaption{
Container<int> storage;//等价于Container<int,MyAllocator>
...
};
预设模板引数的设定大家可以参考这篇博客:default template arguments for both function and class templates
无论何种template parameters都可以有预设自变量,当然它必须匹配对应参数,很明显,预设模板自变量不能依赖于其自身参数,但可以相依赖于它之前声明的参数:
template <typename T,typename Allocator=allocator<T> >
class list;
和函数的预设自变量一样,某个参数带有预设自变量的条件是:后续所有参数也都有预设自变量。
后续参数的Usher自变量通常卸载同一个template声明语句中,但也可以写在该template更早的某个声明语句中。例如:
template <typename T1,typename T2,typename T3,typename T4=char,typename T5=char>
class Quintuple;//OK
template <typename T1,typename T2,typename T3=char,typename T4,typename T5>
class Quintuple;//OK
template <typename T1=char,typename T2,typename T3,typename T4,typename T5>
class Quintuple;//ERROR:T1不能拥有默认值,因为T2没有默认值
同时我们也不能重复指定默认模板引数
template <typename T=void>
class Value;
template <typename T=void>
class Value;//ERROR:预设自变量被重复定义了
Template Arguments是编译器实例化一个template时用来替换template parameters的值。编译器以数种不同的机制来决定以何值替换template parameters:在带有参数P1,P2…的class template X作用域中,template X的名称与template-id
template <typename T>
inline T const& max(T const& a,T const& b){
return a<b?b:a;
}
int main(){
max<double>(1.0,-3.0);
max(1.0,-3.0);
max<int>(1.0,3.0);
}
但是需要注意有的template arguments无法被推导获得,我们最好将这一类参数放在template parameter list的最前面,这样客户端只需明白指定编译器无法推导的那些自变量即可,其余自变量仍可被自动推导获得:
template <typename DstT,typename SrcT>
//由于DstT不出现在自变量列,无法进行自变量推导
inline DstT implicit_cast(SrcT const& x){
return x;
}
int main(){
double value=implicit_cast<double>(-1);//OK,这样编译器可以自动推导SrcT参数
return 0;
}
如果我们将template paramter的顺序换一下呢?
template <typename SrcT,typename DstT>
inline DstT implicit_cast(SrcT const& x){
double value=implicit<int,double>(-1));
return 0;
}
这样我们就需要将两个参数的类型明确给定了,因为SrcT在前面,在推导时候编译器可以确定,但是后面的需要给定默认的值,因此两种类型的值都必须明确指定。
由于function template可以被重载,因此及时写出一个function template的所有自变量,可能也不足以使得编译器确定到底该调用哪种函数,实例如下:
template <typename Func,typename T>
void apply(Func func_ptr,T x){
func_ptr(x);
}
template <typename T>
void single(T);
template <typename T>
void multi(T);
template <typename T>
void multi(T*);
int main(){
apply(&single<int>,3);//OK
apply(&multi<int>,7);//ERROR:符合multi<int>形式的函数不只一个
return 0;
}
不仅如此,明确指定template arguments还可能导致构建出不合法的C++类型,考虑如下的重载函数:
template <typename T> RT1 test(typename T::X const*);
template <typename T> RT2 test(...);
算是test<int>对于第一个function template而言是没有意义的,因为int类型并没与member type X,然而第二个function template没有这种问题,因此算式&test<int>可以明确制定出惟一一个函数地址。于是尽管第一个template以int替换失败,却并没有造成&test<int>随之不合法。正式SFINAE(替换失败并非错误)这一原则,function template的重载实际可行。看看SFINAE原则在如下代码中的应用:
template <int N>
int g(){
return N;
}
template <int* P>
int g(){
return *P;
}
int main(){
return g<1>();//1无法适用于int*参数,SFINAE原则在这里起了重要的作用
return 0;
}
template type arguments是针对template type parameters而指定的值,我们管用的大多数types都可以作为template arguments使用,但有两个例外:
1)local classes和local enum types不能作为template type arguments使用;
2)如果某个type涉及无名的class types或者无名的enum types,这样的type同样也无法作为template type arguments使用,但如果运用typedef使其具名便可以被当做template type arguments使用。
template <typename T>
class List{
...
};
typedef struct{
double x,y,z;
}Point;
typedef enum{red,green,blue} *ColorPtr;
int main(){
struct Association{
int *p;
int *q;
};
List<Association*> error1;//ERROR:template arugement不能为local type
List<ColorPtr> error2;//ERROR:template argument不能为unnamed type
List<Point> ok;//原本无名的type因typedef而有了名称
return 0;
}
Nontype template arguments是针对nontype template parameters(非类型模板参数)而指定的值。下面的nontype template arguments都合法:
template <typename T,T nontype_param>
class C;
C<int,33>* c1;
int a;
C<int*,&a) c2;
void f();
void f(int);
c<void(*)int,f>* c3;//调用f时候,&会被隐寓加入
class {
public:
int n;
static bool b;
};
C<bool&,X::b>* c4;//static类成员都可以被接受
C<int X::*,&X::n> c5;
template <typename T>
void templ_func();
C<void(),&templ_func<double> >* c6;
template arguments的一个一般性约束条件是:必须能够在编译期或者链接期求职。只在执行期间才能求值的表达式不能作为nontype template arguments使用;
及时如此,nontype template arguments还不包括:
null pointer常数
浮点数(floating-point numbers)
字符串字面常数(string literals)
derived-to-base转换
不能以字符串字面常量作为nontype template arguments的一个技术难题在于:两个内容相同的字符串字面常量可能存在两个不同的地址上,一种稍显笨拙的方法是引入一个字符串array:
template <char const* str>
class Message;
extern char const hello[]="Hello World!";
Message<hello>* hello_msg;
这里我们必须使用关键词extern,因为const array默认采用内部链接。
下面还有一些错误实例,供大家参考学习:
template <typename T,T nontype_param>
class C;
class Base{
public:
int i;
}base;
class Derived:public Base{
}derived_obj;
C<Base*,&derived_obj>* error1;//ERROR,derived-to-base不被考虑;
C<int&,base.i>* error2;//ERROR,成员变数不被考虑
int a[10];
C<int*,&a[0]>* error3;//ERROR,不能使用array内某个元素的地址
Template template argument必须是这样的一个class template,其参数完全匹配待替换之template template parameter的参数。Template template argument的default template argument会被编译器忽略,除非对应的template template parameter有预设的自变量。
#include <list>
template <typename T1,typename T2,template <typename > class Container>
class Relation{
public:
...
private:
Container<T1> dom1;
Container<T2> dom2;
};
int main(){
Relation<int,double,std::list> rel;//std::list的提供的template template argument有两个,而对应的template template parameter只有一个预设自变量。
return 0;
}
问题出在std::list template拥有不止一个参数,第二参数(是个allocator,配置器)有默认值,但编译器把std::list匹配至Container时,该默认值被忽略了。
我们可以将其进行改写:
template <typename T1,typename T2,template <typename T,typename=std::allocator<T> > class Container>
class Relation{
public:
...
private:
Container<T1> dom1;
Container<T2> dom2;
};
template template parameter不能使用union和struct,猜猜看,template template argument呢?没有这个限定啦,所以开心的玩起来啦!
当两组template arguments的元素意义对等时候,我们称这两组变量等价。对于type arguments,typedef的名称并不印象对比过程最终被比较的是typedef所指代的type。对于整型nontype arguments,比较的是自变量值,与自变量表达式无关:
template <typename T,int I>
class Mix;
typedef int Int;
Mix<int,3*3>* p1;
Mix<Int,4+5>* p2;//p2和p1具有相同的类型
一个由function template产生的函数,和一个常规函数,无论如何不会被编译器视为等价,及时它们的类型和名称完全相同。这对class template造成两个重要结果:
1)由member function template产生的函数不会覆盖虚拟函数;
2)由constructor template产生的构造函数不会被当做default copy构造函数。同样的道理适用于由assignment template产生的assignment运算符不会被当做一个copy-assignment运算符。
Friend声明语句的基本概念简单:指定某些classes或functions,让它们可以对friend声明语句所在的class进行特权存取。但是下面两种事实使得这个概念变得复杂:
1)friend声明语句可能是某种物体的惟一声明(即friend仅仅在class内部声明,别无其他兄弟);
2)friend函数声明可以就是其定义。
friend class声明语句不能成为一个定义式,这就大大降低了问题的发生,涉及templates时,惟一需要考虑的情况是:你可以把某个class template的特定实体声明为friend:
template <typename T>
class Node;
template <typename T>
class Tree{
friend class node<T>;
...
};
注意:在class template的某一实体成为其他class或者class template的friend之前,class template Node必须已被声明而且可见,但是对于常规class而言并没有什么限制。
template <typename T>
class Tree{
friend class Factory;//OK,因为这是常规类Factory的首次声明
friend class Node<T>;//ERROR,因为之前并没有声明Node
};
function template的具现体可以成为别人的一个friend function,前提是该function template名称之后必须紧跟着以角括号括起来的自变量列-如果编译器可推导出所有自变量,自变量列可以为空:
template <typename T1,typename T2>
void combine(T1,T2);
class Mixer{
friend void combine<>(int&,int&);
friend void combine<int,int>(int,int);
friend void combine<char>(char,int);
friend void combine<char>(char&,int>;//ERROR,char与char&不匹配
friend void combine<>(long,long){...}//不能再此处定义函数
};
注意,我们无法定义一个template实体,最多只能定义一个特化体,因此一个令某实体获得名称的friend声明语句,不能是个定义式。如上面代码中的最后一个模板函数试图定义函数。
如果friend后面没有跟着角括号,有如下两种可能:
1)如果这不是个资格修饰名称(亦即不含::),就绝不会一弄某个template实体。如果编译器无法在friend声明处匹配一个non-template function,则这个friend声明便被当做这个函数的首次声明。这个声明语句是个定义式,即non-template function声明;
2)如果这是一个资格修饰名称(亦即含有::),就必定引用一个先前已定义的function或者functionn template。编译器会有限匹配常规non-template函数,然后才匹配function templates。这个friend声明语句不能是个定义式。
void multiply(void*);//常规函数
template <typename T>
void multiply(T);//function template
class Comrades{
friend void multiply(int){}//定义了一个新函数::multiply(int)
friend void ::multiply(void*);//引用先前定义的常规函数,即multiply(void*),而非multiply<void*>实体
friend void ::multiply<double*>(double*);//带有角括号,此时编译器必须见到该template
friend void ::error(){}//ERROR,带修饰符的friend,不能是个定义
};
上面代码中,我们将friend function声明在常规class中,如果把friend声明在class templates中,先前规则同样适用,而且template parameter可以参与到friend function之内:
template <typename T>
class Node{
Node<T>* allocate();
...
};
template <typename T>
class List{
friend Node<T>* Node<T>::allocate();
...
};
然而将friend function定义域class template中可以引发一个有趣的错误因为任何只在template中被声明的object,都是直到template被实例化后才能成为具现实体。
template <typename T>
class Creator{
friend void appear(){
...
}
};
Creator<void> miracle;
Creator<double> oops;//ERROR,试图再次生成::appear()
这种问题的解决方法是什么呢?确保class template的template parameters出现在定义于该template之中的所有friend function的类型之中,除非我们想要阻止这个class template在一个文件中被多次实例化,解决方法:
template <typename T>
class Creator{
friend void feed(Creator<T>*){
...
}
};
Creator<void> one;//OK,生成feed(Creator<void>*)
Creator<double> two;//OK,生成feed(Creator<double>*)
另外还请注意,这些函数被定义于class定义式内,因此它们案子成为inline。而且如果你在两个不同的编译单元中产生同一个函数,编译器都不会认为有错误。
class Manager{
template <typename T>
friend class task;
template <typename T>
friend void Schedule<T>::dispatch(Task<T>*);
template <typename T>
friend int ticket{
return ++Manager::counter;
}
static int counter;
};
和常规的friend声明语句一样,只有当friend template产生一个无修饰函数名,而且该名称之后不紧跟着角括号,这个friend template才是一个定义式。
friend template只能声明primary templates机器primary templates的成员。任何与primary template相关的偏特化体和明确特化体都将被编译器自动视为friends。
PS:
1)template template parameters(双重模板参数)是一种class templates占位符号,声明方式和class template类似。
2)primary templates指的是没有进行偏特化或者特化的模板类或者模板函数。