模板与泛型编程

泛型编程是在编译时将类型确定,而OOP是在程序运行之前都不知道类型。

函数模板

有两种模板变量。

方式都是在函数前增加template <模板参数列表>template\ <模板参数列表>

类型参数。用typename来声明。template <typename name1,typename name2,…>

非类型参数。就是一个在模板中使用的常量。类型只能是整型或者引用或指针。定义与普通定义一样,除了在template中。

需要在调用函数的时候可以通过输入数据能够推断出模板参数或者直接显式声明模板参数。

1
2
template<unsigned N,unsigned M>
int compare(const char (&p1)[N],const char (&p2)[M]){}

inline和constexpr都是在template后的。

模板编译

由于模板函数只有在调用的时候才会生成实例,而此时编译器必须知道函数的定义是什么,因此一般模板类的声明和定义放在一起。

类模板

不像函数模板可以推断,必须显式声明。

类的每一个实例会生成一个独立的类。

但是只有当类型不同时才是不同的类,如果是相同的,会发现静态变量是通用的。

而对于成员函数,若是声明在外部,则由于其也有各自对应类型版本,因此前要加template,并且,由于已经是类的一个成员函数,而不同类型版本的类是不同的,因此也要指定类的版本,也就是class.

但即便类模板已经被实例化了,其成员函数也只会在被使用时才会被实例化。

当在类模板中使用自身类型时,就无需再指定版本,也就是模板实参,也就是不用加

而当且仅当类名出现后,才进入了类模板的作用域,因此,返回类型必须提供类型实参。

友元

在声明友元前必须声明类模板,仅需声明即可。

而若是希望两个模板类的友元关系一一对应,那么在声明友元的时候使用相同的模板实参即可,若是希望对应其所有的实例,即所有的版本,那么使用不同的模板实参即可。

别名

可以为模板类类型用using来声明别名,如果要使用T,那么同样需要前加上template…

模板实参

模板实参会隐藏外层同名,但不能复用,也不能在类内复用。

模板实参的名字并没有任何含义,在同一模板类中,成员函数定义时声明模板参数无需与类声明时相同,只需类型与个数相同即可。

模板实参也可以有默认实参,使用方法与普通的函数默认实参一致。

但即便模板实参全有默认参数,实例化时仍然要使用尖括号表示使用模板类,如class<> param.

由于模板类可能并没有实例,当访问类的static变量和类型成员时都使用的是作用域符,因此无法分辨。因此,c++默认访问的是变量,除非前面使用typename来修饰来表示这是类的类型成员。

模板成员函数

普通类和模板类中的成员函数都可以模板化。这里指的模板类中的成员函数模板化是指使用不同的模板实参,如在初始化时使用不同容器提供的实际上类型一致的迭代器。

在类外定义时,先template模板类,后template模板函数。

模板成员函数的模板实参会在调用的时候根据参数进行推断。

控制实例化

当一个模板类在多个文件中被实例化的时候,就会产生多个实例,而此时我们可以通过显式实例化的方式来控制这一点。

实例化声明:extern template declaration

实例化定义:template declaration

在某个特定的文件中使用实例化定义来实例化模板类的一个版本,后当我们需要在其他文件中使用这个实例化时实例化声明一下即可。需要注意的是,实例化定义只能有一个,不然实例化声明并不知道指向的是哪一个实例化。

显式实例化会实例化模板类的所有成员,而不像隐式实例化只当在使用的时候才实例化对应的。

灵活与效率

当某个函数作为参数传入时,调用时需要跳转,但对于编写者很灵活。

当某个函数作为可调用对象作为模板参数时,其将在实例化时就绑定了,虽不灵活,但效率更高。

模板参数判断

除了一般的是什么类型就判断为什么类型以外,一部分还可以进行隐式类型转换。

  1. const转换:忽略顶层const或转为const
  2. 在非引用的情况下将数组转为指针

当使用多模板实参的时候,若都用相同的形参,对实参的要求就有点高了,所以可以在多模板实参的时候使用不同的形参。

当模板形参与普通形参同时出现时,普通形参仍遵循原来的规则(如类型转换)。

显式化指定

当模板形参无法判断出类型时,如函数返回值是一个模板形参时,就必须显式指定。

fun(params)。

但是,需要注意的是,params从左向右一一赋值,因此最好把形参中考前的作为推断不出的形参。

当显式化指定后,该形参就变成了普通形参,遵循原来的规则。

当可以通过auto+decltype推断来获取返回类型时,可以使用尾置返回。

有时候一些类型难以推断,如将一个引用类型转为一个普通类型,此时可以使用std的<type_traits>的类型转换模板。

