您的位置:首页 > 编程语言 > C语言/C++

《effective modern c++》笔记之c++类型推导(1)

2017-07-06 20:25 169 查看

模版类型推导

如果你不介意看一下伪代码,我们可以把函数模版看做下面是这个样子。

template<typename T>
void f(ParamType param);


然后一个函数调用长这样:

f(expr);


推导出来的T的类型不仅取决于expr,还取决于ParamType

有以下三种情况:

ParamType是一个引用或者指针,但不是universal reference

ParamType是universal reference

ParamType既不是指针也不是引用

第1种情况:ParamType 是一个引用或者指针,但不是universal Reference

最简单的情况也就是这种情况。类型推导按照下面这个方式工作



1. 如果expr类型是一个引用,那么无视引用。

2. 然后用expr的类型匹配ParamType类型,来决定T.



举几个例子,如果这是我们的模版:

template<typename T>
void f(T& param);


接着我们有这些变量,

int x = 27;
const int cx = x;
const int& rx = x;


那么,对于不同的调用,param和T推导的类型如下:

f(x);       //T是int, param类型是int&
f(cx);      //T是const int, param类型是 const int&
f(rx);      //同上,因为无视引用


这部分的类型推断很直观,几乎就是你想要的那样。

第2种情况:ParamType 是一个universal Reference

universal reference以右值引用的形式声明。以下是右值引用大概的描述:

如果expr是左值,T和ParamType被推导为左值引用。在模版类型推断中,这是唯一一种情况T被推导为引用。T和ParamType类型相同

如果expr是右值,那么会按照正常的流程走。

举几个例子:

template<typename T>
void f(T&& param); //param是一个 universal reference

int x = 27; const int cx = x; const int& rx = x;

f(x); //x是一个左值,所以T是 int&,param也是int&
f(cx); //cx是一个左值,所以T是 const int&, param也是const int&
f(rx); //rx是一个左值,所以同上
f(27); //27是一个右值,所以T是int,param类型是int&&


第3种情况:ParamType 既不是指针,也不是引用

如果ParamType既不是指针又不是引用,那么我们在处理值传递.

template<typename T>
void f(t param);        //param 现在通过值传递


这意味着param只是一个拷贝,一个全新的对象。因为它是一个全新的对象,所以

如果expr类型是一个引用,那么无视引用的部分

在无视引用之后,如果expr是const或者是volatile,都无视掉。

(毕竟是一个全新的对象啊,操作新的对象不影响原来的对象。)

正如我们看到的,引用、指针的const属性会在类型推导期间保留。但我们现在考虑一下当expr是值传递的时候,会怎么样。

template<typename T>
void f(T param);

const char* const ptr = "Fun with pointers";

f(ptr);


T的类型是 const char* ,其实不管是不是指针,消除的都是顶层const

数组变量

因为函数参数中数组的声明会被当做指针,所以void myFunc(int param[])其实和void myFunc(int* param)是一样的。

这个导致了 一个数组类型的参数值传递给模版函数的时候,会被推导为指针类型。

const char name[] = "J. P. Briggs";


template<typename T>
void f(T param);


所以对模版f 的调用,类型参数T会被推断为const char*:

f(name);  //name是数组,但T被推断为const char*


但你以为这就结束了吗?下一个就是曲线球,接好了。

虽然函数不能声明参数为真正的数组,但他们可以声明数组的引用。让我们修改一下模版f,将它的变量改为引用。

template<typename T>
void f(T& param);   //注意这里是引用T&了


接着我们将数组传递给它

f(name);


在这个例子中,T被推断为const char [13], f参数的类型为const char (&)[13].

虽然这个语法看起来有毒。。(自我感觉最有意思的一次翻译。。)但产生了一个有趣的结果就是模版能够推断 数组的长度。

//编译时返回数组大小
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)
)noexcept
{
return N;
}


函数变量

函数类型也会退化为函数指针,以上讨论过的所有 数组的类型退化 都适用于函数变量。

void someFunc(int, double); //someFunc类型是 void(int,double)

template<typename T>
void f1(T param);           //f1值传递

template<typename T>
void f2(T& param);          //f2引用传递

f1(someFunc);               //param类型是void (*)(int, double)

f2(someFunc);               //param类型是void (&)(int, double)


auto类型推导

除去一个例外,你可以把模版类型推导和auto类型推导 看做是一对一的映射。

当一个变量用auto声明,auto扮演了模版中T的角色,类型描述符扮演了ParamType的作用。

auto x = 27;
//映射

tempalte<typename T>
void func_for_x(T param);

func_for_x(27);


const auto& rx =x;
//映射

teamplate<typename T>
void func_for_x(const T& param);

func_for_rx(x);


我们再来讲讲那个例外,c++11支持这样的初始化

int x{27};     //定义了一个值为27的int


一般情况下,我们都可以用auto来代替类型。但在这里

auto x{27};    //定义了一个initializer_list<int>


如果auto声明的变量的初始化器被包在括号中,那么推断类型就是std::initialize_list。但这个待遇只适用于auto。

auto x = { 11, 23, 9};   //x的类型是std::initializer_list<int>


如果你这么使用模版

template<typename T>
void f(T param);   //正确用法是用std::initializer_list<T>代替 T

f({11, 23, 9}); //报错。。


(c++ 14)但如果auto是一个函数的返回类型或者是lambda参数,那么使用的是模版类型推断,而不是auto类型推断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: