1. 函数模板
函数模板是一种特殊的函数,可以使用不同的类型进行调用,对于功能相同的函数,不需要重复编写代码,并且函数模板与普通函数看起来很类似,区别就是类型可以被参数化
- 存在inline 和 constexpr 函数模板
1.1 实例化函数模板
template <typename T> void func(T t)
{
cout<<t<<endl;
}
1.1.1 隐式实例化: func(i)
在发生函数模板的调用时,不显示给出模板参数而经过参数推演,称之为函数模板的隐式模板实参调用
1.1.2 显示实例化:func<int>(i)
template [函数返回类型] [函数模板名]<实际类型列表>(函数参数列表)
func(1);
template int func(1);//尽量用上面一行的写法代替本行
事实上,编译器只在要调用函数的时候才使用到函数,如果不使用显示实例化,每次调用函数时,模板都会消耗性能去推导使用的是哪个类型的函数,增加了程序运行时的负担;使用了显示实例化,则在编译时就已经处理了函数选择。
1.2 模板实参推测
从函数实参来确定模板实参的过程被称为模板实参推断(template argument deduction
如果一个函数使用了模板类型参数,那么它的初始化规则将不再是普通函数的初始化规则;而是只有有限的几种类型转换会自动地应用这些实参。编译器通常不是对实参进行转换,而是生成一个新的模板实例:
- const 转换:可以将一个非const对象的应用或者指针传递给一个const的引用或指针形参。
- 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。
1.3 函数模板显示实参
对于有些函数模板在调用的时候必须提供一个显示模板实参,如:
// 编译器无法推断T1
template<typename T1, typename T2, typename T3>
T1 func(T2, T3);
显示模板实参按从左至右的顺序来对应的模板参数匹配。
上诉代码可以使用
- func(2, 3);
- func<int,int>(2, 3);
- func<int, int,int>(2, 3);
T1显示指定T2,T3是从函数实参中推测而来
// 编译器无法推断T3
template<typename T1, typename T2, typename T3>
T3 func(T1, T2);
上诉代码需要用户指定所有的三个模板参数,因为显示模板实参按从左至右的顺序来对应的模板参数匹配
1.4 尾置返回类型与类型转换
当用户不确定返回类型时用显示模板实参表示模板函数返回类型是很有效的;但是有些情况下,显示指定模板实参反而会增加负担。比如这样:
template <typename It>
??? &func(It beg, It end) {
... // 处理序列
return *beg; // 返回序列中的一个元素引用
}
我们需要知道 *beg 的返回类型,用 decltype(*beg) 获取即可,**但是编译器在遇到函数参数列表之前,beg是不存在;**所以我们必须使用尾置返回类型。
template <typename It>
auto func(It beg, It end) -> decltype(*beg)
{
... // 处理序列
return *beg; // 返回序列中的一个元素引用
}
如何返回一个非引用类型:
标准库给我提供的类型转换模板,定义在头文件type_traits中,使得我们可以获得元素类型。使用remove_reference::type 脱去引用,剩下元素类型本身
template <typename It>
auto func(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
... // 处理序列
return *beg; // 返回序列中的一个元素引用
}
// 这样返回类型就不是引用了,而是这个元素类型本身。
1.5 函数指针和实参推测
当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。
#include <iostream>
#include <typeinfo>
using namespace std;
// 模板类型参数
template <typename T1,typename T2, typename T3> T1 compare(T2 t2, T3 t3)
{
return t2 > t3 ? t2:t3;
}
//
void func(int (*p)(const int&, const int&))
{
cout << "func int" << endl;
p(1, 2);
}
void func(double (*p)(const double&, const double&))
{
cout << "func double" << endl;
p(1, 2);
}
//非模板类型参数
int main()
{
double (*pf1)(const int&, const int&) = compare;
cout << typeid( pf1(1, 2)).name() << endl;
int (*pf2)(const int&, const int&) = compare;
cout << typeid(pf2(1, 2)).name() << endl;
func(compare<int>);
func(compare<double>);
//func(compare) 避免产生调用歧义 不知道调用重载函数的哪个版本
}
结果:
1.6 引用和模板实参推断
template <typename T> void f(T &p);
// p是一个模板类型参数T的引用
编译器会应用正常的引用绑定规则;const 是底层的,不是顶层的
(引用用类型中的const是底层的,参数传递中当实参初始化形参时会忽略顶层const。)
1.6.1 左值引用函数参数推断类型
模板类型参数是一个普通的(左值)引用时,只能传递一个左值。若实参是const 的, 则T将会被推断为 const类型:
template <typename T> void f1(T&); // 实参必须是一个左值
f1(i); // i 是一个 int; T 是 int
f1(ci); // ci 是一个 const int; T 是 const int
f1(43); // 错误:传递给一个&参数的实参必须是一个左值
如果类型参数是 const T&, 那么可以传递给他任何类型的实参(一个对象,临时对象或一个字面值常量)。当 const 是函数参数本身时,T不会是一个 const 类型。
template <typename T> void f2(const T&); // 任意类型
// f2 中参数是 cosnt &; 实参中的const 是无关的
f1(i); // i 是一个 int; T 是 int
f1(ci); // ci 是一个 const int; T 是 int
f1(43); // 一个const &参数可以绑定到右值; T 是 int
1.5.2 右值引用函数参数推断类型
在实际中,模板参数的右值引用通常用于两种情况:模板转发其实参 或者 模板被重载。
template <typename T> void f3(T&&);
f3(43); // 实参是一个 int 类型的右值; 模板参数 T 是 int
f3(i); // 实参是一个 int 类型的左值; 模板参数 T 是 int&
1.5.2.1 引用折叠
引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数。
- X& &、X& && 和 X&& & 都折叠成 X&
- 类型 X&& &&折叠成 X&&
有了引用折叠,则可以传递任意类型的实参给函数参数类型的右值引用。但是如果是一个左值传递,函数参数通过折叠被实例化为一个普通的左值引用;如果是一个右值传递,则函数参数被绑定到一个左值。
1.6 转发
某些函数需要将其一个或者多个实参连同类型不变地转发给其他函数
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
f(std::forward<T2>(t2), std::forward<T1>(t1))
}
void g(int &&i, int& j)
{
........ // 处理
}
// std::forward 返回显示实参类型的右值引用,当用于指向模板参数类型的右值引用函数参数时,会保留实参类型所有细节
1.7 模板与重载
需要多个对不同类型使用同一种算法函数时可以使用模板,但是并非所有的类型都使用同一种算法,为了解决这个问题,产生了模板重载。
- 包含模板的函数匹配规则
- 候选函数包括所有模板实参推断成功的函数模板实例
- 可行函数按类型转换来排序
- 如果有多个函数提供“同样好的”匹配,同样好的函数中只有一个是非模板函数,则选择此函数
- 同样好的函数中全是模板函数,选择更“特例化的模板
#include <iostream>
#include <sstream>
using namespace std;
template <typename T> void debug_rep(const T& t)
{
cout << "refrence: " << t;
}
template <typename T> void debug_rep(T* p)
{
cout << "pointer: " << *p;
}
int main()
{
std::string s("hi");
debug_rep(&s);
}
问题在于模板debug_rep(const T&)本质上可以用于任何类型,包括指针类型。此模板比debug_rep(T*)更通用,后者只能用于指针类型;
1.8 可变参数模板
C++11的新特性–可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。
1.8.1 可变模版参数的展开
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”。
template <typename... T>
void f(T... args)
{
cout << sizeof...(args) << endl; //打印变参的个数
}
sizeof… 运算符: : 打印变参的个数
1.8.2 递归函数方式展开参数包
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
cout << "parameter " << head << endl;
print(rest...);
}
int main(void)
{
print(1, 2, 3, 4);
return 0;
}
结果:
包扩展:
- 函数参数包扩展
template <typename T> string debug_rep(const T& t)
{
ostringstream ret;
ret << "refrence: " << t;
return ret.str();
}
template <typename T>
void print(ostream& os, const T& t) {
os << t << endl;
}
template <typename ...Args>
void errorMsg(ostream& os, const Args&...rest)
{
// return print(os, debug_rep(rest1),debug_rep(rest2),debug_rep(rest3)...);
return print(os, debug_rep(rest)...);
}
- 转发参数包
std::forward(args)…
template<class F, class... Args>void expand(const F& f, Args&&...args)
{
//这里用到了完美转发
initializer_list<int>{(f(std::forward< Args>(args)),0)...};
}