函数模板重载会选择更特殊的函数模板
一个例子:
#include<iostream>
#include<vector>
using namespace std;
template<typename T>
void FUN(T & t1,T & t2)
{
cout << "conmon"<<endl
}
template<typename T>
void FUN(vector<T> & v1, vector<T> & v2)
{
cout << "vector" << endl;
}
void main()
{
vector<int> v1, v2;
FUN(v1,v2);
//输出结果:vector
}
上个例子中FUN可以同时演绎两个函数模板,但是最后确选择了vector版本的FUN,原因是:第二个FUN函数模板形参vector<T>对于vector<int>实参更特殊!——重载解析规则选择了更加特殊的函数模板进行实例化!
两个相同的实例化体?
让我们来看看这个示例:
#include<iostream>
using namespace std;
template<typename T>
void FUN(T a)
{
cout << "T a" << endl;
}
template<typename T>
void FUN(T * a)
{
cout << "T * a" << endl;
}
void main()
{
FUN<int>((int *)0);
FUN<int *>((int *)0);
FUN(0);//重载,两个候选函数模板,但是后面的模板参数不匹配,抛弃
FUN((int *)0);//重载,但是后面的函数模板更特殊,选择后面的
}
由上面的示例,我们还能看出:函数模板重载和普通函数的重载的特征不同点——函数模板是若干模板等着被实例化以构成重载!而普通函数是若干函数等着被调用而构成重载!(所以函数模板的重载是哪一个基于函数模参更适合被实例化,然后实例化,普通函数重载是哪个函数形参更匹配,然后被调用!但他们构成重载的条件都是相同的,都是基于参数(不论是普通函数形参还是函数模板模参)而构成是否最适合被调用或最适合被实例化!)
但上面示例真正要表达的不是这个!而是两个“看起来”一模一样的函数实例化体竟然可以共存!并且能够区别被调用!
即 FUN<int>((int *)0)和FUN<int *>((int *)0)都实例化了“看起来”相同类型的函数:void FUN(int *)!其实不是这样的,因为函数模板被实例化之后的实例化体是带着模板信息的(例如函数模参信息T*/T)!因此基于模板来调用看起来相同类型的函数其实是调用的不同的函数!
为了更好地理解上面示例,让我们了解一下签名:
只要两个函数具有不同的函数签名,那么这两个函数就可以在同一个程序中同时存在!
签名的组成:
1,非受限的函数名称(函数模板名称也是)
2,函数名称所属的类作用域和名字空间作用域
3,函数的const、volatile、const volatile限定符
4,函数的参数类型(如果此函数是模板,那么指定是参数被替换之前的函数模板形参类型——T */T)
5,如果此函数产生自模板,那么还有返回类型
6,如果此函数产生自模板,那么还有模板参数(T)和模板实参(int)
只要具备上述任意一条,两个函数就可以同时存在,但是两个函数同时存在却不一定表示万事大吉!
示例:
#include<iostream>
using namespace std;
template<typename T1,typename T2>
void FUN(T1, T2)
{
cout << "FUN(T1,T2)" << endl;
}
template<typename T1, typename T2>
void FUN(T2, T1)
{
cout << "FUN(T1,T2)" << endl;
}
void main()
{
//FUN(2, 2);//error
}
错误信息:
2 IntelliSense: 有多个 重载函数 "FUN" 实例与参数列表匹配:
函数模板 "void FUN(T1, T2)"
函数模板 "void FUN(T2, T1)"
参数类型为: (int, int)
错误原因:虽然两个FUN可以同时存在,但是FUN(2, 2)根本产生不出FUN的实例,因为这里函数模板重载发生了二义性:两个模板都能被完美地匹配int,int的实例化!所以根本产生不了FUN的实例,也就不谈FUN可以同时存在了!
解决办法(FUN分处两个编译单元):只能看见本翻译单元的FUN模板!
函数模板可以和普通函数一起构成重载,但是普通函数具有天然的优越性!
示例:
#include<iostream>
using namespace std;
template<typename T>
void FUN(T)
{
cout << "FUN-template";
}
void FUN(int &)
{
cout << "FUN-nontemplate";
}
void main()
{
int a = 10;
FUN(a);
}
显式特化
全局特化:
1)所有的模板参数都被替换
2)模板类没有重载,模板类的特化可以替代函数模板的重载特性(效果上,且不论是全局类模板特化还是局部类模板特化)
全局特化与缺省模板实参
——特化对的时候用来替换的模板实参是可选的
示例:
#include<iostream>
using namespace std;
template<typename T>
class Types
{
public:
typedef int Type;
};
//基本模板
template<typename T,typename U=typename Types<T>::Type>
class S
{
public:
static void FUN()
{
cout << "class S" << endl;
}
};
//特化时不写缺省模参
template<>
class S<void>
{
public:
static void FUN()
{
cout << "class S<void>" << endl;
}
};
//特化时写缺省模参
template<>
class S<char, char>
{
public:
static void FUN()
{
cout << "class S<char, char>" << endl;
}
};
void main()
{
S<int>::FUN();// class S
//1
S<void>::FUN();// class S<void>
//2
S<void, int>::FUN();// class S<void>
//3
S<void, double>::FUN();// class S
S<char, char>::FUN();// class S<char, char>
}
注意示例中的1,2,3:template<> class class S<void>的全局特化必须替换所有的缺省模参,所以这里相当于:
class S<void,int(缺省)>
{
public:
static void FUN()
{
cout << "class S<void>" << endl;
}
};
于是上述此特化表明:有一个缺省的已知模参,你可以使用此已知的模参,也可以显式表明此缺省实参,但是如果不适用此缺省实参,将实例化别的模板!(S<void, double>::FUN()就是这样)
定义或者特化已经被全局特化的类模板的成员
示例1:定义已经被全局特化的类的成员——不用再加template<>
#include<iostream>
using namespace std;
template<typename T>
class X
{
public:
void FUN();
};
template<>
class X<int>
{
public:
void FUN();//已经被全局特化的成员
};
//定义已经被全局特化的类的成员
void X<int>::FUN()
{
}
//错误的定义方法
template<>
void X<int>::FUN()
{
}
void main()
{
}
示例2:特化已经被全局特化的类的成员——还是遵循示例1的规则
#include<iostream>
using namespace std;
template<typename T>
class X
{
public:
//内部类模板
template<typename U>
class C;
};
template<>
class X<int>
{
public:
//内部类模板
template<typename U>
class C;
};
template<>//这个template<>是对内部C模板的特化加的
class X<int>::C<double>
{
public:
static void FUN()
{
cout << " X<int>::C<double>" << endl;
}
};
void main()
{
X<int>::C<double>::FUN();
}
注意这种方法是错的(在前面已经特化了X<int>的前提下,后面还将详细论述!):
template<>//error:不需要给外层加
template<>
class X<int>::C<double>
{
public:
static void FUN()
{
cout << " X<int>::C<double>" << endl;
}
};
显式实例化能否与全局特化共存?——视两者的位置而定
我们需要知道,全局特化不会生成实例化体,它只是对于特定模板参数的模板定义的另一种形式!所以可以先全局特化一个定义出来,然后再用显式实例化手动产生一个这种定义的POI(实例化体)
#include<iostream>
using namespace std;
template<typename T>
class X{
};
template<>
class X<int>{
};
template class X<int>;
template<typename T>
void FUN();
template<>
void FUN<int>();
template void FUN<int>();
void main()
{
}
上述示例是成功的,原因就在示例的上面,但是如果你先显式实例化,再进行全局特化,将会出错,原因:显式实例化已经根据前面的存在的模板的定义实例化出一个实体,但是全局的特化允许用户再提供一种基于前面显式实例化的模板实参的不同的模板的定义,此时将发生矛盾!
例如:
template<typename T>
class X{
};
template class X<int>;//error
template<>
class X<int>{
};
一个隐式实例化能否与全局特化共存?——视两者的位置而定
原因和之前的一样:不能对已经实例化的实例化体产生另一种定义!
#include<iostream>
using namespace std;
template<typename T>
class X{
};
X<int> x;
//error
template<>
class X<int>{
};
void main()
{
}
函数模板的全局特化
规则:
1)可以通过演绎来特化(不必指定特化的参数,但是显式指定也没错!)
#include<iostream>
#include<string>
using namespace std;
template<typename T>
void FUN(T)
{
cout << "void FUN(T)" << endl;
}
//1
template<>
void FUN(int)
{
cout << "void FUN(int)" << endl;
}
//2
template<>
void FUN<double>(double)
{
cout << "void FUN<double>(double)" << endl;
}
template<typename T>
void FUN(T *)
{
cout << "void FUN(T *)" << endl;
}
//1
template<>
void FUN(int *)
{
cout << "void FUN(int *)" << endl;
}
//2
template<>
void FUN<double *>(double *)
{
cout << "void FUN<double *>(double *)" << endl;
}
void main()
{
FUN(string());
FUN(int());
FUN(double());
string * str = new string("aa");
FUN(str);
FUN((int *)0);
FUN((double *)0);
/*
输出结果:
void FUN(T)
void FUN(int)
void FUN<double>(double)
void FUN(T *)
void FUN(int *)
void FUN(T *)
*/
}
2)对于基本模板的缺省模板实参,特化时不能指定,要用占位置的方式指定!
#include<iostream>
#include<string>
using namespace std;
template<typename T>
void FUN(T t,T x=42)
{
}
template<>
void FUN(int t, int x)
{
cout << x / t << endl;
}
void main()
{
FUN(2);
FUN(2,100);
/*
输出结果:
21
50
*/
}
注意,template<> void FUN(int t, int x)其中的x是必须的,如果不指定,就会出错!
函数缺省模板实参特化和模板缺省模参的特化规则的对比:
模板缺省模参的特化:是继承了基本模板的缺省模参来特化自己,所以那个缺省模参的位置就是基本模板的缺省模参,如果缺省模参的位置被表述为非基本模板的缺省模参,将绕开特化!(这个继承而来的缺省模参可以自动填充,也可以显式表述!)
函数缺省模板实参特化:就是本小题所论述的内容,即必须使用占位函数参数,只是这个占位的形参将被指定为基本模板中的缺省函数函数!
一个例子说明上述区别:
#include<iostream>
#include<string>
using namespace std;
template<typename T,typename U=double>
void FUN() {
cout << "void FUN()" << endl;
}
template<>
void FUN<int>() {
cout << "void FUN<int>()"<< endl;
}
void main()
{
/*
template<typename T,double>
void FUN()
template<>
void FUN<int,double>()//double可以自动填充,也可以显式表述,但是不能如果指定为非double,将绕开特护
*/
FUN<int>();
FUN<int,double>();
FUN<int,string>();
/*
输出结果:
void FUN<int>()
void FUN<int>()
void FUN()
*/
}
成员的全局特化
1)如果特化了类模板的成员,将不能再对这个类模板整体进行基于该类型参数的全局特化(避免定义冲突!)
#include<iostream>
using namespace std;
template<typename T>
class X
{
public:
static int code;
static void MemberFun()
{
cout << "基本模板的MemberFun" << endl;
}
static void PrintCode()
{
cout << code << " " << endl;
}
};
template<>
int X<string>::code = 1000;
template<>
void X<string>::MemberFun(){
cout << "X<string>的MemberFun" << endl;
}
//不能再基于string参数对X整体进行特化
//template<>
//class X<string>
//{};
void main()
{
X<string>::PrintCode();
X<string>::MemberFun();
}
不能再基于string参数对X整体进行特化的原因:在基于string参数对X整体进行特化的定义中,可以舍弃code、MemberFun等成员,那么将和和上面的成员特化矛盾!
2)对于类模板的成员,特化使得其可以在类外重新声明!
#include<iostream>
using namespace std;
//只能使用缺省构造函数进行初始化的类型
class C
{
public:
C() {
cout << "C初始化发生!" << endl;
}
int a = 100;
void FUN(){}
private:
C(C const &);
};
//普通类不能允许类外声明
//void C::FUN();//error:错误 1 error C2761: “void C::FUN(void)”: 不允许成员函数重新声明
template<typename T>
class X
{
public:
static C c;
void MemberFUN(){}
static void FUN(){
cout << c.a << endl;
}
};
//类外重新声明
template<>
void X<string>::MemberFUN();
//error:不能申明不存在的成员
//template<>
//int X<string>::What();
//这里相当于定义,而不是声明了
template<>
C X<string>::c;
void main()
{
X<string>::FUN();
/*
输出结果:
C初始化发生!
100
*/
}
上述例子中有意思的事情是:template<> void X<string>::MemberFUN()是属于类外重新声明的,但是对于static C c成员,template<> C X<string>::c却触发了定义!而不再是重新声明(如果template<> C X<string>::c也是属于重新声明的话,那么将不能对那些只能使用缺省构造的静态内部成员进行类外的特化定义了!因为无法触发定义!但是好像以前存在这种情况,即template<> C X<string>::c属于重新声明的情况,现在好像已经解决,将其视为定义!)
嵌套于类模板内部的类模板的特化:
#include<iostream>
#include<string>
using namespace std;
template<typename T>
class X
{
public:
//内部嵌套类模板
template<typename U>
class C;
};
//特化成员
template<>
template<typename U>
class X<int>::C
{
public:
void FunInC()
{
cout << "class X<int>::C" << endl;
}
};
//内部模板特化的特化
template<>
template<>
class X<int>::C<string>
{
public:
void FunInC()
{
cout << "class X<int>::C<string>" << endl;
}
};
//error1:
//错误 error C3212: “X<T>::C<std::string>”: 模板成员的显式专用化必须是显式专用化的成员
//template<typename T>
//template<>
//class X<T>::C<string>;//X<T>内部可不一定有所谓的C成员
//error2
//template<>
//class X<string>::C<bool>;
void main()
{
}
这个示例中将导致错误的核心问题在于:
1)特化了成员之后就不能再对外围类进行基于该类型参数的特化了(防止特化定义矛盾)!(template<typename U> class X<int>::C的意义在于:X<int>定义的内部一定存在C,且不允许用户再重新特化X<int>——对X<int>提供新定义!)
2)如果对内部成员特化时,必须保证外部类模板已经特化了(并且提供了特化定义)或者在允许的范围内可以禁止外围类的特化!,要不根本不能确保将要特化的内部成员存在还是不存在!
让我们来分析error1:假如class X<T>::C<string>成立,那么应用于第一条规则,将不允许对class X<T>进行再特化(因为所有的对于class X<T>的特化都有可能致使C<string>不存在),这将导致不允许提供对模板X的任何特化,显然这不合理!(而上面的只对X<int>不允许特化还是合理的!)
error2的错误在于少写一个template<>
局部类模板特化
我们前面说过,类模板的特化可以某种程度上代替重载的效果(即因不同的参数选择不同的定义),但是前面讨论的都是基于单一的一种类型选择定义,然而更加泛化的是可以基于一类类型选择类的定义(类似函数模板的重载),例如对于所有的指针类型提供一种定义(注意是一种而不是一个),对于所有的基本类型提供一种定义!而这种效果可以根据局部特化来实现:
一个例子:
#include<iostream>
using namespace std;
template<typename T>
class X;
template<typename T>
class X<T *>
{
public:
static void FUN(){
cout << "class X<T *>" << endl;
}
};
template<typename T>
class X<T **>
{
public:
static void FUN(){
cout << "class X<T **>" << endl;
}
};
//与函数模板重载的比较
template<typename T>
void FUN(T *)
{
cout << "void FUN(T *)" << endl;
}
template<typename T>
void FUN(T **)
{
cout << "void FUN(T **)" << endl;
}
void main()
{
//这一类的模板实参都能选择这种定义
X<int *>::FUN();
X<int **>::FUN();
//这一类对的函数实参都能选择这种定义
FUN((int *)0);
FUN((int **)0);
/*
输出结果:
class X<T *>
class X<T **>
void FUN(T *)
void FUN(T **)
*/
}
我们多次将模板的特化和重载作比较,事实是它们之间确实存在着很大的相似度,而且类模板的特化比函数的重载更优哦!因为特化不产生新定义!
类模板局部特化的规则:
1)局部特化的实参必须和基本模板的相应参数在种类上匹配
template<typename T>
class X;
template<typename T>
class X<T *, int>;//error:错误 1 error C2977: “X”: 模板 参数太多
2)局部特化的参数列表不能具有缺省实参,但局部特化仍然可以使用基本模板的缺省实参
a:不能具有缺省实参(此例中T为缺省实参):
template<typename T>
class X;
template<typename T=int>
class X<T *>;//error:错误 1 error C2756: “T”: 部分专用化中不允许有默认模板参数
b:可以保留缺省实参:
#include<iostream>
using namespace std;
template<typename T,int N=10>
class X
{
public:
static void FUN()
{
cout << "基本!" << endl;
}
};
template<typename U>
class X<U *>
{
public:
static void FUN()
{
cout << "缺省保留!" << endl;
}
};
void main()
{
X<int *>::FUN();
X<int *, 10>::FUN();
X<int *, 20>::FUN();
/*
输出结果:
缺省保留!
缺省保留!
基本!
*/
}
对于特化的时候继承缺省模板实参,在前面模块有详细论述:“函数缺省模板实参特化和模板缺省模参的特化规则的对比”!
3)局部特化的非类型实参只能是非类型值,或者是普通的非类型模板参数,而不能是更复杂的依赖型表达式
template<typename T,int N>
class X;
template<int N>
class X<int , N*2>;//error:错误 1 error C2755: “X<int,N*2>”: 部分专用化的非类型参数必须是简单标识符
至于为什么不能是复杂的依赖型表达式,原因在《Template parameter deduction(模板实参演绎) 》中的“演绎上下文模块”有详细分析:编译器没那么智能!
4)局部特化的模板实参列表不能和基本模板的参数列表完全等同
template<typename T,int N>
class X;
template<typename U,int C>
class X<U,C>;//错误 1 error C2753: “X<U,C>”: 部分专用化无法与主模板的参数列表匹配
(对于这种情况,目前欠缺使用经验,作者将在其后有充足的使用经验了再进行补充!)
两个局部特化特殊性一样将导致二义性错误
实例化过程匹配模板的顺序是:先对基本模板进行查找,然后匹配调用实参和相关的特化,然后才选择哪一个模板进行实例化,但是此时如果两个局部特化对于调用实参的特殊度相同,那么将导致一个二义性错误!(关于二义性错误,在《Template parameter deduction(模板实参演绎) 》开头有论述!)
另外,类模板局部特化的参数个数可以和基本模板不一样:既可以多,也可以少!
#include<iostream>
using namespace std;
template<typename T>
class X;
template<typename T,typename U>
class X<T(U)>//返回值是T的函数,参数可以是另一个模板参数
{
};
template<typename T, typename U>
class X<U T::*>//T内部的U类型的成员指针
{
};
template<typename T, typename U>
class X<pair<T,U>>//T作为pair的一个成员类型
{
};
void main()
{
}
至于少的情况,前面不远的例子(局部特化的非类型实参......)中可以见到!
但是需要注意的是:无论怎么多还是少,局部特化的实参和基本模板的相应参数的数量必须是相同的:
错误的示例:
template<typename T>
class X;
template<typename T, typename U>
class X<T,U>{
};
总结:特化在模板的设计中很重要,是某些功能实现的桥梁!