原文出处:https://blog.csdn.net/baishuo8/article/details/84440883
Backto C/C++ Index
函数模板:
模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载(而且函数重载只重载参数,不重载返回值,模板可以将返回值也用T代替)。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
上面两种原型定义的不同之处在关键字class 或 typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样。
但typename另外一个作用为:使用嵌套依赖类型(nested depended name)
例子:
我们可以看到这是一个泛型函数,我们可以看到这个函数的返回值是typename deque<T, Alloc, BufSize>::iterator,这里的typename的作用就是告诉编译器,这个iterator是一个类型,而不是成员变量或是成员函数。
template <class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x){
...
}
// definition
template<class T>
T GetMax(const T& a, const T& b){
return (a > b? a:b);
}
// usage
int main() {
int i = 5, j=6;
float m = 1.3, n = 3.14;
int k = GetMax<int>(i, j);
float l = GetMax<float>(m,n);
...
}
在第一行声明中,我们已经生成了一个通用数据类型的模板,叫做GenericType。因此在其后面的函数中,GenericType 成为一个有效的数据类型
类模板:
类模板只是函数模板的一个扩展,即含有用了函数模板的成员方法或用了含有类型模板的类成员。其实,也没有什么函数模板,我们的模板只是类型模板,用在函数里,这个函数就变成了"函数模板",用在类里,这个类就变成了"类模板"。
// class templates
#include <iostream.h>
template <class T> class pair {
T value1, value2;
public:
pair (T first, T second) {
value1=first;
value2=second;
}
T getmax ();
};
template <class T>
T pair::getmax (){
T retval;
retval = value1>value2? value1 : value2;
return retval;
}
int main () {
pair myobject (100, 75);
cout << myobject.getmax();
return 0;
}
注意成员函数getmax 是怎样开始定义的:
template <class T>
T pair::getmax ()
所有写 T 的地方都是必需的,每次你定义模板类的成员函数的时候都需要遵循类似的格式(这里第二个T表示函数返回值的类型,这个根据需要可能会有变化)。
模板特殊化(Template specialization)
https://blog.csdn.net/q1007729991/article/details/38510295(全特化和偏特化)
模板的特殊化是当模板中的pattern有确定的类型时,模板有一个具体的实现。例如假设我们的类模板pair 包含一个取模计算(module operation)的函数,而我们希望这个函数只有当对象中存储的数据为整型(int)的时候才能工作,其他时候,我们需要这个函数总是返回0。这可以通过下面的代码来实现:
偏特化只有类模板可以使用,函数模板不行
// Template specialization
#include <iostream.h>
template <class T> class pair {
T value1, value2;
public:
pair (T first, T second){
value1=first;
value2=second;
}
T module () {return 0;}
};
template <>
class pair <int> {
int value1, value2;
public:
pair (int first, int second){
value1=first;
value2=second;
}
int module ();
};
template <>
int pair<int>::module() {
return value1%value2;
}
int main () {
pair <int> myints (100,75);
pair <float> myfloats (100.0,75.0);
cout << myints.module() << '\n';
cout << myfloats.module() << '\n';
return 0;
}
由上面的代码可以看到,模板特殊化由以下格式定义:
template <> class class_name <type>
这个特殊化本身也是模板定义的一部分,因此,我们必须在该定义开头写template <>。而且因为它确实为一个具体类型的特殊定义,通用数据类型在这里不能够使用,所以第一对尖括号<> 内必须为空。在类名称后面,我们必须将这个特殊化中使用的具体数据类型写在尖括号<>中。
当我们特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现(如果你仔细看上面的例子,会发现我们不得不在特殊化的定义中包含它自己的构造函数 constructor,虽然它与通用模板中的构造函数是一样的)。这样做的原因就是特殊化不会继承通用模板的任何一个成员。
模板的参数值(Parameter values for templates)
除了模板参数前面跟关键字class 或 typename 表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基本数据类型的。例如,下面的例子定义了一个用来存储数组的类模板:
// array template
#include <iostream.h>
template <class T, int N>
class array {
T memblock [N];
public:
void setmember (int x, T value);
T getmember (int x);
};
template <class T, int N>
void array<T,N>::setmember (int x, T value) {
memblock[x]=value;
}
template <class T, int N>
T array<T,N>::getmember (int x) {
return memblock[x];
}
int main () {
array <int,5> myints;
array <float,5> myfloats;
myints.setmember (0,100);
myfloats.setmember (3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
我们也可以为模板参数设置默认值,就像为函数参数设置默认值一样。
下面是一些模板定义的例子:
template <class T> // 最常用的:一个class 参数。
template <class T, class U> // 两个class 参数。
template <class T, int N> // 一个class 和一个整数。
template <class T = char> // 有一个默认值。
template <int Tfunc (int)> // 参数为一个函数。
非类型模板
https://blog.csdn.net/lanchunhui/article/details/49634077
非类型模板参看,顾名思义,模板参数不限定于类型,普通值也可作为模板参数。在基于类型的模板中,模板实例化时所依赖的是某一类型的模板参数,你定义了一些模板参数(template<typename T>)未加确定的代码,直到模板被实例化这些参数细节才真正被确定。而非类型模板参数,面对的未加确定的参数细节是指(value),而非类型。当要使用基于值的模板时,你必须显式地指定这些值,模板方可被实例化。
2.注意:
2.1 、非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。
2.2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
2.3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
2.4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
2.5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
2.6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
2.7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
2.8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
2.9 、非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。
2.10、 非类型模板形参的形参和实参间所允许的转换
1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换
2、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
3、提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int 的提升转换
4、整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
5、常规转换。
3.典型应用:
https://blog.csdn.net/u012999985/article/details/50780311
典型应用就是数组长度定义,众所周知int a[N]这种语句中N必须是一个编译期常量否则无法通过编译,要实现定义不同长度的数组,就可以使用template<int T> void foo(){double a[T]}这样的写法,调用foo时必须传递一个编译期常量如10,如foo<10>(),即可在函数foo中创建一个长度为10的double数组。
模板与多文件工程 (Templates and multiple-file projects)
从编译器的角度来看,模板不同于一般的函数或类。它们在需要时才被编译(compiled on demand),也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。
当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。在这种情况下,通常接口(interface)和实现(implementation)是分开的。用一个函数库做例子,接口通常包括所有能被调用的函数的原型定义。它们通常被定义在以.h 为扩展名的头文件 (header file) 中;而实现 (函数的定义) 则在独立的C++代码文件中。
模板这种类似宏(macro-like) 的功能,对多文件工程有一定的限制:函数或类模板的实现 (定义) 必须与原型声明在同一个文件中。也就是说我们不能再 将接口(interface)存储在单独的头文件中,而必须将接口和实现放在使用模板的同一个文件中。
回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h) ,取而代之,我们应该生成一个模板文件(template file),将函数模板的接口和实现 都放在这个文件中 (这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误 (linkage errors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。
Details
D01:声明和实现必须在一起让编译器看到
就像 inline
关键字一样. 但是为了隔离 declaration
和 definitions
, 有各种方式.
- 放在单独的template 文件里, 不隔离.
- template function 就写在
.h
文件里, 不隔离 - template class 隔离, 但是指明模板 T 的可能性
// .h template<typename T> class foo { public: void bar(const T &t); }; //.cpp template <class T> void foo<T>::bar(const T &t) { } // Explicit template instantiation template class foo<int>;
Ref
- C++ Templates: 总结的很完备了
- Storing C++ template function definitions in a .CPP file: 文件隔离的各种可能性