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

模版类型推导


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

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 referenceint 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只是一个拷贝,一个全新的对象。因为它是一个全新的对象,所以

  1. 如果expr类型是一个引用,那么无视引用的部分
  2. 在无视引用之后,如果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 (&)[N])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

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

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

如果你这么使用模版

templateT>
void f(T param);   //正确用法是用std::initializer_list代替 Tf({11, 23, 9});  //报错。。

(c++ 14)但如果auto是一个函数的返回类型或者是lambda参数,那么使用的是模版类型推断,而不是auto类型推断。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部