现在我们已经知道如何使一个泛型定义扩展成一些相关的类家族和函数家族。但是有些时候,我们需要同一个名字的函数的不同实现(透明定义),为的是在不同情况下获得较高的性能,在这种情况下就不是简单的参数替换就能够解决的。
一、透明定义使用的技术
对于函数模板使用的透明定义的技术是:函数模板的重载
对于类模板的使用透明定义的技术是:类模板的特化(全局特化和局部特化)
二、重载函数模板
两个名称相同的模板可以同时存在,还可以对他们进行实例化。
template<typename T>
int f(T)
{
retrun 1;
}
template<typename T> //重载版本
int f(T*)
{
return 2;
}
如果我们用int*来替换第一个模板参数T,用int来替换第二个模板参数T,纳闷将会获得两个具有相同参数类型和返回类型的同名函数。
int main()
{
cout << f<int*>((int*)0) << endl; //这里回实例化出两个实体,根据实参(int*)选择接近的
cout << f<int>((int*)0) << endl; //这里会实例化出两个实体,根据实参(int*)选择接近的
}
注意:表达式0是一个整数,而不是一个null指针常量。只有在发生特定的隐式转型之后参会成为一个null的指针常量,但是在模板实参演绎的过程中并不会考虑这种转型。
三、签名
只要具有不同的签名,就可以在同一个程序中存在,注意是同一个程序而不是同一个翻译单元。
对函数签名的定义如下:
(1)非受限的函数名称
(2)函数名臣各所属的额类的作用域获名字空间作用域,如果函数名称具有内部连接属性还包括名称声明所在的翻译单元
(3)函数const,volatile或者是cons volatile限定符
(4)函数的参数类型(如果是产生自模板的,指的是模板参数被替换之前的类型)
(5)如果这个函数产生自函数模板,那么还包括它的返回值类型
(6)如果这个函数是产生自函数模板,那么包括模板参数,模板实参。
template<typename T1, typename T2>
void f1(T1, T2);
template<typename T1, typename T2>
void f2(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)\n";
}
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)\n";
}
int main()
{
f1<char, char>(‘a‘, ‘b‘); //错误:二义性
}
因此只有在这两个模板出现不同的翻译单元,它们两个实例化体才可以在同个程序同时存在。
//翻译单元1
#include <iostream>
template<typename T1, typename T2>
void f1(T1, T2)
{
std::cout << "f1(T1, T2)\n" ;
}
void g()
{
f<char, char>(‘a‘, ‘b‘);
}
//翻译单元2
#include<iostream>
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)\n";
}
extern void g(); //定义在单元1
int main()
{
f<cahr, char>(‘a‘, ‘b‘);
g();
}
最后输出:
f1(T2, T1);
f1(T1, T2);
四、显示特化类模板
C++标准的“显示特化”概念指的是一种语言特性,我们通常称之为全局特化。
它为类模板提供了一种使模板参数可以被全局替换的实现,而没有剩下的模板参数。事实上,类模板,函数模板都可以被全局特化,而类模板的成员(成员函数, 嵌入类, 静态成员变量等,他们的定义可以位于类定义的外部)也可以被全局特化。
(一)如何显示特化
template<typename T>
class S{
public:
void f(){
std::cout << "generic(S<T>::info())" << std::endl;
}
};
template<> //显示特化
class S<void>{
public:
void msg(){ //新增加了msg函数
std::cout << "fully specialized (S<void>::msg())" << std::endl;
}
}
实际上,全局特化之和类模板的名称关联,可以包含名称不同的成员函数。
(二)模板特化声明
(模板)全局特化的声明不一定是定义。当一个全局特化声明之后,针对该特化的模板实参列表调用,将不再使用模板的泛型定义,而是使用这个特化的定义。所以如果在一个调用处需要该特化的定义,而在这之前没有提供定义(光有声明)那么程序将会出现错误。
template<typename T>
class Types{
public:
typedef int I;
};
template<typename T, typename U = typename Types<T>::I>//注意这里有默认形参
class S; //(1)
template<>
class S<void>{ //(2)
public:
void f();
};
template<>class S<char, char>; //(3)
template<> class S<char, 0>; //错误应该是一个类型
int main()
{
S<int>* p1; //使用(1)处声明, 不需要定义
S<int> e1; //使用(1)处声明, 需要定义,但是找不到定义
S<void> pv; //使用(2)处声明
S<void, int> sv;//使用(2)处声明, 使用默认形参
S<void, char> e2; //使用(1)处声明, 需要找到定义,但是找不到定义
S<char, cahr> e3; //使用(3)处声明,需要定义,但是找不到定义
}
template<>
class S<char, char>{}; //(3)处的定义
(三)如何定义全局模板特化成员
对于特化声明而言,因为它并不是模板声明,所以应该使用普通的成员定义语法,来定义全局类模板特化的成员(即不能使template<>前缀)
template<typename T>
class S;
template<> class S<char**> {
public:
void print() const;
}
void S<char**>::print() const //这里不能使用template<>
{
std::cout << "使用普通成员定义语法来定义全局类模板特化成员函数" << std::endl;
}
(四)全局特化版本与实例化版本不能共同存在一个程序中(即使是在不同的翻译单元)。
template<typename T>
class Invalid{};
Invalid<double> x1; //此处将产生一个Invalid<double>实例化体
template<>
class Invalid<double>; //错误:Invalid<double>已经被实例化了
(五)特化全局成员函数
template<typename T> //(1)泛型模板定义
class Outer{
public:
template<typename U>
class Inner{
private:
static int count;
};
static int code; //(4)
coid print() const{ //(5)
std::cout << "generic" << std::endl;
}
};
template<tyepname T>
int Outer<T>::code = 6; //泛型模板静态成员定义
template<typename T> template<typename U>
int Outer<T>::Inner<U>::count = 7;// 泛型模板静态成员定义
template<>
int Outer<void>::count = 12; //特化全局成员函数
template<>
int Outer<void>::print() const
{
std::cout << “Outer<void>” << std::endl;
}
定义将会代替类Outer<void>(4)和(5)的泛型定义, 类Outer<void>的其他成员仍然默认的按照(1)处的泛型模板定义产生。另外由于这样也算全局特化了Outer<void>,所以不能再次提供Outer<void>的显式特化。
(六)template<>不能位于template<类型>后面,也就是说,不能先特化Inner,而不特化Outer。
五、局部的类模板特化
因为该模板定义所使用的模板实参只是被局部的指定所以被称为局部特化。
(一)怎样个局部指定法?
template<int N1, int N2>
class S{}; //(1)
template<2, 3> //全局特化
class S{};
template<int N> //(2)局部特化,改模板的实参只是被局部指定。
class S<2, N>{};
这样就把类模板特化成一个“针对模板实参”的类家族,上面的例子就是针对第一个模板实参为2的类家族。
(1)处叫做基本模板。(2)处叫做局部特化
-------------------------------------------------------------
template<typename T>
class List{
public:
void append(T const&);
inline size_t length() const;
};
template<> //全局特化
class List<void*>{
public:
void append(void* const&);
inline size_t length() const;
};
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();
}
};
上面的例子就是针对模板实参为指针的类家族,并且这里使用了一点小技巧:该特化使用了“委托代理”,将所有该类家族的处理交由List<void*>实例处理。这样纸就解决了C++模板备受指责的代码膨胀问题。
为此上面的例子还要一个List<void*>的全局特化,以便编译器可以找到该实例。
template<>
class List<void*>{
void append(void* p);
inline size_t length() const;
};
针对局部特化有以下的一些约束条件:
(1)局部特化的参数必须和基本模板参数在种类上相匹配
(2)局部特化的参数列表不能有缺省实参,但局部特化仍然可以使用基本模板的缺省实参
(3)局部特化的实参或是模板参数,不能是更复杂的依赖型表达式,(诸如2*N,其中N是模板参数)对于非类型实参,或是普通的非类型模板参数。
(4)局部特化的模板实参列表不能和基本模板的参数列表完全相同。(不考虑重新命名)
template<typename T, int I = 3>
class S; //基本模板
template<typename T>
class S<int, T> //错误,第二个参数要是int类型的值,参数类型不匹配。
template<typename T = int>
class S<T, 10> //错误,不能有缺省实参
template<int I>
class S<int, I*2> //错误,局部特化不能有表达式
template<typename U int R>
class S<U, R> //错误,跟基本模板没有实质上区别
编辑整理:Claruarius,转载请注明出处。