C++ template高阶技巧(4)---《C++ Templates》

来源:互联网 发布:免费注册淘宝账号官网 编辑:程序博客网 时间:2024/06/05 04:19

摘要:

1)function templates不允许拥有template template parameters;
2)assignment运算符的template版本不会取代default assignment运算符;
3)可以将class template当做template parameter使用,称为template template parameters;
4)只有当采用by value方式使用字符串字面常数时候,字符串底部的array才会被转型(退化)为一个字符指针。

关键词typename

typename有两种作用,一种是放在template后面的类型参数中声明type template parameter,另一种是放在class template或者tunction template内部声明参数类型。

template <typename T>class MyClass{    typename T::SubType* ptr;    ...};

以上面代码为例,如果我们不在MyClass中声明typename的话,是什么意思呢?SubType会被认为Class T的一个static成员,然后和ptr做乘法运算而已。如果声明为typename的话呢,则SubType就是class T内部定义的一个类型,从而ptr是一个指向T::SubType的指针。

下面例子是typename的两种作用在function template中的应用体现:

#include <iostream>template <typename T>void printcoll(T const& coll){    typename T::const_iterator pos;    typename T::const_iterator end(coll.end());    for(pos=coll.begin();pos!=end;++pos){        std::cout<<*pos<<" ";    }    std::cout<<std::endl;}

.

template构件

template <int N>void printBitset(std::bitset<N> const& bs){    std::cout<<bs.template to_string<char,char_traits<char>,allocator<char> >();}

这个例子中我们在bs后面添加.template狗之间,显得有些奇怪,为什么要添加呢?这是因为如果不添加的话,编译器无法得知紧跟后面的“<”代表的是个template argument list的其实,而非一个小于符号。注意,只有当位于“.”之前的构件取决于某个template parameter时候,这种问题才会发生,上例中bs是取决于N的,一次需要添加.template构件。“.template”或者”->template”记号只在template中才能使用,而且必须紧跟与template parameter相关联的对象。

使用this->

class Base{public:    void exit();};template <typename T>class Derived:public Base<T>{public:    void foo(){        exit();    }};

本例中,针对模板Base<T>,编译阶段就会出错,因为定义于是Base内的exit会被编译器忽略,因此要么得到编译错误,要么就是调用的外部exit()。建议在使用与template相关的符号时候,总是以this->或者Base<T>::进行修改时。

Member Template(成员模板)

class的成员也可以是template:既可以是nested class template,也可以是member function template。
这里我们举个例子,是针对不同类型的Stack进行相互赋值的操作,进而引出成员模板的需求:

Stack<int> intStack1,intStack2;Stack<float> floatStack;...intStack1=intStack2;//OKfloatStack=intStack2;//Error,这两个Stack类型不同

针对这种情况,我们只需要将assignment运算符定义为一个template,然后我们就可以让两个“类型不同氮气元素类型可以隐式类型转换”的stack相互赋值。因此,我们的Stack需要进行如下声明:

template <typename T>class Stack{private:    std::deque<T> elems;public:    void push(T const&);    void pop();    T top() const;    bool empty() const{        return elems.empty();    }    template <typename T2>    Stack<T>& operator=(Stack<T2> const&);};template <typename T>template <typename T2>Stack<T>& Stack<T>::opearator=(Stack<T2> const& op2){    if((void*)this==(void*)op2){        return *this;    }    Stack<T2> tmp(op2);    elems.clear();    while(!tmp.empty()){        elems.push_front(tmp.top());        tmp.pop();    }    return *this;}Stack<int> intStack1,intStack2;Stack<float> floatStack;...floatStack=intStack1;//OK

注意,上面的template assignment运算符不会取代default assignment运算符,如果你在相同类型的stack之间赋值,编译器还是会采用default assignment运算符。

我们也可以将内部容器的类型也参数化:

