✏️

Generic Progamming (Template)

Tags
用于实现泛型编程
给定一个模板(蓝图),可以生成多个基于此模板的实例,即生成一个新的实例

定义模板

类模板

需要显式传入模板参数
模板类成员函数定义:
template <typename> class Blob; template <typename T> class Blob { public: // ... Blob(); Blob (std::initializer_list<T>); private: std::shared_ptr<std::vector<T>> data; }; template <typename T> Blob<T>::Blob() : data(std::make_shared<std::vector<int>>()) {} template<typename T> Blob<T>::Blob(std::initializer_list<T> il) : data(std::make_shared<std::vector<T>>(il)) {}
同样,模板类成员函数若没被实例化则不会被实例化
 
在类的作用域里才能省略一些模板参数,在外就不行:
template <typename T> class BlobPtr { public: // 省略了T BlobPtr& operator++(); }; template <typename T> // 这里不能省 BlobPtr<T>& BlobPtr<T>::operator++() { // ... }
 
模板友元
template <typename> class Pal; template <typename> class C2; class C { // 用类C实例化的Pal是C 的一个友元 friend class Pal<C>; // Pal2 的所有实例都是C的友元,这种情况无须前置声明 template <typename T> friend class Pal2; }; template <typename T> class C2 { // C2的每个实例将相同实例化的Pal声明为友元 friend class Pal<T>; // Pal 的模板声明必须在作用域之内 // Pal2 的所有实例都是C2的每个实例的友元,不需要前置声明 template <typename X> friend class Pal2; // Pal3 是一个非模板类,它是C2 所有实例的友元 friend class Pal3; // 不需要 Pal3的前置声明 // 不需要前置声明operator friend bool operator==(const C2<T>&, const C2<T>&); };
 
模板类型别名
typedef pair<int, int> pii; template<typename T> using twin = pair<T, T> twin<double> area; // 部分模板参数 template <typename T> using partNo = pair<T, unsigned>; partNo<string> books; // books 是一个pair<string, unsigned>

函数模板

  • 模板类型参数
// typename和class在声明模板类型参数时是等价的 template <typename T, class U> int calc (const T&, const U&); // 实例化使用时,不需要把T显示写出来,编译器会进行实参推导 calc(1, 2.2);
  • 非类型模板参数
    • 表示一个值而非类型,此时用类型关键字而非typename / class声明
    • 实参可以是常量表达式整型,也可以是具有静态生命期的指针或引用
// 一种用法是表示数组元素个数,处理同一类型不同元素数量的数组 template<unsigned N, unsigned M> int compare(const char (&p1) [N], const char (&p2) [M]) { return strcmp(p1, p2); } // 实例化的版本:int compare (const char (&p1) [3], const char (&p2) [4]) compare("hi", "mom");
 

成员模板

给非模板类定义一个模板方法,在工具类中会用得比较多
class DebugDelete { public: explicit DebugDelete(std::ostream& _os = std::cerr) : os(_os) {} template <typename T> void operator() (T *p) const { os << "deleting unique_ptr" << std::endl; delete p; // 删除指针 } private: std::ostream& os; }; int* p = new int(10); DebugDelete() (p);
 
模板类也可以定义模板方法:
template <typename T> class Blob { template <typename It> Blob(It b, It e); //... }; template <typename T> // 类的类型参数 template <typename It> // 构造函数的类型参数 Blob<T>::Blob(It b, It e): data (std::make shared<std::vector<T>>(b, e)) { }

模板编译

编译器遇到模板定义时不生成代码,使用时才会生成对应版本的实例(实例化阶段)
所以与函数/类成员函数不同,一般将模板定义放在头文件当中
 

错误报告

编译和使用时不能检查出类型相关的问题,实例化时才能发现,依赖于编译器如何管理实例化,可能在链接时才报告
因此模板程序应该尽量减少对实参类型的要求
 

作用域访问符(::)访问type和static变量

T::size_type * p;
产生二义性:声明指向T::size_type类型的指针还是让静态变量size_type去乘p?
编译器默认为后者,故如果要使用模板类的类型名称则在前加上typename关键字
template <typename T> typename T::value_type top(const T& c) { if (!c.empty()) { return c.back(); } else { return typename T::value_type(); } }
 

显式实例化

extern 关键字修饰模板,表示不会在本文件 (.o) 生成实例化代码
下面展示一种避免多个文件重复生成同一种模板实例的项目结构:
  • 头文件声明并定义模板
  • 源文件声明extern模板
// templateBuild.h #pragma once #include <vector> #include <initializer_list> // 类模板 template <typename T> class Blob { public: Blob(std::initializer_list<T> il); Blob(const Blob&); T& operator[](size_t index); private: std::vector<T> data; }; // 函数模板 template <typename T> int compare(const T& a, const T& b);
// templateBuild.cpp #include "templates.h" // 显式实例化定义 template class Blob<std::string>; // 实例化整个类模板 template int compare<int>(const int&, const int&); // 实例化函数模板 // 类模板成员实现 template <typename T> Blob<T>::Blob(std::initializer_list<T> il) : data(il) {} template <typename T> Blob<T>::Blob(const Blob& src) : data(src.data) {} template <typename T> T& Blob<T>::operator[](size_t index) { return data[index]; } // 函数模板实现 template <typename T> int compare(const T& a, const T& b) { return (a < b) ? -1 : (b < a) ? 1 : 0; }
#include "templates.h" // 显式实例化声明 (extern) extern template class Blob<std::string>; // 类模板声明 extern template int compare<int>(const int&, const int&); // 函数模板声明 int main() { // 使用声明过的实例化 (代码不在本文件生成) Blob<std::string> sa1 = {"Hello", "World"}; // 使用initializer_list Blob<std::string> sa2 = sa1; // 使用拷贝构造函数 // 自动实例化 (无extern声明) Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9}; // 实例化Blob<int>和initializer_list构造函数 Blob<int> a2(a1); // 实例化拷贝构造函数 // 使用声明过的函数实例化 int result = compare(a1[0], a2[0]); // 调用compare<int> return 0; }

泛化与特化

template<typename TK, typename TV> // 泛 template<> class/struct xx<TK, TV> // 全特化 template<typename TV> class/struct xx<bool, TV> // 数量偏特化 template<typename T> class/struct xx<T*> // 范围偏特化 template<T> class/struct xx<const T*>
 
notion image
 

模板实参推断

尾置返回类型

当不知道确切的返回类型,但是知道这个类型与参数的关系时,可以用尾置返回类型:
template <typename It> auto fcn(It beg, It end) -> decltype(*beg) { return *beg; }
 

类型转换标准库

进行类型转换的标准库模板类:定义于<type_traits>
notion image
 

引用折叠、std::move原理、转发