C++ 特化与重载(12)---《C++ Templates》

目前为止,我们已经学习了C++如何使一个泛型定义被展开为一族系相关的classes或者function。但是这远远不够,以一个特定替换物取代泛华的templates parameters远远达不到优化的要求。因此,本片中我们将介绍两种机制,用于完善纯粹的泛型不足,它们是:1)template的特化;2)function template的重载。
在学习今天的课程之前,先让我们回忆一下以前的泛型知识,举例回顾一下:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    T tmp(*a);
    *a = *b;
    *b = tmp;
}
int main(){
    Array<double> a1(10.0);
    Array<double> a2 = a1;
    a2.show();
    return 0;
}

运行结果:

这里写图片描述

如果我们改一下main函数中的代码,看看copy constructor function和copy operator assignment运算符之间的区别:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    T tmp(*a);
    *a = *b;
    *b = tmp;
}
int main(){
    Array<double> a1(10.0);
    Array<double> a2;
    a2 = a1;
    a2.show();
    return 0;
}

新的运行结果:
这里写图片描述

泛型代码不合适的情况

template <typename T>
class Array{
private:
    T* data;
    ...
public:
    Array(Array<T> const&);
    Array<T>& operator=(Array<T> const&);
    void exchange_with(Array<T>* b){
        T* tmp=data;
        data=b->data;
        b->data=tmp;
    }
    T& operator[](size_T k){
        return data[k];
    }
    ...
};  
template <typename T>
inline void exchange(T* a,T * b){
    T tmp(*a);
    *a=*b;
    *b=tmp;
}

对于简单类型,exchange()泛型实作码可以有效运作,然而对于copy操作代价高昂类别而言,这个泛型可能带来高昂的代价。在上面的例子中,泛型实作码需要一个对Array<T>copy构造函数的调用,以及两个对其copy assignment运算符的调用,对大型的数据结构而言,copy过程往往复制超麻烦,通过exchange()往往可以利用两个内部指针进行交换就可以达成,就像成员函数exchange_with()所作所为那样。

  • 通透定制

成员函数exchange_with()为泛化的exchange()函数提供一个有效的特殊实作,但是从某种角度来看,却很不方便:
1)class Array的使用者不得不记住自己有额外的函数,需要在必要的时候使用;
2)实作泛型算法时,经常无法区分何时该使用这个额外接口,何时不使用它,例如下面的代码就会出现这个问题:

template <typename T>
void create_algorithm(T* x,T* y){
    ...
    exchange(x,y);//怎样选择正确的算法?
    ...
}

由于以上问题的存在,C++ templates在设计的提供了如下的解决方案,使程序可以通透地处理function templates和class templates。对于function templates来说,我们可以通过重载机制实现:

template <typename T>
void quick_exchange(T* a,T* b){
    T tmp(*a);
    *a=*b;
    *b=tmp;
}
template <typename T>
void quick_exchange(Array<T>* a,Array<T>* b){
    a->exchange_with(b);
}
void demo(Array<int>* p1,Array<int>* p2){
    int x,y;
    quick_exchange(&x,&y);//quick_exchange(T,T)
    quick_exchange(p1,p2);//quick_exchange(Array<T>*,Array<T>*)
}

对quick_exchange的调用有两个int*自变量,推导机制发现只有第一个template使用,于是将T替换为int,不存在需要调用哪个函数的疑虑。第二次调用匹配两份template中的T替换为int或者Array<int>都可以,我们是不是准备开心地将它作为歧义处理呀?哈哈哈,不要被表面所欺骗了,C++有其定义的适配策略,通过比较两种适应度的强弱,更强的胜出,经过对比,第二个template比第一个template适应效果更好一些,因此,更特化的template胜出。
为此,我们准备了以下的代码:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    cout << "泛化版本的交换操作" << endl;
    T tmp(*a);
    *a = *b;
    *b = tmp;
}

template <typename T>
void exchange(Array<T>* arr_a, Array<T>* arr_b){
    cout << "泛化版本Array<T>的交换" << endl;
    Array<T> tmp(*arr_a);
    *arr_a = *arr_b;
    *arr_b = tmp;
}
int main(){
    Array<double> a1(10.0);
    Array<double> a2;
    exchange(&a1, &a2);
    return 0;
}

运行结果:
这里写图片描述
可以看到我们这儿针对function template的重载机制起了很重要的作用!!!

函数重载与特化的优先级:
1)重载

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    cout << "泛化版本的交换操作" << endl;
    T tmp(*a);
    *a = *b;
    *b = tmp;
}

template <typename T>
void exchange(Array<T>* arr_a, Array<T>* arr_b){
    cout << "泛化版本Array<T>的交换" << endl;
    Array<T> tmp(*arr_a);
    *arr_a = *arr_b;
    *arr_b = tmp;
}
/*
template<>
void exchange<Array<double> >(Array<double>* arrA, Array<double>* arrB){
    cout << "特化版本Array<double>的交换" << endl;
    Array<double> tmp(*arrA);
    *arrA = *arrB;
    *arrB = tmp;
}
*/
int main(){
    Array<double> a1(10.0);
    Array<double> a2;
    exchange(&a1, &a2);
    return 0;
}

运行结果:
这里写图片描述
2)特化

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    cout << "泛化版本的交换操作" << endl;
    T tmp(*a);
    *a = *b;
    *b = tmp;
}
/*
template <typename T>
void exchange(Array<T>* arr_a, Array<T>* arr_b){
    cout << "泛化版本Array<T>的交换" << endl;
    Array<T> tmp(*arr_a);
    *arr_a = *arr_b;
    *arr_b = tmp;
}
*/
template<>
void exchange<Array<double> >(Array<double>* arrA, Array<double>* arrB){
    cout << "特化版本Array<double>的交换" << endl;
    Array<double> tmp(*arrA);
    *arrA = *arrB;
    *arrB = tmp;
}

int main(){
    Array<double> a1(10.0);
    Array<double> a2;
    exchange(&a1, &a2);
    return 0;
}

运行结果:
这里写图片描述

3)特化和重载两种情况都在:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T data;
public:
    Array(){

    }
    Array(T data){
        this->data = data;
    }
    Array(Array<T> const&);
    Array& operator=(Array<T>& b);
    void exchange_with(Array<T>* b){
        T* tmp = data;
        data = b->data;
        b->data = tmp;
    }
    void show(){
        cout << data << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};
template <typename T>
Array<T>::Array(Array<T> const& arrayb){
    cout << "copy constructor function()" << endl;
    this->data = arrayb.data;
}


template <typename T>
Array<T>& Array<T>::operator=(Array<T>& b){
    cout << "copy operator=运算符操作" << endl;
    this->data = b.data;
    return *this;
}


template <typename T>
void exchange(T* a, T* b){
    cout << "泛化版本的交换操作" << endl;
    T tmp(*a);
    *a = *b;
    *b = tmp;
}

template <typename T>
void exchange(Array<T>* arr_a, Array<T>* arr_b){
    cout << "泛化版本Array<T>的交换" << endl;
    Array<T> tmp(*arr_a);
    *arr_a = *arr_b;
    *arr_b = tmp;
}

template<>
void exchange(Array<double>* arrA, Array<double>* arrB){
    cout << "特化版本Array<double>的交换" << endl;
    Array<double> tmp(*arrA);
    *arrA = *arrB;
    *arrB = tmp;
}

int main(){
    Array<double> a1(10.0);
    Array<double> a2;
    exchange(&a1, &a2);
    return 0;
}

运行结果:
这里写图片描述

由上面例子可见,function template的特化优先级高于function template的重载优先级的。即优先级为:非模板函数>模板函数特化>模板重载

  • 语意的通透性
struct S{
    int x;
}s1,s2;

void distinguish(Array<int> a1,Array<int> a2){
    int* p=&a1[0];
    int* q=&s1.x;
    a1[0]=s1.x=1;
    a2[0]=s2.x=2;
    quick_exchange(&a1,&a2);
    quick_exchange(&s1,&s2);
}

这个例子显示,在调用quick_exchange()之后,指向第一个Array的指针p改而指向第二个Array,然而调用quick_exchange()之后,执行那个non-Array s1的指针任然只下过s1,只是指针所指的值互换了。进行一个代码优化:

template <typename T>
void exchange(Array<T>* a,Array<T>* b){
    T* p=&(*a)[0];
    T* q=&(*b)[0];
    for(size_t k=a->size();k--!=0;){
        exchange(p++,q++);
    }
}

这个版本相对泛化版本的优势在于,不需要一个大型的瞬时Array<T>。exchange() template被递归调用,因此即使面对多层递归也可以有很好表现。另外请注意,这个较为特化的template版本并没有被声明为inline,因为它内部做了相当数量的工作,而原始的泛化版本是inline。

重载function template

template <typename T>
int f(T){
    return 1;
}

template <typename T>
int f(T*){
    return 2;
}

这儿我们举一个例子,如下:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
int f(T t){
    return 1;
}

template <typename T>
int f(T* t){
    return 2;
}
int main(){
    int i = 10;
    int *ptr_i = &i;
    cout<<f(i)<<endl;
    cout << f(ptr_i) << endl;
    return 0;
}

运行结果:
这里写图片描述

当一个template中的T被替换为int*,如果把第二个template中的T替换为int,我们将得到参数类型和返回值类型完全相同的两个函数,这些template可以共存,但是合法吗???

#include <iostream>
int main(){
    std::cout<<f<int*>((int*)0)<<std::endl;
    std::cout<<f<int>((int*)0)<<std::endl;
    return 0;
}

输出结果为:
1
2

这下我们可以证明这种function template重载合法。C++中根据匹配规则选择最特化的那个function template进行匹配。

  • Signatures

两个函数可以共存于一个程序中,只要彼此的signatures不同。我们定义函数的signatures包含以下信息:
1)函数的非受饰名称(或者生成该函数的function template名称);
2)该名称的class、namespace作用域;以及它所声明于其中的编译单元;
3)该函数所带的const、volatile或者const volatile饰词;
4)函数参数类型(如果该函数是有一个function template生成,所指得失template parameters被替换前的类型);
5)返回类型(如果函数从一个function template生成);
6)template parameters和template arguments(如果函数从一个function template生成),这意味这下面的templates和其具现体原则上可存在于一个程序中:

template <template T1,template T2>
void f1(T1,T2);

template <typename T1,typename T2>
void f1(T2,T1);

template <typename T>
long f2(T);

template <typename T>
char f2(T);

然而,当这些函数被声明于同一个作用域中,它们无法被使用,因为有歧义困扰,距离如下:

#include <iostream>
template <typename T1,typename T2>
void f1(T1,T2){
    std::cout<<"f1(T1,T2)"<<std::endl;
}
template <typename T1,typename T2>
void f1(T2,T1){
    std::cout<<"f1(T2,T1)"<<std::endl;
}
int main(){
    f1<char,char>('a','b');//ERROR,歧义明显
    return 0;
}

这里,函数f1

#include <iostream>
template <typename T1,typename T2>
void f1(T1,T2){
    std::cout<<"f1(T1,T2)"<<std::endl;
}
void g(){
    f1<char,char>('a','b');
}
#include <iostream>
template <typename T1,typename T2>
void f1(T2,T1){
    std::cout<<"f1(T2,T1)"<<std::endl;
}
extern void g();

int main(){
    f1<char,char>('a','b');//调用本例中的f1
    g();//调用上面编译单元中的f1
    return 0;
}

运行结果为:
f1(T2,T1)
f1(T1,T2)

  • Partial Ordering of Overloaded Function Templates(重载化函数模板的偏序规则)
#include <iostream>
template <typename T>
int f(T){
    return 1;
}
template <typename T>
int f(T*){
    return 2;
}
int main(){
    std::cout<<f<int*>((int*)0)<<std::endl;
    std::cout<<f<int>((int*)0)<<std::endl;
    return 0;
}

我们发现,在替换了给定的template argument list后,重载机制最终正确选择了它所要调用的函数,然而即使不提供明确的template arguments,重载机制也会选中某个函式,这种情况下自变量推导机制加入战场,现在我们修改一下代码进行讨论:

#include <iostream>
template <typename T>
int f(T){
    return 1;
}
template <typename T>
int f(T*){
    return 2;
}
int main(){
    std::cout<<f(0)<<endl;
    std::cout<<f((int*)0)<<std::endl;
    return 0;
}

运行结果:
这里写图片描述
考虑到第一个调用语句f(0):自变量类型为int,与第一个template的参数类型匹配;
第二个调用语句f((int*)0)比较有趣:自变量推导机制成功作用于两个templates身上,并产生f<int>(int*)和f<pointer to int>(int*)//这里pointer to int即为int*,因为int*打不出来,哈哈哈,从传统的重载解析角度看,两个函数对于以int*为引数的呼叫式匹配程度相同,这也许会是你认为这里发生歧义(ambiguous),然而,并没有,因为这种情况下,C++还有自己的template选择机制呢?更特化的template所产生的函数将被调用。这里,我们认为第二个template更特化,因此,这个程序没有歧义性。

  • Formal Ordering Rules(正序规则)

