Effective Modern C++ Item 2 理解auto型别推导

需要记住:除了一个奇特的情况例外以外,auto型别推导就是模板型别推导

template<typename T>
void f(ParamType param);
f(expr);                //以某表达式调用f

auto x = 27;            //auto扮演了上述T的角色,变量的型别修饰词则扮演了ParamType的角色,
const auto cx = x;
const auto& rx = x;
// 对于以上三条,可以模拟出一个概念模板去理解auto的推导过程
    template<typename T>    //为推导x的型别而生成的概念性模板
    void func_for_x(T param);
    func_for_x(27);         //概念性调用语句:推导得出的param的型别就是x的型别
//同理
    template<typename T>    //为推导cx的型别而生成的概念性模板
    void func_for_cx(const T param);
    func_for_cx(x);         //概念性调用语句:推导得出的param的型别就是cx的型别
//同理
    template<typename T>    //为推导rx的型别而生成的概念性模板
    void func_for_rx(const T& param);
    func_for_rx(x);         //概念性调用语句:推导得出的param的型别就是rx的型别

和条款1类似,也分为三种情况处理auto型别推导:

情况1. 型别饰词是指针或引用,但不是万能引用
情况2. 型别饰词是万能引用
情况3. 型别饰词既不是指针又不是引用

上述情况1和情况3已经讨论过,重点说一下情况2,如下例子:

auto&& uref1 = x;           //x的型别是int,且是左值,所以uref1的型别类型是int&
auto&& uref2 = cx;          //cx的型别是const int,且是左值,所以uref2的型别类型是const int&
auto&& uref3 = 27;          //27的型别是int,且是右值,所以uref3的型别类型是int&&

对于数组和函数而言:

const char name[] = "Adam Xiao";        //name的型别是const char[13]
auto arr1 = name;                       //arr1的型别是const char*
auto& arr2 = name;                      //arr2的型别是const char(&)[13]
void someFunc(int, double);             //someFunc是个函数,型别是void(int, double)
auto func1 = someFunc;                  //func1的型别是void(*)(int, double)
auto& func2 = someFunc;                 //func2的型别是void(&)(int, double)

那么特例来了

对于赋值构造的时候,会出现一些不一样的情况。对于C++98的时候,可以选择如下方式进行变量初始化:

int x1 = 27;
int x2(27);

而在C++11中,为了支持统一初始化 uniform initialization,新增了下面的语法选项:

int x3 = {27};
int x4{27};

一共有四种语法,然而结果却殊途同归,得到一个值为27的int。
将这四种初始化语法换成auto情况则会出现一些不同点:

auto x1 = 27;       //型别是int,值是27
auto x2(27);        //型别是int,值是27
auto x3 = {27};     //型别是std::initializer_list<int>,值是{27}
auto x4{27};        //型别是std::initializer_list<int>,值是{27}

这里出现奇特的原因是因为以下规定:

当用于auto声明变量的初始化表达式是使用大括号括起时,推导所得的型别就属于std::initializer_list

在以上奇特规定的限制下,如下报错就不奇怪了:

auto x5 = {1, 2, 3.0};       //错误! 推导不出std::initializer_list<T>中的T

对比Item 1去看,上述这个奇特的规定就是auto型别推导和模板型别推导的唯一不同之处。而这个不同之处存在的根因,实际上是因为auto会假定用大括号括起的初始化表达式代表一个std::initializer_list,但模板型别推导却不会。代码例子如下:

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

template<typename T>        //带有形参的模板
void f(T param);            //与x的声明等价的声明式

f({11, 23, 9});             //错误!无法推导T的型别

要使得模板推导能够正常工作,那么必须写成这样:

template<typename T>
void f(std::initializer_list<T> initList);

f({11, 23, 9});             //T的型别推导为int,从而initList的型别是std::initializer_list<int>

对于以上情况,会得出一个常见的很多程序猿的习惯:
很多程序猿都只在必要的时候才会使用大括号括起的初始化表达式

对于C++14来说,额外有几点要说明,C++14比C++11额外支持了auto可以当做返回值。那么根据上述特例的以下代码是无法通过编译的:

//大括号作为返回值的时候,auto无法推导
auto creatInitList()
{
    return {1,2,3};         //错误,无法为{1,2,3}完成型别推导
}
//大括号作为入参的时候,auto无法推导
std::vector<int> v;
...
auto resetV = [&v](const auto& newValue){v = newValue;}; //C++14
...
resetV({1,2,3});             //错误,无法为{1,2,3}完成型别推导
要点速记
1. 在一般情况下,auto型别推导和模板型别推导是一模一样的。但是auto型别推导会将大括号括起的初始化表达式理解为std::initializer_list,但模板型别推导却不会有这样的理解。
2. 在函数返回值或lambda表达式中的形参使用auto,意思是用模板型别推导而非auto型别推导。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值