template <typename T,typename CONT=std::deque<T> >class Stack{private:    CONT elems;public:    void push(T const&);    void pop();    T top() const;    bool empty() const{        return elems.empty();    }    template <typename T2,typename CONT2>    Stack<T,CONT>& operator=(Stack<T2,CONT2> const&);};template <typename T,typename CONT>template <typename T2,typename CONT2>Stack<T,CONT>& Stack<T,CONT>::operator=(Stack<T2,CONT2> const& op2){    if((void*)this==(void*)&op2){        return *this;    }    Stack<T2,CONT2> tmp(op2);    elems.clear();    while(!tmp.empty()){        elems.push_front(tmp.top());        tmp.pop();    }    return *this;}

Template Template Parameters(双重模板参数)

一个template parameter本身也可以是个class template,这一点非常重要。
以前为了使用其他类型的元素容器,stack class需要两次指定元素类型:一次是元素类型本身,另一种是容器类型:

Stack<int,std::vector<int> > VStack;

如果我们使用template template parameter的话,就可以只指明元素类型,无需指定容器类型,为了完成这种情况,我们需要使用template template parameters,如下:

template <typename T,template <typenam ELEM> class CONT=std::deque>class Stack{private:    CONT<T> elems;public:    void push(T const&);    void pop();    T top() const;    bool empty() const{        return elems.empty();    }};

与先前的stack相比,这儿第二个template parameter被声明为一个class template,如果我们使用template <typename ELEM> typename CONT>则会失效,因为这儿CONT是一个class类型,因此必须使用class来加以声明。

Template Template Argument的匹配

template template argument不但必须是个template,而且其必须严格匹配它所替换的template template parameter的参数。Template template argument的默认值不被考虑,因此如果不给出所拥有的默认自变量值时后,编译器会认为匹配失败。
针对上面的template template parameters,如果我们使用以下代码替代:

template <typename T,template <typename> class CONT=std::deque>class Stack{    ...};template <typename T,template <typename> class CONT>void Stack<T,CONT>::push(T const& elem){    elems.push_back(elem);}

编译器会报错:默认值std::deque不符合template template parameter CONT的要求。问题在于:标准库中的std::deque template要求不知一个参数,第二个参数是配置器(allocator),虽然有默认值,但它被用来匹配CONT的参数时候,其默认值被编译器强行忽略了。
解决方法是可以重写class声明语句,使得CONT参数要求一个“带两个参数”的容器:

template <typename T,template <typename ELEM,typename ALLOC=std::allocator<ELEM> > class CONT=std::deque>class Stack{private:    CONT<T> elems;    ...};

零值初始化

针对C++中的内建类型(int、double、pointer type等等)来说,并没有一个default构造函数,使它们初始化为有意义的值,因此我们可以在声明内建类型的时候,明确调用其default构造函数,使得其值为“0”,如:

template <typename T>void foo{    T x=T();}

class template的各个成员,器类型有可能被参数化,为保证初始化这样的成员,必须定义一个构造构造函数,在其成员出事列中对每个成员进行初始化:

template <typename T>class MyClass{private:    T x;public:    //这样可以保证及时T为内建类型时,x也可以被初始化    MyClass():x(){    }    ...};

以字符串字面常数作为Function Template Argument

以by reference传递方式将字符串字面常数传递给function template parameters时候,有可能遇上奇怪的错误:

#include <string>template <typename T>inline T const& max(T const&a,T const&b){    return a<b?b:a;}int main(){    std::string s;    max("apple","peach");//OK    max("apple","tomato");//Error,类型不同    max("apple",s);//Error,类型不同    return 0;}

换句话说“apple”和“peach”的array类型都是char const[6],而“tomato”是char const[7],上述调用只哟偶第一个合法,因为两个参数具有相同的类型,如果你使用by value传递,就可以传递不同类型的字符串字面常数,即通过退化的方式解决,其对应的array大小不同:

#include <string>template <typename T>inline T max(T a,T b){    return a<b?b:a;}int main(){    std::string s;    ::max("apple","peach");//OK    ::max("apple","tomato");//OK    ::max("apple",s);//Error,类型不同}
template <typename T,int N,int M>T const* max(T const(&a)[N],T const(&b)[M]){    return a<b?b:a'}

强迫使用者进行显示转换,注意这也并不会解决string和const char*的问题。