该规则用来比较重载的template function在匹配时候哪个更匹配(特化)。
假设比较两个同名的function template ft1和ft2,它们都适用于某个给定的调用语句。被预设自变量和省略号参数涵盖的那些参数不在ordering rules考虑范围之内。接下来我们合成两组自变量类型(针对转型运算符则还包括一个回返类型),并逐一以下面方式替换template parameters
1)将每个type template parameter替换为一个独一无二的编造类型;
2)将每个template template parameter替换为一个独一无二的class template;
3)将每个nontype template parameter替换为一个独一无二且在相称类型之下的编造值。

如果以第一组合成类型对第二个template进行自变量推导产生出一个完美匹配,反之不然,我们便说第一个template比第二个更特化。相反地,如果以第二组合城类型与第一个template进行自变量推导产生出一个完全匹配,反之不然,我们边说第二个template比第一个更特化。当两个推导都成功或者失败时候,两个template之间毫无顺序可言。
最后,考虑一个更加复杂的例子,涉及多个函数参数:

template <typename T>
void t(T*,T const* =0,...);

template <typename T>
void t(T const*,T*,T*=0);

void example(int *p){
    t(p,p);
}

首先,因为实际调用语句中并未用到第一个template的省略号参数,而第二个template的最后一个参数被其预设自变量顶替因此在partial ordering中这些参数都被忽略注意第一个template的预设自变量未被使用,因此对应的参数会参与到ordering之中。
合成的自变量类型(A1*,A1 const*)和(A2 const*,A2 )。前者对第二个template的推导过程成功,伊A1 const替换T,但引发的匹配并不完全,因为以类型(A1,A1 const*)调用t<A1 const>(A1 const*,A1 const*,A1 const*=0)时,需要一个const饰词,类似情况,以第二组自闭那两类型(A2 const*,A2*)推导第一个template的过程中,也无法找到完全匹配。因此两个template之间没有顺序关系,因此这个调用具有歧义性。
Formal ordering rules(正序规则)通常可以导致直观的选择。

Templates和Nontemplates

Function templates可以被non-template function重载,在其他因素相同的情形下,编译器会优先选择non-template function,下面例子证实了这点。

#include <string>
#include <iostream>

template <typename T>
std::string f(T){
    return "Template";
}
std::string f(int&){
    return "Nontemplate";
}
int main(){
    int x=7;
    std::cout<<f(x)<<std::endl;
    return 0;
};

运行结果:
这里写图片描述

明确特化(显示特化)—适用于class template和function template

C++类的特化或者函数的特化有什么作用呢?
答案:阳奉阴违,很厉害的一种属性,即funtion template或者class template中的特化版本中,可以是一个完全与模板类或者模板函数无关的一种特化,参考如下代码:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Array{
private:
    T* data;
    int N;
public:
    Array(){

    }
    Array(T* data,int n){
        this->data = data;
        this->N = n;
    }
    Array(Array<T>  const& arr){
        this->data = arr.data;
        this->N = arr.N;
    }
    void show(){
        for (size_t i = 0; i < N; i++){
            cout << data[i] << " ";
        }
        cout << endl;
    }
    T& operator[](size_t k){
        return data[k];
    }
};

template <>
class Array<string>{
private:
    string i;
public:
    Array(){

    }
    Array(string i){
        this->i = i;
    }
    void show(){
        cout << i << endl;
    }
    void caicai(){
        cout << "你被骗了,这是特化版本中的特性,与模板类或者模板函数没有任何关系" << endl;
    }
};
int main(){
    int *p = new int[10];
    for (int i = 0; i < 10; i++){
        p[i] = i + 1;
    }
    Array<int> arr(p,10);
    arr.show();
    Array<string> arr1("hello");
    arr1.show();
    arr1.caicai();
    return 0;
}

运行结果:
这里写图片描述
我们可以发现,特化版本可以和template版本没有任何的联系,甚至可以声明一个完全不同的类,因此,这就是特化带来的多样性:阳奉阴违,但是意味着实例化时候如果参数是特化的那种参数,只能调用特化提供的版本了,尽管与template可能千差万别。

