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

  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<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类型推断。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值