使用方法,typename fun::type variable。

当且仅当可以转换的时候才会进行,否则返回原类型。

模板函数不能直接作为函数指针传入重载函数中,会发生二义性的问题。必须先行通过函数指针的类型来间接确定或fun来直接确定。

推断引用

  1. 保留并合并底层const。

  2. 会正常解析引用。解析的方式和类型别名一致。在这两种情况下将会产生引用折叠的问题,也就是对引用的引用。只有右值引用的右值引用会折叠为右值引用,其他都会被折叠为左值引用。

    因此对于一个右值引用的模板形参,可以传入左值引用将其转变为左值引用。或者相反。

    上述指的都是函数的实参。

    但是一旦涉及到了引用折叠就会很麻烦,因为这个模板形参即可能是普通类型的,也可能是引用的。

static_cast除了用于合法的类型转换,还可以将一个左值转换为一个右值引用,这就是move的实现。

但是对于普通的函数传参,引用折叠就并不会起作用,甚至会报错。

转发

转发就是通过一个函数将实参传递给另一个函数来调用。

在此处我们仅讨论转发函数是一个模板函数。

在转发的时候,我们希望保留原来实参的一些属性,如左右值引用与const。

此时,我们可以让转发函数的参数都为右值引用来通过引用折叠来保留所有属性。这当被转发的函数的参数为普通的参数或左值引用的时候非常有效,但是当为右值引用的时候,由于右值引用本身就是一个左值,因此不能将右值引用传递给接收右值引用的。

至于为什么能用std::move的返回值来给右值引用赋值,是因为它返回的事constexpr type&&,是一个右值,而非普通右值引用的左值。

此时我们可以使用forward,当面对普通参数和右值引用,forward会像move一样返回一个constexpr的右值引用,而面对左值引用,由于引用折叠,会返回一个左值引用。

重载与模板

函数推断

所有可以合理实例化的模板函数加入待选函数中。

选择其中最匹配的待选函数。如果匹配度差不多,则先选择非模板函数。如果没有非模板函数,则选择其中特例化最高的,否则二义性。

重载模板可以用于对于指针和普通变量个性化处理,指针形参对于指针更加特化。

在定义任何函数前最好声明所有重载函数(包括模板)。

可变参数模板

可变数目的参数被称为参数包,分为两种,模板参数包与函数参数包,模板参数包就是函数参数包中参数的类型。

template<typename knowntype,typename…args>

type fun(knowntype param,(const)(*)args(&)…changeableparams)

…代表接下来是可变参数。

这两个参数包都是会根据调用来生成不同的实例,并可通过sizeof来获取args和changeableparams的个数。

一般情况下,可变参数模板函数都是递归进行的,每一层都处理一个元素。

当处理完第一个元素后,希望继续传入下一层,使用fun(changeableparams…)。另外,一般会有一个非可变参数模板来作为最终的终止,因为非可变参数模板更特化,所以会调用这个,然后就不会继续递归了。

…的意思是对一个参数包进行扩展。

扩展就是将这个参数包展开为param1,param2…的形式。

当我们希望展开的同时对每一个参数都做相同的一个函数操作时,需要func(params)…,将…放在外面,对整体进行扩展,但扩展完后必须作为其他函数的参数?以及这个func必须是非模板函数?

当我们希望无损失地转发参数包的时候,直接在模板函数参数中使用右值引用,并在转发时使用forward(changeableparams)…即可。此时,…既会扩展args也会扩展changeableparams。

模板特例化

有时候有一些类型需要特殊化处理,这时候就可以使用模板特例化。像重载一样,但是需要在函数前加上template<>,即便所有的模板参数都特例化了,也需要保留<>来表示这是一个模板,可以部分特例化,将特例化的模板参数用实际的类型替代即可,但书上说只有类模板可以部分特例化。

特例化的实质并不是重载一个实例化的实例,而是就是实例化了一个特殊的模板版本。

特例化的版本必须总是跟着原模板(在同一个作用域内),否则,即便不存在特例化的版本,编译器也不会报错,直接调用原模板了。

当特例化类的时候,在类名后加上<>里面输入特例化后的实参即可,实际上就是把template里的搬过来然后将其中要特例化的改掉,可以只是特例化一部分,比如可它加上&或者&&等等。

除了特例化一整个类之外,我们也可以特例化其中的成员。特例化时只需要将<>中的内容更改即可,方法与特例化一整个类一致。


模板与泛型编程
https://lhish.github.io/project/hide/模板与泛型编程/
作者
lhy
发布于
2024年6月30日
许可协议