我们可以重载function template,并结合partial ordering rules以选择最匹配的function template,这就使得我们可以作为一个泛型实作品加入更多的特化,以便通透实现程序代码的优化,得到更高的效能。然而class templates无法被重载,因此我们采用新的方式解决这种问题,明确特化即全特化,提供一种template实作方式:将所有template parameters替换掉,不留任何template parameters。class template和function template都可以被全特化,定义于class定义之外的class定义式之外的class template成员(如成员函数、嵌套类别和static成员变量)也可以被全特化。

  • Class Template全特化

全特化系有三个符号所组成的序列引入:template,<和>。紧跟在class名称宣告之后的是template arguments。下面例子展示这一点:

template <typename T>
class S{
public:
    void info(){
        std::cout<<"generic S<T>::info())"<<std::endl;
    }
};

template<>
class S<void>{
public:
    void msg(){
        std::cout<<"full Specialized(S<void>::msg())"<<std::endl;
    }
};

全特化的实作不必和泛华定义有任何关联,这使得我们得以拥有不同名称的成员函数。两者之间只靠class template名称相连接。

全特化所指定的template argument list必须与template parameter list相对应。如果你对一个template type parameter指定一个nontype的值,是不合法的,对于那些带有default template argument的那些template parameters,可以不予理会:

template <typename T>
class Types{
public:
    typedef int I;
};
template <typename T,typename U=typename Types<T>::I>
class S;

template <>
class S<void>{
public:
    void f();
};
template <>
class S<char,char>;

template <>
class S<char,0>;//ERROR,不能用0即nontype类型来替换type parameter

int main(){
    //指针无需定义,但是对象必须对其进行定义才能使用
    S<int>* pi;
    S<int> e1;//ERROR,找不到定义
    S<void>* pv;
    S<void,int> sv;
    S<void,char> e2;//ERROR,找不到定义
    S<char,char> e3;//ERROR,找不到定义
    return 0;
}
template <>
class S<char,char>{

}

这个例子表示的是,全特化template的声明语句不必须为定义式。然而当一个全特化体被声明后,对于给定的template arguments集合,泛化定义式不再使用。因此如果没有提供所需定义,程序就会出错。撰写class template特化时,前置声明很有用,使你得以声明彼此想依的类型。全特化声明语句通常与常规class的声明语句完全相同,即前者并不被视为一个template声明,惟一区别是声明的语法,而且该声明必须与先前的template声明匹配,由于它不是一个template声明,所以class template全特化体可以使用日常out-of-class成员定义语法即表示你不能使用template<>前导词。

template <typename T>
class S;

template<>
class S<char**>{
public:
    void print() const;
};

void S<char**>::print() const{
    std::cout<<"pointer to pointer to char"<<std::endl;
};

一个更高等级的特化成员函数声明:

template <typename T>
class Outside{
public:
    template <typename U>
    class Inside{
    };
};
template<>
class Outside<void>{
public:
    template <typename U>
    class Inside{
    private:
        static int count;
    };
};
template <template U>
int Outside<void>::Inside<U> count=1;

全特化就是某确凿值泛化template具现体,因此同一个程序中不能同时存在一个template全特化和一个由前者产生的具现体。如果同时使用两者,编译器会很懵:

template <typename T>
class Invalid{
};
Invalid<double> x1;//class template的实例化

template <>
class Invalid<double>;//特化class template,ERROR,因为已经被实例化过了

然而,这个问题可以深层隐藏,如果其躲在不同的编译单元中呢?这下编译器也很难发现它们,如以下的这种情况:
编译单元1

template <typename T>
class Danger{
public: 
    enum{max=10;}
};
class Buffer[Danger<void>::max];//使用泛化值
extern void clear(char const*);
int main(){
    clear(buffer);
    return 0;
}

编译单元2

template <typename T>
class Danger;
template <>
class Danger<void>{
public:
    enum {max=100};
};
void clear(char const* buf){
    for(int k=0;k<Danger<void>::max;++k){
        buf[k]='\0';
    }
}

这种情况下问题很难被发现,你必须小心确保特化体的申明对泛华template的所有使用者都可见。显示而言,这意味着特化体的声明通常应该在头文件中紧跟其泛化template的声明,但是当泛化实作源自一个外部源码文件的时候,我们可以建立一个头文件,换输入泛化template,然后包括其特化体的申明,这就可以避免这些深层错误。

  • Function Template的全特化

