模板的特例化
特例化一个模板时,模板参数列表为空,标明将显式提供所有模板实参。需要注意的是一个特例化的函数模板并不是模板重载,本质上是一个实例。
template<>
/*形参a,b分别为一个指向const char对象的const指针的引用*/
const char* const& getMax(const char* const &a, const char* const &a) {
return strcmp(a, b) > 0 ? a:b;
const char *a = "hi", *b = "hello";
cout << getMax(a, b) << endl; //输出hi
可变参函数模板(Variadic Templates)
C++11允许使用数目可变的模板参数,可变数目的参数称为参数包,用...表示,可包含从0到任意个模板参数。
1)包展开示例一:
#include <iostream>
using namespace std;
void fun()
{
}
template <typename T>
void fun(const T& lastarg) {
cout << "deal whih the last: " << lastarg << endl;
}
/*
1) C++11允许使用数目可变的模板参数,可变数目的参数称为参数包,用省略号表示,可包含从0到任意个模板参数。
*/
template <typename T, typename...Types>
/*形参args为模板参数包类型,接受可变数目的实参*/
void fun(const T&firstarg, const Types&...args) {
/*使用sizeof...运算符打印参数包args中参数的个数*/
cout << "first arg: "<< firstarg << " sizeof...(args):" << sizeof...(args) << endl;
/*可以通过递归的方式展开函数模板参数包:第一次处理参数包中的第一个参数,然后用剩余
参数调用自身。为了处理边界条件(包参数rest变为0个,然后若再次回调的话就调用不到本身了),
还需要定义一个同名的处理函数*/
fun(args...);
}
int main() {
fun(1, 1.5, "C++");
return 0;
}
2)包展开示例二:
/*可以通过递归的方式展开函数模板参数包:第一次处理参数包中的第一个参数,然后用剩余
参数调用自身*/
template <typename T, Typename...Args>
ostream& print(ostream &os, const T &t, const Args&...rest) {
os << t << " "; //打印第一个参数
return print(os ,rest...); //递归调用
}
/*为了处理边界条件(包参数rest变为0个,然后若再次回调的话就调用不到本身了),还需要定义
一个处理函数,这里写一个带有非可变参的终止函数模板如下所示*/
template <typename T>
ostream& print(ostream &os, const T &t) {
return os << t; //打印最后一个参数
}
上面print模板函数带有3个参数,但是函数体中递归调用语句只有二个参数,执行过程为:参数包rest中的第一个参数与形参t绑定,剩余的参数组成一个新的参数包,当参数包里面只生剩下一个参数时,非可变参模板与可变参模板都匹配,但非可变参模板更特例化,编译器将首选非可变参数模板,例如:
print(cout, 1, 2,5, "C++") << endl; //输出1 2.5 C++
3)参数包的完美转发
可以组合使用可变参模板和forward函数,实现参数包完美转发:
template <typename...Args>
void fun(Args&&...args) { //foo函数负责将所有的实参不变地传给foo函数
foo(std::forward<Args>(args)...)
}
/*上面的参数扩展模式std::forward<Args>(args)... 相当于std::forward<Ti>(ti)。
其中Ti为参数包中第i个参数ti的类型。例如有如下foo函数和函数调用*/
void foo(const string &s, int &&i) {
cout << s << i << endl;
}
/*该调用将扩展为std::forward<const char*>("abc"), std::forward<int>(42)*/
foo("abc", 22);
类模板
1)成员函数定义
类模板成员函数具有与类模板相同的模板参数,因此如果在类模板之外定义成员函数,则它们必须以关键字template开始,后接与类模板相同的模板参数列表。注意,紧随类名后面的参数列表代表一个实例化的实参列表,每个参数不需要typename/class说明符,例如:
template <typename T, size_t N>
class Array {
T m_ele[N];
public:
Array() { }
/*initializer_list是C++11标准库提供的新类型,支持具有相同类型但数量未知的列表类型。
因此,利用initializer_list类型形参可以实现以列表初始化的方式来构造对象。*/
Array(const std::initializer_list<T>&);
T& operator[] (size_t i);
constexpr size_t size() {return N;}
}
/*下面的构造函数初始化列表中,利用T类型的默认初始化方式(T())初始化m_ele中的每一个元素。*/
template <typename T, size_t N>
Array <T, N>::Array(const std::initializer_list<T> &1):m_ele{T()} {
size_t m = 1.size() < N ? 1.size() : N;
for(size_t i = 0; i < m; ++i) {
m_ele[i] = *(1.begin() + i);
}
}
/*下标运算符函数返回数组m_ele中第i个元素的引用*/
template <typename T, size_t N>
T& Array<T, N>::operator[](size_t i) {
return m_ele[i];
}
2)实例化
/*下面代码在编译时创建另一个实例类Array<int, 5>, 创建对象b时,将指向上面的带形参的
构造函数,其中形参1接受初始化列表{1, 2, 3},其余元素具有默认值0*/
Array<int, 5>b = {1, 2, 3}; //创建一个Array<int, 5>类型对象b
for(int i = 0; i < b.size(); ++i)
cout << b[i] << " "; //输出结构为1 2 3 0 0
另外需要注意:类模板实例化时类模板中的成员函数并没有实例化,成员函数只有在被调用时才会被实例化(即产生真正的成员函数) ,成员虚函数除外。
3)分文件编写的2种解决办法
模板的实例化是由编译器完成的,而不是由链接器完成的,分文件编写的话可能会导致在链接期间找不到对应的实例。因此类模板的分文件编写有二种解决办法。第一种解决方式是直接包含源文件(比如:#include "person.cpp");第二种解决方式是将.h和.cpp中的内容写到一起并将后缀改写为.hpp,这种方法比较常用,但是.hpp后缀只是约定俗称,看到.hpp就知道是类模板,并不是必须的。