第1课 理解模板类型推导
一、函数模板
(一) 函数模板及调用形式
template<typename T> void f(ParamType param); //注意这里是ParamType而不是T。这两者可能不一样! f(expr); //调用形式,以实参expr调用f
(二)讨论:
①T和ParamType的类型往往不一样。因为ParamType常包含一些修饰词,如const或引用符号等限定词。
②T的类型,不仅仅依赖于实参expr的类型,还依赖于ParamType的类型。
③ParamType的形式可分为三种情况:A. ParamType是个指针或引用类型(非万能引用)。B. ParamType是一个万能引用。C. ParamType既非指针也非引用。
二、函数模板的推导规则
(一)规则1:ParamType是个指针或引用(但非万能引用),即T*或T&等。
1、两条规则:
①若expr是个引用类型,先将其引用忽略
②然后对expr的类型和ParamType的类型进行模式匹配,来决定T的类型。
2、注意事项:
①ParamType是个引用类型。由于引用的特点,即形参代表实参本身,所以实参expr的CV属性会被T保留下来。(注意,如果传进来的实参是个指针,则param会将实参(指针)的顶层和底层const的都保留下来)。
②当ParamType为T*指针,传参时param指针是实参的副本,因此实参和形参是两个不同的指针。T在推导中,会保留实参中的底层const,而舍弃顶层const。因为底层const修饰的是指针所向对象,表示该对象不可更改,所以const应保留,而顶层const表示指针本身,从实参到形参传递时复制的是指针,因此形参的指针是个副本,无须保留const属性。(如,const char* const ptr中,顶层const指修饰ptr的const,即*号右侧const,而底层const指*号左侧的const)
(二)规则2:ParamType为万能引用,即T&&
1、两条规则:
①如果实参expr是个左值,T和ParamType会被推导为左值引用。(注意,这是模板类型推导中,T唯一被推导为引用(T&)的情形)
②如果要实参expr是个右值,则应用“规则1”来推导。 此时的T被推导为T。
2、注意事项:
①当实参为左值时T被推导为T&(不是T类型),表示形参是实参的引用,即代表实参本身。因此指针的顶层和底层const属性均会被保留。
②形如const T&&或vector<T>&&)均属于右值引用,因为const的引用会剥夺引用成为万能引用的资格,因为由其定义的变量再也不能成为非const类型,所以不是“万能”的类型,而后者己经确定是个vector<T>类型,不可能成为“万能”的类型,如不会再是int型,因此也不是万能引用。(详见《万能引用》一节)
③在推导过程中会发生引用折叠(详见《引用折叠》一节)。
(三)规则3:ParamType是个非指针也非引用类型(即按值传递)
1、推导规则:
①若实参是个引用类型,则忽略其引用部分,同时cv属性被忽略。(因为按值传递,采用复制手段,形参是个副本,与实参是两个不同的参数)。
②如果实参是指针类型,从实参到形参传递时,传用的是按比特位复制,因此形参也是个副本,只保留指针的底层const,而舍弃顶层const)
③其他情况也是按值传递,形参同样也是一个副本。
【编程实验】函数模板的推导规则
#include <iostream> #include <boost/type_index.hpp> using namespace std; using boost::typeindex::type_id_with_cvr; //辅助类模板,用于打印T和param的类型 template <typename T, typename Param> void printType() { cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", "; cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl; } /***************************规则1:当ParamType引用或指针时*****************************/ //(1)当ParamType为T&时 template <typename T> void func_r(T& param) //由于T是个引用,操作的是变量/对象本身,当如果实参带有const或volatile属性,会被T保留下来 { //。如果实参是个指针,param代表实参本身,会将顶层和底层const全部保存一下。 printType<T, decltype(param)>(); } //(2)当ParamType为const T&时 template <typename T> void func_cr(const T& param) //由于假定param是个const引用,为避免重复,T的推导结果就没必要包含const了。 { printType<T, decltype(param)>(); } //(3)当ParamType为右值引用时 template <typename T> void func_rr(const T&& param) //注意由于T&&被const修饰,为右值引用(而非万能引用) { printType<T, decltype(param)>(); } //(4)当ParamType为T*指针时(注意const char* const ptr) //顶层const:指ptr指针变量本身的const属性(T推导时被舍弃) //底层const:指ptr指针所指向对象/变量的const属性(被T保留) template <typename T> void func_p(T* param) //当param是个指针时,如果实参带const,则表示所指对象具有const属性,因此其底层const会被T保留 { printType<T,decltype(param)>(); } //(3)当ParamType为const T*指针时 template <typename T> void func_cp(const T* param) //如果实参带有const,为避免重复,T的推导就没有必要包含const { printType<T, decltype(param)>(); } /***************************规则2:当ParamType为万能引用时,即T&& *****************************/ template<typename T> void func_ur(T&& param) { printType<T, decltype(std::forward<T>(param))>(); } template<typename T> void func_cr2(const T&& param) //注意这是右值引用 { printType<T, decltype(param)>(); } /***************************规则3:当ParamType为非指针和引用类型时 *****************************/ template<typename T> void func_v(T param) { printType<T, decltype(param)>(); } int main() { int x = 27; const int cx = x; const int& rx = x; const int* const ptr = &x; cout << "****************************规则1*****************************" << endl; //ParamType为引用: void func_r(T& param) func_r(x); //T = int, param = int& func_r(cx); //T = const int, param = const int& func_r(rx); //T = const int, param = const int& func_r(ptr); //T = const int*, param = const int* const; //这里表示实参(ptr)的引用, 即代表实参本身,因此ptr的顶、底层const均保留
//Param为const T&: void func_cr(const T& param) func_cr(x); //T = int, param = const int& func_cr(cx); //T = int, param = const int& func_cr(rx); //T = int, param = const int& //Param为指针: void func_p(T* param) func_p(&x); //T = int, param = int* func_p(&cx); //T = const int, param = const int* func_p(ptr); //T = const int, param = const int* //只保留底层const。(注意形参param是实参的副本,即将ptr按比特位复制给param, //因为是副本,所以只保留底层const) //Param为const T* : void func_cp(const T* param) func_cp(&x); //T = int, param = const int* func_cp(&cx); //T = int, param = const int* func_cp(ptr); //T = int, param = const int* //只保留底层const //ParamType为右值引用: void func_rr(T&& param) func_rr(10); //T = int, param = int&& func_rr(std::move(cx)); //T = int, param = const int&& func_rr(std::move(rx)); //T = int, param = const int&& cout << "****************************规则2*****************************" << endl; //void func_ur(T&& param); 注意,当实参为左值时,T一定是一个引用类型(左值引用),这是T被推导为引用的唯一情形 func_ur(x); // T = int&, param = int& func_ur(cx); // T = const int&, param = const int& //T为引用,会保留cv属性 func_ur(rx); // T = const int&, param = const int& func_ur(ptr); // T = const int* const&, param = const int* const& //顶层和底层const均保留 func_ur(10); // T = int, param = int&& 实参为右值,T被推导为int,注意不是int&& func_ur((int&&)10); // T = int, param = int&& 实参为右值,T被推导为int,注意不是int&& //func_cr2(x); // 编译不通过,因为const T&&代表右值引用(而非万能引用),必须传入右值。 func_cr2(10); // T = int, param = const int&& 实参为右值,T被推导为int cout << "****************************规则3*****************************" << endl;
//按值形参: void func_v(T param);
func_v(ptr); //T = const int*, param = const int* (注意只保留底层const,即实参本身的const舍弃)
return 0;
}
三、数组和函数实参的推导规则
(一)推导规则:
(1)当函数模板为按值形参时(如T param):数组和函数类型均退化成指针类型。(如char*、void(*)(int, double)。
//由于数组到指针的退化规则,以下两个函数等价的,所以不能同时声明这两个同名函数。
void myFunc(int param[]);
void myFunc(int* param);
(2)当函数模板为引用类型形参时(如T& param):则数组和函数分别被推导为数组引用和函数引用类型。特别值得注意,数组引用类型会包含数组元素类型及大小信息,如const char(&)[13]。而函数引用类型如void(&)(int, double)
(二)数组引用的妙用:用于推导数组元素个数
【编程实验】数组实参与函数实参的推导
#include <iostream> #include <boost/type_index.hpp> using namespace std; using boost::typeindex::type_id_with_cvr; //辅助类模板,用于打印T和param的类型 template <typename T, typename Param> void printType() { cout << "T = " << type_id_with_cvr<T>().pretty_name() << ", "; cout << "param = " << type_id_with_cvr<Param>().pretty_name() << endl; } //数组和函数实参的推导 template<typename T> void func_v(T param) //按值形参 { printType<T, decltype(param)>(); } template<typename T> void func_r(T& param) //按引用形参 { printType<T, decltype(param)>(); } void test(int x, double y) { } //数组引用的妙用:获得数组元素的个数 template<typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept //注意形参是个数组引用 { return N; } int main() { const char name[] = "SantaClaus"; //name的类型为const char[11];(含\0) const char* pName = name; //数组退化为指针 /********************************数组和函数实参的推导****************************************/ func_v(name); //T = const char*, param = const char*(数组退化为指针,因此T是指针类型) func_r(name); //T = const char[11], param = const char(&)[11] func_v(test); //T = void(*)(int, double), param = void(*)(int, double) (函数名退化为指针,因此T是指针类型) func_r(test); //T = void(int, double), param = void(&)(int, double) (注意T的类型) /***********************************数组引用的妙用******************************************/ int keyValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int mapVals[arraySize(keyValues)]; //arraySize返回值是编译期常量,可用于定义数组大小 cout << arraySize(keyValues) << endl; //9 cout << arraySize(mapVals) << endl; //9 cout << arraySize(name) << endl; //11 }