function template全特化的语法和原则非常类似class template的全特化,只是需要在以前的基础上需要再考虑重载以及自变量推导问题。
当被特化之template可经由自变量推导(即将声明语句中的参数类型视为自变量类型)决定时,全特化声明语句可以省略不写明确template arguments,如下面例子所示:

template <typename T>
int f(T){//(1)
    return 1;
}
template <typename T>
int f(T*){//(2)
    return 2;
}
//(1)的特化
template<>
int f(int){
    return 3;
}
//(2)的特化
template <>
int f(int*){
    return 4;
}

function template的全特化体不能含有预设自变量值。然而明确特化时,仍然使用template的预设自变量:

template <typename T>
int f(T,T x=42){
    return x;
}
template <>
int f(int,int=35){//ERROR,全特化体中不能含有预设自变量值
    return 0;
}

//下面,我们看看,正确的是怎样做的吧!
template <typename T>
int g(T,T x=42){
    return x;
}
template<>
int g(int,int y){
    return y/2;
}
int main(){
    std::cout<<g(0)<<std::endl;
    return 0;
}

全特化声明在很多方面类似于一个普通声明,更明确地说,它并不是声明一个template,因此程序中的noinline function template全特化定义只能出现一份。但我们仍然必须确保全特化声明语句必须紧跟在template之后,以求防范使用该template产生的函数。前例中的template g因此往往声明于两个文件中。接口文件(头文件)看起来像这样:

#ifndef TEMPLATE_G_HPP
#define TEMPLATE_G_HPP
template <typename T>
int g(T,T x=42){
    return x;
}

template<> int g(int,int y);
#endif
#include "template_g.hpp"
template<>
int g(int,int y){
    return y/2;
}

另外,特化体可以声明为inline。在这种情况下,其定义可以并且应该被写在头文件中。

  • Member 全特化

不仅是member template,连class template内的static成员变量、成员函数都可以被全特化。语法规定你必须在每个圈封的class template前加上template<>前导词。如果你特化一个member template,必须使用template<>指出它是特化的:

template <typename T>
class Outer{
public: 
    template<typename U>
    class Inner{
    private:
        static int count;
    };
    static int code;
    void print() const{
        std::cout<<"generic";
    }
};
template <typename T>
int Outer<T>::code=6;

template <typename T>
template <typename U>
int Outer<T>::Inner<U>::count=7;

template<>
class Outer<bool>{
public:
    template <typename U>
    class Inner{
    private:
        static int count;
    };
    void print() const{
    }
};
template<>
int Outer<void>::code=12;

template<>
void Outer<void>::print() const{
    std::cout<<"Outer<void>";
}

和function template全特化一样,我们需要一种方式用以声明class template的常规成员的特化体,但不指定任何定义,防止重复定义,虽然C++不允许常规class 的成员函数和static成员变量存在非定义之out-of-class声明语句,但是当特化class template成员时候确实合法:

//非定义声明语句
template<>
int Outer<void>::code;
template<>
int Outer<void>::print();

这里Outer<void>::code全特化体的非定义声明语句和一个默认构造函数进行初始化的定义式,语法相同。但这类声明会被编译器理解为非定义声明语句。
因此,我们无法提供一个只能以默认函数初始化类型,又提供一个static成员变量的全特化定义:

class DefaultInitOnly{
public:
    DefaultInitOnly(){
    }
private:
    DefaultInitOnly(DefaultInitOnly const&);
};
template <typename T>
class Statics{
private:
    static T sm;
};
//这种声明方式不存在任何C++语法可为它提供一份定义
template <>
DefaultInitOnly Statics<DefaultInitOnly>::sm;

member template Outer<T>::Inner也可以经过一个给定的template argument进行特化,不会影响某特定之Outer<T>具现体,由于只有一层圈封的template,我们只需要一个template<>前导词。程序代码为:

template<>
template <typename X>
class Outer<wchar_t>::Inner{
public:
    static long count;
};
template<>
template <typename X>
long Outer<wchar_t>::Inner<X>::count;

template Outer<T>也可以被全特化,但只能在某个给定的Outer<T>实体中对它进行全特化,我们需要两个template<>前导词,一个针对外层class,另一个针对内层template:

template<>
template<>
class Outer<char>::Inner<wchar_t>{
public:
    enum {count=1};
};

