C++11 模板函数类型推导

如何理解模板函数类型推导

用一段伪代码来介绍模板函数类型推导要做的事情是什么

//声明一个模板函数f
template 
void f(ParamType param);

//调用该模板函数
f(expr);

如上代码片段,通过调用模板函数,依据实参 expr 来推导出 T 与 ParamType 的类型就是模板函数类型推导要做的事情,具体来讲,两件事:一是推导 T 的类型;二是推导 ParamType 的类型

T 与 ParamType 的类型有何不同

通常来讲, T 与 ParamType 的类型往往是不一样的,因为在ParamType中通常会加一些如const或引用饰词等,如下代码片段

int x = 27;

//声明模板函数 
template 
void f(const T& param);

//调用该模板函数
f(x);            // 此时T类型为 int, ParamType类型也为const int&

如上所示,T 与 ParamType 的类型结果是不一致的,但 T 与实参 x 的类型是一致的,那是不是说所有情况下 T 与实参的类型都会保持一致,事实也并非想象的这么简单,T的类型不仅取决于实参 expr,同时也取决于 ParamType 的形式,需要分以下三种情况来讨论:

a. 当 ParamType 为指针或者引用时,但此时的引用不是万能引用;

b. 当 ParamType 为万能引用时;

c. 当 ParamType 既非指针也非引用时;

a. ParamType 为指针或引用,而非万能引用时的模板类型推导

此时的模板类型推导规则如下:

实参 expr 是引用或指针类型时,则将 引用或指针 属性先忽略掉;

然后对 expr 的类型与 ParamType 的类型执行模式匹配,来决定 T 的类型;如下代码片段

int x = 27;

const int cx = x;

const int &rx = x;

//声明模板函数
template 
void f(T& param);        //此时ParamType为引用类型;

//调用模板函数
f(x);         //实参x为非引用类型int, 则T为int, ParamType为int&

f(cx);        //实参cx为const int, 则T为const int, ParamType为const int&

f(rx);        //实参rx为引用类型,则先将引用性忽略,T为const int, ParamType为 const int&

如果将 ParamType 的类型从 T& 改为 const T& 时,T 的类型推导结果就会有一些变化,因为此时已经假设形参是一个const 类型的引用了,所以在T的类型推导结果中如果含有const就没有必要了,如下代码片段;

const int cx = 27;

//声明模板函数
template 
void f(const T& param);

//调用模板函数
f(cx);      //ParamType含有const饰词,则T的类型为int, ParamType的类型为const int&

若当ParamType为指针类型时,则推导原则和上面一致,如下代码片段

int x = 27;

const int *px = &x;

//声明模板函数
template 
void f(T* param);

//调用模板函数
f(&x);            //T为int, ParamType为int*;

f(px);            //现将指针属性忽略,T为const int, ParamType为const int*;

b. ParamType 为万能引用时的模板类型推导

此时的类型推导规则比较特殊,特殊之处有二,其一,是唯一一种来类型推导时需要区分实参为左值还是右值的情况;其二,是唯一一种将T推导为引用类型的情况,具体规则如下:

实参为左值时,则 T 与 ParamType 的类型均推导为左值引用类型

实参为右值时,则 T 为相应的右值类型,ParamType 为相应类型的右值引用

如下代码片段所示:

int x = 27;

const int cx = x;

const int &rx = x;

//声明模板函数
template
void f(T&& param);

//调用该模板函数
f(x);        // x 为左值,则 T 为 int&, ParamType 也为 int&

f(cx);       // cx 为左值,则 T 为const int&, ParamType 也为 const int&

f(rx);       // rx 为左值,则 T 为const int&, ParamType 也为 const int&

f(27);       // 27 为右值,则 T 为int, ParamType 为 int&&

这也是当ParamType为万能引用时,唯一会区分实参为左值还是右值的情况,其他非万能引用时,从来不会做这样的区分。

