- 泛型代码两个原则:
1 模板中函数参数是const的引用:保证函数可用于不能拷贝的类型
2 函数体只是用<比较运算
- 类型模板参数:
template<typename T> inline bool compare(const T& lhs, const T& rhs)
{
return lhs < rhs;
}
- 非类型模板参数:
template<unsigned N> inline int back(const int (&lhs)[N])
{
return lhs[N - 1];
}
int main()
{
const int a[2]{1, 2};
cout<<back(a);
return 0;
}
1 数组作为非引用参数会被退化为指针,无法推导出N,需要使用引用保持数组类型
注意区别引用的数组:
bool back(int &lhs[2])
//非法
和数组的引用:
bool back(int (&lhs)[2])
2 inline说明符放在模板参数列表之后
- 模板编译:
编译器遇到模板定义时,并不生成代码。只有当实例化出模板的特定版本时,编译器才会生成代码,为了生成一个实例化版本,编译器需要掌握函数模板或类模板的成员函数的定义。因此,模板的头文件包含声明和定义。函数模板和类模板的成员函数定义放在头文件中。
- 类模板
函数模板可以推导参数类型,类模板不可以,需要额外提供信息
template<typename T>
class Blob
{
private:
vector<T> vec;
public:
void func(const T& lhs);
};
template<typename T>
void Blob<T>::func(const T& lhs)
{
vec.push_back(lhs);
}
int main()
{
Blob<int> b;
b.func(2);
return 0;
}
使用类模板类型时必须提供模板参数,
在类模板自己的作用域中,可以直接使用模板名而不提供实参,
在类外定义成员时,直到遇到类名::才算进入类作用域
template<typename T>
Blob<T> Blob<T>::operator++(int lhs)
{
Blob ret = *this;
return ret;
}
- 使用类的类型成员
默认情况下,作用域运算符访问的是名字,不是类型。使用typename显示告诉编译器访问的是类型。
template <typename T>
typename T::value_type top(const T& c)
{
return typename T::value_type();
}
- 模板成员函数:
模板成员函数不能是虚函数
template<typename T>
template<typename It>
Blob <T>::Blob(It b, It e)
{
vec.insert(vec.begin(), b, e);
}
类模板外定义成员模板函数时,必须同时为类和成员模板提供模板参数列表,编译器根据实参推断模板成员函数的参数类型。
- 控制实例化
当两个或者多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数,每个文件都会有改模板的一个实例。
通过显示的实例化避免开销
A文件:
extern template class Blob<string>; //实例化声明
Blob<string> s1;//实例化在其他文件
B文件:
template class Blob<sting>;//实例化定义
将一个实例化声明为extern就表示承诺在程序的其他位置有该实例化的非extern声明(定义)。
与普通实例化不同,实例化定义会实例化所有成员。
普通情况下,类模板的成员函数只有当程序用到它时才会实例化。
- 函数模板显式实参
template<typename T1,typename T2,typename T3>
T1 sum(T2, T3);
auto func = sum<long>(i,j)
特别是当返回类型与函数参数列表中的所有类型不一致时,需要显式指出返回类型。
为了不显式实参,可以使用尾置返回类型
template<typename It>
auto func(It begin, It end)->decltype(*begin)
{
return *begin;
}
解引用返回左值,decltype推断的类型为begin所指元素类型的引用
若只要返回值,不返回引用,可使用类型转换模板进行类型转换