[Function Template]
一.
函数模板的实例化(函数模板应用)
1.
模板实参推演
:用函数实参的类型来决定模板实参的类型和值的过程。
模板实参推演的通用算法如下:
(1)
依次检查每个函数实参以确定在每个函数参数的类型中出现的模板参数
(2)
如果找到模板参数则通过检查函数实参的类型推演出相应的模板实参
(3)
函数参数类型和函数实参类型不必完全匹配下列类型转换可以被应用在函数实参上以便将其转换成相应的函数参数的类型:
.
左值转换
.
限定修饰转换
.
从派生类到基类类型的转换假定函数参数具有形式T<args> T<args>&或T<args>*则这里的参数表args 至少含有一个模板参数
(4)
如果在多个函数参数中找到同一个模板参数则从每个相应函数实参推演出的模板实参必须相同
2.
显式(指定)函数模板实参
template <typename Type>
Type sum( Type op1, int op2 ) { /* ... */ }
//
显式(指定)模板实参
int op = 5;
int * p = sum<int *>(&op ,2);
3.
函数模板显式实例化声明
template <typename Type>
Type sum( Type op1, int op2 ) { /* ... */ }
//
显式实例化声明
template int* sum< int* >( int*, int );
二.函数模板的编译模式:
1.
包含编译模式
2.
分离编译模式
//model2.h
模板声明
template<typename Type>
Type min(Type t1,Type t2);
//model2.c
模板定义
export template<typename Type>
Type min(Type t1,Type t2)
{
return t1<t2?t1:t2;
}
并不是所有的编译器都支持分离模式即使支持也未必总能支持得很好支持分离模式需要更复杂的程序设计环境所以它们不能在所有C++编译器实现中提供
三.函数模板的特化(显式、部分显式)
// const char*
显式特化
:
//
覆盖了来自通用模板定义的实例
typedef const char *PCC;
template<>
PCC max< PCC >( PCC s1, PCC s2 )
{
return ( strcmp( s1, s2 ) > 0 ? s1 : s2 );
}
1
.即使函数模板显式特化所指定的函数模板只有声明而没有定义我们仍然可以声明函数模板显式特化在前面的例子中函数模板sum()在被特化之前只是被声明了一下尽管不需要函数定义但是模板声明也还是需要的在名字sum()被特化之前编译器必须知道它是个模板
template <class T1, class T2, class T3>
T1 sum( T2 op1, T3 op2 );
//
显式特化声明
//
错误
: T1
的模板实参不能被推演出来
//
它必须显式指定
template<> double sum( float, float );
// ok: T1
的实参被显式指定
// T2
和
T3
可以从
float
推演出来
template<> double sum<double>( float, float );
// ok:
所有实参都显式指定
template<> int sum<int,char,char>( char , char );
2
.一个程序不能对相同的模板实参集的同一模板同时有一个显式特化和一个实例
//
通用模板定义
template <class T>
T max( T t1, T t2 ) { /* ... */ }
int main() {
// const char* max<const char*>( const char*, const char* )
的实例
//
使用通用模板定义
const char *p = max( "hello", "world" );
cout << " p: " << p << endl;
return 0;
}
//
无效程序
: const char*
显式特化覆盖了通用模板函数
typedef const char *PCC;
template<> PCC max< PCC >( PCC s1, PCC s2 ) { /* ... */ }
3
.显式特化的声明应该被放在头文件max.h 中并在所有使用函数模板max()的程序中包含这个文件
// ---- max.h ----
//
通用模板定义
template <class Type>
Type max( Type t1, Type t2 ) { /* ... */ }
// const char*
模板显式特化的声明
typedef const char *PCC;
template<>
PCC max< PCC >( PCC s1, PCC s2 );
// ---- File1.C ----
#include <iostream>
#include "max.h"
void another();
int main() {
// const char* max<const char*>( const char*, const char* )
的特化
;
const char *p = max( "hello", "world" );
//....
}
四.函数模板的重载
1.
函数模板重载实例
template <typename Type>
Type min( const Array<Type>&, int ); // #1
template <typename Type>
Type min( const Type*, int ); // #2
template <typename Type>
Type min( Type, Type ); // #3
2.
函数模板重载的二义性
template <typename T>
int min5( T, T ) { /* ... */ } // #1
template <typename T, typename U>
int min5( T, U ); // #2
//
错误
:
二义性
:
来自
min5( T, T )
和
min5( T, U )
的两个可能的实例
min5( 1024, i );
min5()
的第二个声明允许两个不同类型的函数实参,但是它没有要求它们一定是不同的,在这种情况下T 和U 都可以是int 型。对于两个实参类型相同的调用,这两个模板声明都可以被实例化,要指明哪个函数模板比较好并且消除调用的二义性的惟一方法是显式指定模板实参。例如
// ok:
从
min5( T U )
实例化
min5<int, int>( 1024, i );
但是在这种情况下,我们其实可以取消重载函数模板,因为min5(T,U)处理的调用集是min5(T,T)处理的超集所以应该只提供min5(T,U)的声明而min5(T,T)应该被删除,因此尽管重载是可能的,但是我们在设计重载函数时仍然必须小心确保重载是必需的
3.
模板更特化
在某些情况下即使对于一个函数调用两个不同的函数模板都可以实例化但是该函数调用仍然可能不是二义的已知sum()的下列两个模板定义下面就是一种情况虽然从这两个函数模板的任一个都可以生成一个实例但是第一个模板定义比较好
template <typename Type>
Type sum( Type*, int ); // #1
template <typename Type>
Type sum( Type, int ); // #2
int ia[1024];
// Type == int ; sum<int>( int*, int ); or
// Type == int*; sum<int*>( int*, int ); ??
int ival1 = sum<int>( ia, 1024 );
真让人吃惊上面的调用居然没有二义性该模板是用第一个模板定义实例化的为该实例选择的模板函数是最特化的most specialized 因此Type 的模板实参是int 而不是int*
一个模板要比另一个更特化两个模板必须有相同的名字相同的参数个数对于不同类型的相应函数参数如上面的T*和T 一个参数必须能接受另一个模板中相应参数能够接受的实参的超集例如对模板sum(Type*,int) 第一个函数参数只能匹配指针类型的实参对于模板sum(Type,int) 第一个函数参数可以匹配指针类型以及任意其他类型的实参,第二个模板接受第一个模极所能够接受的类型的超集。接受更有限的实参集合的模板被称为是更特化的
五.函数模板的重载解析
1.
在一个面向过程的程序中,可能同时存在这如下三种函数形式的混合重载:
(1)
普通函数(与函数模板同名)
(2)
函数模板的显式特化
(3)
通用函数模板
2.
函数调用解析步骤如下:
(1)
为调用列出候选函数集;
(2)
选出可行函数。
3.
实例分析:
template
<class Type> //
函数模板
void
Max(Type t1,Type t2)
{
cout<<"Template instance"<<endl;
}
template
<> //
函数模板特化
void
Max<char>(char t1,char t2)
{
cout<<"Template specification"<<endl;
}
void
Max(double,double) //
普通函数
{
cout<<"Common function"<<endl;
}
int
_tmain(int argc, _TCHAR* argv[])
{
int ival=0;
double dval=0;
float fd=0;
1
、Max(0,ival); //Template instance
2
、Max(0.25,dval); //Common function
3
、Max(0,fd); //Common function
4
、Max(0,'j'); //Common function
return 0;
}
1、
模板实参推演为int类型,函数模板特化不是这种类型,因此函数模板的int型实例是一个候选函数;可以实现int型到double型的自动转换,因此普通函数也是一个候选函数。
因为函数模板的int型实例是精确匹配,所以确定为调用函数模板的int型实例;
2、
模板实参推演为double类型,函数模板特化不是这种类型,因此函数模板的double型实例是一个候选函数;普通函数也是一个候选函数。
因为两个候选函数都是精确匹配,出现二义性,所以去掉函数模板的double型实例,选用普通函数;
3、
因为函数调用实参为int和float两个类型,所以函数模板实参推演失败,更谈不上模板特化了。因为可以实现int和float型到double型的自动转换,所以普通函数成为唯一的候选函数;
4、
同3。
六.模板定义中的名字解析
1.
模板定义中的名字解析分两个步骤进行:
首先、不依赖于模板参数的名字在模板定义时被解析;
其次、依赖于模板参数的名宇在模板被实例化时被解析
事实上函数模板的设计者必须确保为模板定义中用到的所有不依赖于模板参数的名字提供声明如果用在模板定义中的名字不依赖于模板参数并且在定义该模板时没有找到该名字的声明则模板定义是错误的当模板被实例化时编译器就不再考虑这样的错误。
[Class Template]
一.类模板的实例化(类模板应用)
1.
显式(指定)类模板实参
2.
类模板显式实例化声明
二.类模板的特化
1.
全特化