c. 当ParamType既非指针也非引用类型时的模板类型推导

当ParamType既非指针也非引用时,也就是所谓的按值传递,此种情况形参本质上是实参的一个拷贝,形参与实参是不同的两个对象,在内存中有着不同的存储空间,这样就促成了此种情况下的类型推导规则:

当实参具有引用属性时,引用性将被忽略;当其具有const常量性时,将被忽略;当其具有volatile属性时,同样将被忽略掉;如下代码片段:

int x = 27;

const int cx = x;

const int& rx = x;

template
void f(T param);

f(x);     // T 为 int, ParamType 也为int

f(cx);    // T 为 int, ParamType 也为int

f(rx);    // T 为 int, ParamType 也为int

因为形参是一个全新的对象,所以当实参具有某种特性时(引用性或者常量性或者volatile性),并不意味着形参也该具有这样的特性。

需要说明的是只有在按值传递时,const及volatile特性才会被忽略,但考虑下面这样的情况:如果ptr是一个指向常对象的常量指针时,而且按值传递时,类型推导结果会怎样呢:

const char* const ptr = "Hello world, hello dream.";

// * 左侧的 const 代表它指向一个常对象,不可更改的对象;
// * 右侧的 const 代表指针的指向不能更改,它本身是一个常指针


template 
void f(T param);

f(ptr);    // 此时ptr本身的指向不能更改的常量性将被忽略,但指向对象的常量性将会保留
           // 所以 param 是个 const char* 类型的一个全新的指针,该指针可以修改指向

有关模板函数类型推导的情况已经基本讨论完了,但是还有一个情况需要值得留意,数组实参

数组实参

如果将数组作为实参传递给一个函数,那么该函数的形参应该怎么定义呢,按理来说应该是下面这样的:

int nArr[5] = {0};

void myFunc(int param[]);

以上代码的语法是合法的,但是我们很少将形参定义为数组类型,由于在C语言中数组的声明可以按照指针的声明加以处理,所以上面代码也可以写成这样的形式:

void myFunc(int *param);

这种数组和指针形参的等价性是C语言的遗迹,这种写法更容易让人理所应当的认为数组和指针类型就是一回事儿,容易混淆视听。那如果将数组类型按值传递的方式传递给模板函数,会有怎样的推导情况呢,如下代码片段:

const char name[] = "hello world, hello dream"; //name 类型为 const char[25]

template
void f(T param);

f(name);        //此时 T 的类型为 const char *

如上所示,T 的类型被推导成 const char* 指针类型,并非数组类型;有意思的是,如果按引用的方式来传递数组类型,则 T 会被推导成实际的数组类型,如下代码片段:

const char name[] = "hello world, hello dream";

template 
void f(T& param);    //按引用方式传递形参的模板

f(name);     // T 的类型为 const char[25]; ParamType 的类型为 const char(&)[25];

正如上面示例代码,T 会被推导为实际的数组类型,这个类型中会包含数组尺寸。形参类型被推导为数组的引用。

函数实参

上面讨论的数组实参其实是C++里面的一种退化机制,从数组退化为指针;但并非只有这一种退化,函数的类型也可以退化成函数指针;并且将函数作为实参的模板类型推导与数组实参一模一样;如下代码片段:

void funcExample(int, double);  //funcExample是个函数,其类型为 void(int, double);

template
void f1(T param);    //param按值传递

template
void f2(T& param);    //param按引用传递

f1(funcExample);    //此时 param 的类型为函数指针  void(*)(int, double);

f2(funcExample);    //此时 param 的类型为函数引用  void(&)(int, double);

以上就是关于模板函数类型推导的所有内容了,除了讨论了ParamType三种形式下的类型推导,还说明了两种特殊情况下的类型推导机制。如果有帮助到您,那就点个赞吧哈哈~

你可能感兴趣的:(C/C++,类型推导,c++,算法)