C++模板入门(一)函数模板
一、函数模板初探
1.定义模板
1 | template <typename T> |
在这个例子中template<>角括号中是使用逗号分隔的参数列表,template是类型参数的引导字,这是目前为止最典型的模板参数用法,但模板也可以使用除类型外的其他参数,我们将在之后进行介绍。
需要注意的两点是:1.使用的类型T必须支持>号操作。2.因为采用按值返回,所以T的类型也必须是可拷贝的。
鉴于历史原因,可以使用class关键字代替typename,因为typename一词在C++98标准中引进,由于历史原因便保留了class 关键字,但要注意这不是类的概念,所以不能使用struct替代typename。
2.使用模板
1 | int a1 = 1, a2 = 2; |
上述代码中,::max()分别传入int,double两个类型的参数,并返回两者最大值(这里使用::是考虑到std标准库中也含有一个std::max()函数,防止冲突),注意模板不会被编译成可以处理任何类型的单一实体。在编译期,模板会根据传入参数的类型不同,而生成相应的实体,因此对于上述代码,模板生成了int max(),double max()两个实体,替代了原来的模板函数,这一过程被称为模板的实例化。
3.两阶段编译
(1).定义期间,模板不会实例化,该阶段不会检查调用类型是否可以合法地进行函数内的操作,只能发现诸如语法错误或者独立于模板的静态断言错误。
(2).实例化期间会再次检查模板代码,确保所有代码的有效性。
编译和链接:
当使用模板触发其实例化的时候,编译器需要查看模板的定义,这就意味着模板的定义并不能像普通函数一样使用头文件和源文件,而是必须定义在头文件中,我们约定俗成为.hpp。
二、模板实参推导
1 | template <typename T> |
在第一部分我们使用int参数时,模板可以推导出T为int类型,但看上述代码,若传入参数为常引用类型,再次使用int传参,编译能否成功?
答案是肯定的,int类型调用了常引用的::max()版本,并且T依旧被推到为int类型,这说明T可能只是类型的部分体现。
1.类型推导中的类型转换
(1).当声明调用参数按引用类型传递时,禁用类型推导,使用相同类型T的两个传入参数类型必须匹配。
(2).当声明调用参数为按值传递时,仅支持退化的简单转换:忽略const,volatile等限定符,引用转换为引用类型,原始数组或函数转换为指针类型,且经退化后的两个参数类型必须匹配。
1 | const int c1 = 1; |
解决上述错误的方法有三种:
(1).强制类型转换
1 | ::max(static_cast<double>c2 c3); |
(2).显式指定T的类型,阻止编译器自动类型推导
1 | ::max<double>(c2, c3); |
(3).指定不同类型的类型参数
2.默认实参的类型推导
类型推导对默认调用实参不起作用:
1 | template <typename T> |
解决方法:对模板参数也同样声明一个默认实参,与调用实参相匹配:
1 | template <typename T = std::string> |
三、多模板参数
1.返回类型的模板参数
1 | template <typename RT, typename T1, typename T2> |
由于模板无法针对返回类型做出推到,因此我们必须指定返回类型:
1 | ::max<double>(1, 2.2); |
可以只指定第一个或者依次显示指定实参类型,直到不存在不能隐式推导的实参类型为止。
2.推导返回类型
使用auto:
1 | template <typename T1, typename T2> |
看起来是最简单的方案,但这样做无法得到一个统一的返回类型,也就是说可能发生隐式的类型转换,产生不必要的麻烦。
更为严重的问题是,如果我们选择这样做,必须存在能够推导出返回类型的返回语句,并且所有返回语句推到的返回类型必须相匹配,否则程序就会报错:
1 | template <typename T1, typename T2> |
像这样,即时明显下面的return语句永远不会被调用,但在编译期它仍会被考虑在内,程序会在编译期报错。
auto+尾置返回类型:
1 | template <typename T1, typename T2> |
在C++11中,我们能够使用形参的尾置返回类型语法来声明返回类型。如使用decltype(),这样做会找到两种返回值的公共算数类型。事实上,如果把其中的a > b改为true即可,在编译期即可找到它们的公共类型。
std::decay<>
上面两种做法无论何时都有一个显著的缺点:传入参数T可能是引用类型,此时我们应该返回T的退化类型,而不是引用类型。
使用类型特征std::decay<>,他的返回结果为成员type:
1 | template <typename T1, typename T2> |
common_type萃取公共类型:
1 |
|
或
1 |
|
common_type会萃取两种(或更多)类型的公共类型,并且支持退化操作。
四、传值与传引用的选择
通常情况下,除了占用内存非常少的简单类型(如基本类型和std::string_view)之外,建议所有类型按引用传递,避免非必要的拷贝操作。
但是若有以下几个原因,传值更优:
1.编译优化更好。
2.移动语义拷贝成本更低。
3.有时完全没有拷贝或移动。
4.有时传引用会面临风险。