//下面的程序就不合法:因为template<>不能更在一个template parameter list之后
template<typename X>
template<>
class Outer<X>::Inner<void>;//ERROR

我们可以将这段程序代码和Outer<bool>的member template特化体进行对照,由于后者已经完全被全特化,没有外层圈封的template,因此只需要一个template<>前导词:

template<>
class outer<bool>::Inner<wchar_t>{
public:
    enum{count=2};
};

下面举一个member 特化的较为全面的一个例子,希望有助于大家理解:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Outer{
public:
    template <typename U>
    class Inner{
        static int count;
    };
private:
    static int code;
};

template <>
class Outer<string>{
public:
    template <typename U>
    class Inner{
    private:
        static int count;
    };
private:
    static int code;
};
int Outer<string>::code = 10;

template <>
class Outer<string>::Inner<string>{
private:
    static int count;
};
int Outer<string>::Inner<string>::count = 100;

Class Template的偏特化

template全特化非常有用,但如果想要针对一整个族系的template arguments而不是特定某一组template arguments来特化class template也是很正常的,即全特化的时候有所保留,例如:

template <typename T>
class List{
public:
    ...
    void append(T const&);
    inline size_t length() const;
    ...
};

在使用上述template的大型项目中,可能会将其成员实例化为多种类型,但那些不是对inline的成员函数而言,这将导致目标代码过大,然而从底层来看,List<pointer to int>::append()的目标码和List<pointer to void>::append()的目标代码一阿英。换句话说我们希望所有以指针为元素的Lists能够共享同一份实作码,这也就引出了我们这章的主题—偏特化

template <typename T>
class List<T*>{
private:
    List<void*> impl;
    ...
public:
    void append(T *p){
        impl.append(p);
    }
    size_t length() const{
        return impl.length();
    }
    ...
};

这便是偏特化的由来,因为你只制定了这个template的一部分template arguements,偏特化的语法是:template parameter list的声明(template<…>)加上class template名称,再加上一组么明确指定的template arguments。

上述代码中有个问题:包含另一个List<pointer to void>成员,构成递归声明。我们可以在偏特化之后再加上全特化,结束这个递归:

template<>
class List<void*>{
    ...
    void append(void* p);
    inline size_t length() const;
    ...
};

这种方法之所以可行,是因为

优先级:全特化>偏特化。

偏特化声明语句的parameter list和argument list存在诸多限制。其中一些如下:
1)偏特化template的自变量必须与primary tempalte的参数种类(type、nontype或者template)逐一匹配;
2)偏特化template的parameter list不能拥有预设自变量,它将使用primary class template的预设自变量;
3)偏特化template的nontype arguments只能是非受控数值或者简朴的nontype template parameters,不能使较复杂的受控运算式,如2*N(N是template parameter);
4)偏特化template的template argument list不能与primary template的template argument list完全相同。

template <typename T,int I=3>
class S;

template <typename T>
class S<int,T>;//ERROR,参数种类不匹配

template <typename T=int>
class S<T,10>;//ERROR,无预设自变量

template <int I>
class S<int,I*2>;//ERROR,不能使nontype表达式

template <typename U,int K>
class S<U,K>;//ERROR,与primary template无显著区别

所有偏特化体和全特化体一样,与其primary template相关联。当你使用一个编译器总会首先查询primary template,但随后各个自变量会与其关联的各个特化体进行匹配,决定到底选用哪一份template实作码。如果有多个匹配结果,就找出其中最特化的那个版本,如果无法比较哪份更特化,就会出现歧义。
最后,我们之处,class template的偏特化完全有可能比其primary template拥有更多或者更少的参数。再次考虑我们的泛华template list。我们已经讨论过如何对以pointer为元素的List进行优化,但我们也可能想对pointer-to-member类型做同样的事情。下面程序针对pointer-to-member-pointers达到同样的优化目的:

template <typename C>
class List<void* C::*>{
public:
    typedef void* C::*ElementType;
    ...
    void append(ElementType pm);
    inline size_t length() const;
    ...
};

template <typename T,typename C>
class List<T* C::*>{
private:
    List<void* C::*> impl;
    ...
public:
    typedef T*  C::* ElementType;
    ...
    void append(ElementType pm){
        impl.append((void* C::*)pm);
    }
    inline size_t length() const{
        return impl.length();
    }
    ...
};

PS:
优先级顺序:全特化>偏特化

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值