• (十六)模板与泛型编程


    • 面向对象编程(OOP) :能处理类型在程序运行之前都未知的情况
    • 泛型编程:在编译时就能获知类型
    • 模板:是泛型编程的基础,模板是创建类或函数的公式。

    1定义模板

    1.1函数模板

    • 可以定义一个通用函数模板,通过定义类型参数,针对特定类型生成对应函数版本。当调用函数模板,编译器会用模板参数实例化一个特定版本的函数。

    template //模板定义中,模板参数列表不能为空

    • 类型参数分为两种。
    • 一种是模板类型参数,类型参数前要有关键字classtypename,在模板参数列表中,二者没有区别。
    • 另一种是非类型模板参数,非类型参数表示一个值而非一个类型。通过特定类型名,如unsigned来指定,做两个长度不同的数组比较。非类型参数可以是一个整型(实参是常量表达式),或者是一个指向对象或函数类型的指针或(左值)引用(实参要具有静态生存期)。
    • 函数模板可以声明为inline/constexpr
    template<typename T>inline T min(const T& t1, const T& t2) { return t1 < t2 ? t1 : t2; }
    
    • 1
    • 模板中函数参数是const的引用,保证函数可以用于不能拷贝的类型;
    • 函数内部尽可能和使用标准库模板函数对象(P510)
    • 对于模板编译:

    当实例出模板的特定版本时,编译器才会生成代码。
    考虑到生成实例化版本时,编译器需要掌握函数模板或类模板成员函数的定义,模板的声明和定义都会放到头文件中。

    1.2类模板

    • 用来生成类的蓝图。由于编译器不能为类模板推断模板参数类型,必须在模板名后的尖括号中提供额外信息。

    1.2.1定义

    • 以关键字template开始,后跟模板参数列表
    • 类模板成员函数可以在模板内部(隐式内联),也可以在模板外部。定义在外部要以关键字template开始。
    • 在类代码内可以简化模板类名的使用(比如从hhdyhhdy
    template<typename T>
    class hhdyptr {
    	hhdyptr& operator++(); //类内可以简化模板类名
    	};
    template<typename T>
    hhdyptr<T>& hhdyptr<T>::operator++() {//类外不可以简化模板类名
    	hhdyptr hh = *this;//类内可以简化模板类名
    	++* this;
    	return hh;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    //以关键字template开始,后跟模板参数列表
    template <typename T>
    class hhdy {
    public:
    	typedef T value_type;
    	typedef typename std::vector<T>::size_type size_type;
    	//构造
    	hhdy():data(std::make_shared<std::vector<T>>()){}
    	hhdy(std::initializer_list<T> il):data(std::make_shared<std::vector<T>>(il)){}
    
    	//元素数目
    	size_type size() const{
    		return data->size();
    	}
    	bool empty() const {
    		return data->empty();
    	}
    	//增删元素
    	void push_back(const T& t); 
    	void push_back(T&& t) {
    		data->push_back(t);
    	}
    	void pop_back() {
    		data->pop_back();
    	}
    	//元素访问
    	T& back() {
    		return data->back();
    	}
    	T& operator[](size_type idx) {
    		if (check(idx,"out of range")) {
    			return ( * data)[idx];
    		}
    		return back();
    	}
    
    private:
    	std::shared_ptr<std::vector<T>> data;
    
    	bool check(size_type i, const std::string& msg) const {
    		try {
    			if (i >= size())
    				throw std::out_of_range(msg);
    			return 1;
    		}
    		catch (std::out_of_range& err) {
    			std::cerr << err.what() << std::endl;
    			return 0;
    		}
    	}
    };
    
    template<typename T>
    void hhdy<T>::push_back(const T& t) {
    	data->push_back(t);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    1.2.2实例化

    • 类模板实例化
    • 一个类模板每个实例都形成一个独立的类。没有其他hhdy类的特殊访问权限。
    hhdy<int> hh = { 1,2,3,4 }; //需要提供元素类型
    
    • 1
    • 类模板成员函数实例化
    • 成员函数只有在被用到时才会实例化
    	hh.push_back(5);
    
    	std::cout << hh.size() << std::endl;
    	std::cout << hh[6]<< std::endl;
    
    • 1
    • 2
    • 3
    • 4

    1.2.3类模板与友元

    • 对于类与函数
    • 类与友元各自是否是模板相互无关。
      • 一个类可以将另一个模板的每个实例都声明自己的友元,或者限定特定的实例为友元。
    • 在声明特定实例为友元前,需要进行友元类和友元函数的前置声明;将每个实例都声明为友元时不需要前置声明,此时友元声明中要使用与类模板本省不同的模板参数。
    • 对于自己的类型参数,可以将模板参数声明为友元
    //声明特定实例为友元前需要前置声明
    template <typename T>class hhdy;
    template <typename T>class xx;
    template <typename T>
    class xx{
     friend class hhdy<int>;
    };
    //将每个实例都声明为友元时不需要,友元声明中要使用与类模板本省不同的模板参数。
    template <typename T>
    class xx{
     friend template <typename C> friend class hhdy;
    };
    //可以将模板参数声明为友元。
    template <typename T>
    class xx{
    friend T;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.2.4类模板类型别名

    • 可以定义一个typedef引用实例化类:
      typedef hhdy ihhdy;
    • 可以通过using为类模板定义类型别名,可以固定一个或多个模板参数:
    template<typename T> using  twin = pair<T,T>;
    twin<int> pairs; //pair
    template<typename T> using  partNo = pair<T,unsigned>;
    partNo<int> xx; //pair
    
    • 1
    • 2
    • 3
    • 4

    1.2.5类模板静态函数

    • 类模板可以声明static成员,对于任意给定类型X,FOO类型对象共享静态对象和函数。
    class xx {
    public:
    	xx() = default;
    	xx(T t):num(t){}
    	static void prt() {
    		std::cout << "999" << std::endl;
    	}
    	T num;
    	static std::size_t ctr;
    };
    template<typename T>size_t xx<T>::ctr = 0;  //将static数据成员也定义为模板
    
    int main(){
    	xx<int>  xx1,xx2; //共享static成员,实例化xx类及内部静态成员
    	xx2.prt();
    	xx<int>::prt();//通过类模板访问要说明类的类型参数
    	std::cout << xx<int>::ctr << std::endl;
    	std::cout << xx2.ctr << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.3模板参数

    • 作用域:模板参数明可用范围实在其声明之后,至模板声明或定义结束之前。模板参数会隐藏外层作用域中声明的相同名字。模板内不能重用模板参数名。
    • 声明:声明必须包含模板参数,声明与定义的模板参数名不必相同。但必须有相同数量和种类的参数。
    • 类型成员:作用域运算符(::)可以访问static成员和类型成员。对于模板类型参数T,当遇到T::size_type*p时,会不知道size_type是类型成员还是static成员,要通过关键字typename加以区分。

    此时有两种解释:

    • 1.T::size_type 类型的p指针->typename T::size_type *p
    • 2.静态成员size_type乘以p->T::size_type*p
    • 默认模板实参:可以为函数和类模板提供默认实参:
    //函数
    template<typename T,typename F = std::less<T>>
    bool compare(const T& v1, const T& v2, F f = F()) {
    	return f(v1, v2);
    }
    //类模板
    template<typename T = int>class A {
    };
    A<> b; //默认A
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    template<typename U>class A;
    template<typename T = int>class A {
    public:
    	typedef  size_t A_type;
    	T calc(const T&, const T&);
    	//不是典型例子
    	typename A<T>::A_type capacity() {
    		return A<T>::cap;
    	}
    private:
    	static size_t cap;
    	T t;
    };
    template<typename T> size_t A<T>::cap = 100;
    template<typename T> T A<T>::calc(const T& a, const T& b) {
    	//double T; //报错,重声明模板参数名称
    	return a + b;
    }
    int main() {
    	A<int> a;
    	A<> b;
    	std::cout << a.calc(1,2) << std::endl;
    	std::cout << a.capacity() << std::endl;
    	std::cout << compare(3,2) << std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    1.4成员模板

    • 类可以包含本身是模板的成员函数。
    • 成员模板不能是虚函数。

    1.4.1非模板类的成员模板

    //默认删除器
    class DebugDelete {
    public:
    	DebugDelete(std::ostream&s = std::cerr):os(s){}
    	template<typename T>void operator()(T* p)const { //成员函数模板
    		os << "delete unique_ptr" << std::endl;
    		delete p;
    	}
    private:
    	std::ostream& os;
    };
    
    int main() {
    	//用来删除堆上指针
    	double* p = new double;
    	DebugDelete d;
    	d(p);
    	int* ip = new int;
    	DebugDelete()(ip);
    	//可以用做unique_ptr删除器,unique_ptr的析构函数会调用DebugDelete的调用运算符进行析构。
    	std::unique_ptr<int, DebugDelete> p1(new int, DebugDelete());
    	*p1 = 2;
    	std::cout << *p1 << std::endl;
    	return 0; //unique_ptr在函数结束后会进行析构并调用DebugDelete()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    1.4.2类模板的成员模板

    • 类和成员各自有自己独立的模板参数
    template<typename T>
    class A {
    public:
    	template<typename U>
    	U calc(const U&, const U&);
    };
    //类模板外定义成员模板
    template<typename T>
    template<typename U>
    U A<T>::calc(const U& a, const U& b) {
    	return a + b;
    }
    intmain(){
    	A<int> a;
    	std::cout << a.calc(1,2) << std::endl; //3,推断为calc
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.5模板实例化控制

    • 通过显式实例化来避免实例化相同模板的额外开销
    extern template declaration; //实例化声明
    template declaration; //实例化定义
    
    • 1
    • 2

    extern模板声明表示在程序其他位置存在实例化的非extern声明或定义,它不会在本文件中生成实例化代码。
    对于一个给定实例化版本,可以有多个extern声明,但必须只有一个定义。
    extern声明必须出现在使用此实例化版本代码之前。
    两个程序必须链接在一起

    //templatebuild.cc,函数非extern声明,进行实例化
    template int compare(const int&,const int&);  
    template class Blob<string>;
    //Application.cc,函数extern声明,不实例化
    extern template class Blob<string>;
    extern template int compare(const int&,const int&);
    Blob<string> sa1,sa2;
    int i = compare(1,2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.6 效率与灵活性

    以智能指针里shared_ptr和unique_ptr为例:

    区别shared_ptrunique_ptr
    管理保存指针的策略给予共享指针所有权的能力独占指针
    重载默认删除器的方式创建或reset指针时传递给它一个可调用对象必须以显式模板实参的形式提供删除器类型
    删除器绑定方法运行时绑定,能直接访问,保存一个指针或封装了指针的类编译时绑定,删除器直接绑定在unique_ptr对象中
    工作del?del(p):delete p;del(p);
    优势运行时绑定,重载删除器更方便编译时绑定,避免间接调用删除器的运行时开销

    2 模板实参推断

    模板实参推断:从函数实参来确定模板实参的过程被称为模板实参推断。

    2.1类型转换

    2.1.1 隐式转换

    • 对于类型转换,除以下转换外,其他转换无法应用于函数模板:
    • 顶层cosnt无论是在形参还是实参中都会被忽略;
    • 可以将一个非const对象的引用或指针传递给一个const的引用或形参。
    • 可以将数组和函数转化为数组指针和函数指针。
    • 编译器通常不对实参进行类型转换,而是生成一个新的模板。
    • 如果希望对函数实参进行正常类型转换,可以将函数模板定义为两个类型参数:在使用时要求实参类型必须兼容。
    • 对于不是模板参数的函数参数会进行正常的类型转换
    template<typename T>T fobj(T t1, T t2) { //拷贝模式
    	return t1 ;
    }
    template<typename T>T fref(const T& t1, const T& t2) { 
    	return t1 ;
    }
    template<typename Ttypename K>bool fcom(T t1, K k1) { //拷贝模式
    	return t1>k1 ;
    }
    template<typename T>bool fcompare(T t1, T t2) { //拷贝模式
    	return t1>t2 ;
    }
    
    int main() {
    	std::string s1("a value");
    	const std::string s2("another value");
    	std::cout<<fobj(s1, s2)<<std::endl; 
    	std::cout << fref(s1, s2) << std::endl; //s1可以转化为const形式
    	int arr1[4] = { 1,2,3,4 };
    	int arr2[6] = { 1,2,3,4,5,6 };
    	std::cout << fobj(arr1, arr2) << std::endl; //输入变为数组指针
    	//std::cout << fref(arr1, arr2) << std::endl; //错误,数组类型不匹配
    	long lng = 1024;
    	fcom(lng,1024); //正确
    	fcompare(lng,1024); //错误,不知道转换为还是
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.1.2 显式转换

    • 当编译器无法推断出模板实参类型时,允许用户控制模板实例化。
    • 例1:函数返回类型与参数类型列表中任何类型都不相同时。
    template<typename A,typename B,typename C> A sum(B b,C c);
    
    • 1
    • 此时需要为类型A提供显式模板实参
    auto val3 = sum<long long>(i,lng);
    
    • 1
    • 显式模板实参按从左到右的顺序与对应模板参数匹配
    template<typename A,typename B,typename C> C sum(A a,B b);
    auto val = sum<int>(i,lng); //错误,不能推断A类型
    auto val2 = sum<int,long,long long>(i,lng); //正确
    
    • 1
    • 2
    • 3
    • 例2:显式指定实参的类型转换
    template<typename T>bool fcompare(T t1, T t2) { //拷贝模式
    	return t1>t2 ;
    }
    fcompare<int> (i,lng); //实例化fcompare
    
    • 1
    • 2
    • 3
    • 4

    2.1.3 尾置返回类型

    • 对于一些特殊情况,需要尾置返回类型。比如处理迭代器并返回迭代器类型时,由于迭代器类型在参数列表中,所以可以使用尾置返回类型返回。
    template<typename It>
    auto fcn(It beg,It end) -> decltype(*beg)  //使用尾置返回类型
    {
    	return *beg;
    }
    //使用
    std::vector<int> vi = { 1,2,3,4 };
    auto& i = kfcn(vi.begin(), vi.end()); //int&
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 进行类型转换的标准库模板类:希望编写一个函数,返回的是元素值而不是引用。进行迭代器操作时,生成的都是元素的引用。此时可以使用标准库类型转换
    • 头文件:#include
    //使用typename告知编译器type表示一个类型
    template<typename It>
    auto kfcn(It beg,It end) ->
     typename std::remove_reference<decltype(*beg)>::type  
    {
    	return *beg;
    }
    //返回元素值拷贝
    auto i = kfcn(vi.begin(), vi.end());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 标准库模板中有一个名为type的public成员,表示一个类型。当不需要转换时,该类型时模板参数类型本身,否则是转换后的模板类型。
    • 包括:解引用、加底层const、加左值引用、加右值引用、增删指针、变有符号/无符号、移维度等
      在这里插入图片描述

    2.1.4 函数指针

    • 用一个函数模板初始化函数指针或为函数指针赋值时,编译器使用指针类型推断模板实参。
    template<typename T> int kcompare(const T&, const T&);
    //可以用函数指针指向kcompare的一个实例
    int (*pf)(const int&, const int&) = kcompare;
    //对于存在多个重载的函数指针,需要调用特定实例版本的函数指针,需要用显式模板调用
    void func(int(*)(const string&,const string&));
    void func(int(*)(const int&,const int&));
    func(compare<int>);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.1.5引用

    2.1.5.1 左值引用
    • 对于普通左值引用,只能传递给它一个左值
    • 对于const T&类型,可以传递给它任何类型的实参(对象、临时对象或字面值常量等)。
    2.1.5.2 右值引用
    • 对于右值引用(T&&),可以传递任意类型实参。左值变为左值引用,右值变为右值引用,引用会进行引用折叠。
    2.1.5.3引用折叠的两个例外规则
    • 引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数
    • 1.将一个左值传递给函数的右值引用参数,且此右值引用指向模板类型参数时,该实参为左值引用类型。
    template<typename T> void A(T&&);
    int i = 64;
    A(i); //函数实参类型变为int&
    const int ci = 5;
    A(ci);//函数实参类型变为const int&
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 2.间接创造一个引用的引用,这些引用会形成折叠,成为普通的左值引用类型。对于右值引用的右值引用,会折叠为右值引用。
    X& &, X& &&, X&& &都折叠成X&
    X&& &&折叠成X&&
    
    • 1
    • 2

    2.1.6 std::move(右值引用的函数模板案例)

    • std::move定义:
    //不论接收的是什么类型,返回一个右值引用
    template<typename T>
    typename std::remove_reference<T>::type&& move(T&& t) {
    	return static_cast<std::remove_reference<T>::type&&>(t);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    template <class _Ty>
    _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
        return static_cast<remove_reference_t<_Ty>&&>(_Arg); //去引用后加右值引用,返回一个右值
    }
    
    • 1
    • 2
    • 3
    • 4

    static_cast 强制将对象类型转换为type,也可以显式的将一个左值转换为右值引用。

    2.1.7 转发

    • 函数转发:函数需要将其一个或多个实参连同类型不变地转发给其他函数,转发时保持被转发函数的所有性质。
    //翻转函数
    template<typename F,typename T1,typename T2>
    void flip(F f, T1&& t1, T2&& t2) {
    	std::cout<<f(std::forward<T2>(t2), std::forward<T1>(t1)) << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 形参都是右值引用形式:实参既可以是左值又可以是右值,并且进行了参数绑定
    • std::forward:头文件#includestd::forward返回实参类型的右值引用
    • std::forward(arg); //使用Type作为forward的显式模板实参类型,实际类型从arg中推断。arg是右值时,Type是普通类型,forward返回Type&&;当arg是左值,Type是左值引用,返回类型是指向左值类型的右值引用,折叠后为左值引用。
    template <class _Ty>
    _NODISCARD constexpr _Ty&& forward(
        remove_reference_t<_Ty>& _Arg) noexcept { //  forward an lvalue as either an lvalue or an rvalue
        return static_cast<_Ty&&>(_Arg); //返回右值引用(可能折叠)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    template<typename K>K ff(K1&& t1, K2& t2) {
    	return t1;
    }
    template<typename F,typename T1,typename T2>
    void flip(F f, T1&& t1, T2&& t2) {
    	//ff(t2, t1); //错误,因为t2作为右值表达式是个左值,不能传入右值,需要将t2从右值表达式转换为右值再传入ff函数,所以要使用std::forward函数。
    	std::cout<<f(std::forward<T2>(t2), std::forward<T1>(t1)) << std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3函数重载与模板

    • 函数模板可以被另一个模板或一个普通非模板函数重载
    • 当有多个重载模板对一个调用提供好匹配时,选择最特例化的版本;
    • 对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,选择非模板版本;
    • 定义任何函数之前,要声明所有重载的函数版本。
    #include
    #include
    #include
    //1
    template<typename T>std::string debug_rep(const T& t) {
    	std::ostringstream ret;
    	ret << t;
    	return ret.str();
    }
    //2
    template<typename T>std::string debug_rep(T* p) {
    	std::ostringstream ret;
    	ret <<"poiter: " << p;
    	if (p) 
    		ret << " " << debug_rep(*p);
    	else
    		ret << " null pointer";	
    	return ret.str();
    }
    //3
    std::string debug_rep(const std::string &s) {
    	return '"' + s + '"';
    }
    //4
    std::string debug_rep(char* p) {
    	std::cout << "char" << std::endl;
    	return debug_rep(std::string(p));
    }
    //5
    std::string debug_rep(const char* p) {
    	std::cout << "const char" << std::endl;
    	return debug_rep(std::string(p));
    }
    
    //声明
    template<typename T>std::string debug_rep(const T& t);
    template<typename T>std::string debug_rep(T* p);
    std::string debug_rep(const std::string& s);
    std::string debug_rep(char* p);
    std::string debug_rep(const char* p);
    
    int  main() {
    	std::string s("hi");
    	std::string* sp = &s;
    	char strs[] = "hello world";
    	std::cout << debug_rep(s) << std::endl;//可匹配1/3,最终匹配3
    	std::cout << debug_rep(sp) << std::endl;//可匹配1/2,匹配到第2个版本
    	std::cout << debug_rep(strs) << std::endl;//会匹配到2,但不对,要进行精确匹配(增加4)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    4可变参数模板

    4.1可变参数模板

    • 可以使用initializer_list定义一个接受可变数目且具有相同类型的实参的函数,要想接收可变数目且实参类型不同,需采用可变参数模板。
    • 可变参数模板是接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包。分为模板参数包和函数参数包两种。
    • 用一个省略号来指出一个模板参数或函数参数表示一个包。
    • 可变参数函数通常采用递归形式处理实参,为了种植地柜,还需要要定义一个非可变参数形式的print函数。
    • 参数包可以进行大小获取(sizeof…)以及包扩展
    #include
    #include
    #include
    
    template<typename T,typename... Args>
    void getsize(const T& t, const Args& ... rest) {
    	std::cout << sizeof...(Args) << std::endl; //3,返回参数包中参数数目
    	std::cout << sizeof...(rest) << std::endl;//3
    }
    
    //需要定义/声明递归结束时的非可变参数版本防止无限递归
    template<typename T> std::ostream& print(std::ostream& os, const T& t) {
    	return os << t;
    }
    template<typename T,typename... Args>  //扩展Args,扩展模板参数包
    std::ostream& print(std::ostream& os,const T& t, const Args&... rest) {  //扩展rest,扩展出一个有包种元素组成的,逗号分隔的列表,是函数调用生成实参列表
    	if constexpr (sizeof...(rest) == 0)  //c++17语法,按条件编译,有这一条就不需要非可变参数版本了
    		return os << t;
    	else {
    		os << t << ", ";
    		return print(os, rest...);
    	}
    }
    int main() {
    	int i = 0;
    	double d = 3.14;
    	std::string s = "hhdy";
    	getsize(std::cout,i, d, 42, s);
    	print(std::cout,i, d, 42, s)<<std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    4.2转发参数包

    • 组合使用可变参数模板和forward机制,实现实参不变的传给其他函数
    • 例:emplace_back:在容器内存空间中直接构造一个函数,参数移动构造,是参数列表右值引用
    class hhdy {
    public:
    	friend std::ostream& print_hh(std::ostream& os, const hhdy& hh);
    	hhdy(int n = 0,std::string str = "null"):i(n),s(str){}
    private:
    	int i;
    	std::string s;
    };
    std::ostream& print_hh(std::ostream& os,const hhdy& hh) {
    	os << hh.s << " " << hh.i;
    	return os;
    }
    class hhdy_vec {
    public:
    	friend std::ostream& print_hhv(std::ostream& os, const hhdy_vec& hhv);
    	hhdy_vec(){ std::vector<hhdy> vh; }
    	hhdy_vec(std::vector<hhdy> vii):vh(vii){}
    
    	template<class... Args>
    	void emplace_back(Args&&... args) {
    		vh.emplace_back( std::forward<Args>(args)...);
    	}
    private:
    	std::vector<hhdy> vh;
    
    };
    std::ostream& print_hhv(std::ostream& os, const hhdy_vec& hhv) {
    	for (auto hh : hhv.vh) {
    		print_hh(os,hh)<<std::endl;
    	}
    	return os;
    }
    int main() {
    	hhdy_vec hhv;
    	hhv.emplace_back(60, "xx");
    	print_hhv(std::cout,hhv);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    5模板特例化

    5.1模板特例化

    • 当不能或不希望使用模板版本时,可以定义类或者函数模板的一个特例化版本。
    • 特例化版本是模板的一个独立的定义

    5.2函数模板特例化

    • int类型特例化版本必须为模板中美每个模板参数提供实参
    • 特例化时使用template<>,<>表示会为原模板所有模板参数提供实参
    • 特例化版本的本质是一个实例,当同时存在可用的非模板版本、特例化版本和函数模板版本时,非模板版本>特例化版本>函数模板版本
    • 模板及特例化版本应声明在同一个头文件中,所有同名模板声明应放在前面,然后是模板特例化版本。
    template<typename T>
    void test(T&& i) {
    	std::cout <<"函数模板型版本test函数:" << i << std::endl;
    }
    template<> 
    void test(int&& i) {
    	std::cout << "int类型特例化版本test函数:" << i << std::endl;
    }
    template<size_t n, size_t m> 
    int getcom(const char(&t1)[n] , const char(&t2)[m]) {  //注意模板参数写法
    	std::cout << "size_t类型特例化版本test函数:";
    	return strcmp(t1, t2);
    }
    int main() {
    	test(10);
    	test("hh");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5.3类模板特例化

    5.3.1类模板特例化

    • eg:为标准库hash模板定义一个特例化版本(自定义类对象版本),使其能存储在无序容器中。
    • 特例化hash类包括:
    • 一个重载的调用运算符,接受一个容器关键字类型的对象,返回size_t。
    • 两个类型成员,result_type以及argyment_type,调用运算符的返回类型和参数类型
    • 默认构造函数和拷贝赋值运算符
    • 为了将其定义在hash所在std命名空间内,需要打开std命名空间:
    namespace std{
    } //关闭命名空间,没有分号
    
    • 1
    • 2
    • 使用时hash值需要与自定义类模板的operator==运算符兼容
    • 在自定义类模板的有文件中定义特例化版本
    template<class T>class std::hash; //友元声明前
    class hhdy {
    public:
    	friend struct std::hash<hhdy>;  //声明为友元以访问对象
    	hhdy(int i, std::string s) :height(i), date(s) {}
    private:
    	int height;
    	std::string date;
    };
    namespace std {
    	//参考struct std::hash;
    	//template <>
    	//struct hash {
    	//	_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef double _ARGUMENT_TYPE_NAME;
    	//	_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef size_t _RESULT_TYPE_NAME;
    	//	_NODISCARD size_t operator()(const double _Keyval) const noexcept {
    	//		return _Hash_representation(_Keyval == 0.0 ? 0.0 : _Keyval); // map -0 to 0
    	//	}
    	//};
    	template<>
    	struct hash<hhdy> {
    		typedef size_t result_type;
    		typedef hhdy argument_type;
    		size_t operator()(const hhdy& hh)const {
    			return hash<int>()(hh.height) ^ hash<std::string>()(hh.date);
    		};
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    5.3.2类模板部分特例化

    • 本质:生成一个特例化类模板
    • 部分特例化版本的模板参数列表是原始模板的参数列表的一个自己或一个特例化版本。
    template<class T> struct remove_reference {
       typedef T type;
    };
    template<class T>struct remove_reference<T&>{
       typedef T type;
    };
    template<class T>struct remove_reference<T&&> {
       typedef T type;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.3.3特例化类成员

    • 可以特例化特定成员函数而不是整个模板
    template<typename T>struct Foo{
    	Foo(const T &t = T()):mem(t){}
    	void Bar()
    	T mem;
    }
    template<>
    void Foo<int>::Bar{} //当为Foo类时,成员函数Bar函数使用特例化版本进行不同操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    0704~springboot整合ES&RabbitMQ
    Linux线程的概念
    联想Thinkbook Ubuntu18.04 安装nvidia显卡驱动
    研究生如何选择适合自己的导师
    opcua pubsub 消息的wireshark解码
    离散信号的卷积与相关
    9.13号作业
    OpenHarmony 社区运营报告(2023 年 10 月)
    OpenCV实战——使用YOLO进行目标检测
    易点易动设备管理系统:提升生产企业设备保养效率的利器
  • 原文地址:https://blog.csdn.net/qq_40212968/article/details/126612351