浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第1课 理解函数模板的类型推导

Posted on 2019-07-14 12:33  浅墨浓香  阅读(2456)  评论(2编辑  收藏  举报

第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
}