c++11/14 auto 与 decltype

Posted zkccpro

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++11/14 auto 与 decltype相关的知识,希望对你有一定的参考价值。

auto 与 decltype

c++11以后新增的这两个关键字可谓是“冤家路窄”。如果你是初学者,可能不能体会到为什么他俩有紧密的关系。你可能会说:auto就是根据根据给变量赋的值推出变量的类型;decltype就是可以推出来任意表达式的类型嘛。

void fun(int,int);
decltype(fun) f;//f的类型为:void(int,int)
auto f1=f;//f1的类型为:void(*)(int,int),这里的函数会退化成指针,这是为啥呢?

确实,作为初学者知道这些应该就够了,或许一般的初学者还会清楚decltype推导函数数组形参类型的退化:

void fun(int arr[10]);
decltype(fun) f;//f的类型为:void(int*)

原本的数组形参退化成了指针。不过这知道不知道也无所谓,毕竟现代c++中几乎不采用这种写法了。本文需要解决的问题是从更深层次的角度来分析auto推导类型的规则,以及auto与decltype的关系。

一、auto推导类型的规则

auto推导类型是啥规则啊?还记得“模板类型推导规则”一文的介绍吗?对,不要怀疑,**auto采用和模板类型推导一样的规则。**这样是不是就豁然开朗了!为什么本文第一段代码中,传入函数实参时,f1会退化成函数指针?只要理解了模板推导规则,这个问题就顺理成章了。(参加“模板类型推导规则”一文的第5条)

还记得模板类型推导中模板函数形参类型分为3种情况吗?左值引用、万能引用、值类型。下面分别看看这3种情况对应auto的关系:

auto& a=i;
//的推导规则等价于
template<typename T>
void f(T& arg);

auto&& a=i;//没错,这也算是万能引用!只有这2种情况算作万能引用!
//的推导规则等价于
template<typename T>
void f(T&& arg);

auto a=i;
//的推导规则等价于
template<typename T>
void f(T arg);

除了以上3点,模板类型推导规则一文中提到的**“数组实参退化**”和**“函数类型实参退化”**规则在auto中也同样适用。

上面的代码写的有点蹩脚auto a=i,是为了提醒,auto声明的变量必须要初始化,就像引用一样。(你想啊 你不初始化,编译器该怎么知道到这里应该给auto变量推导为什么类型、分配多大的内存呢?)

你以为这样就完事了吗??其实不是,auto的推导规则只有一点例外,和模板类型推导不一样!

  • auto与模板参数 对待(统一初始化)的区别?

    先说结论,记住即可,没有为啥:auto可以推导统一初始化的类型,而模板参数则不能推导统一初始化的类型。

  auto a=2;//a的类型为:std::initializer_list<int>,值为2
  
  template<typename T>
  void f(T arg);
  f(2);//编译出错,模板无法推导出的类型

统一初始化是c++11引入的新的初始化方式,可能你已经用过了,感觉还不错。但这里有很多坑,弄不好容易”反噬“自己。。后面的文章会详谈c++11的统一初始化。

  • c++11/c++14中的auto:

    c++14是更好用的c++11!这个版本对c++11的很多使用细节问题做出了改进,使用更加灵活方便。比如auto,就是c++14改进的重点之一。

    c++14对auto的改进共有2点:

    1. 支持lambda的形参列表中使用auto

      这就相当于为lambda增添了模板功能,lambda在c++14中也可以写成模板函数了!这在c++11中是不行的。但auto用在lambda形参中是一个“伪auto”,并不支持统一初始化类型的推导:

      auto lam=[](auto& ref);//完全等价于模板形参
      int a=0;
      lam(a);//OK
      lam(1);//NO,和模板一样,也推不出统一初始化!
      
    2. 函数返回值支持使用auto

      c++11中也不允许返回值使用auto,但c++14就可以,且这个auto也是一个“伪auto”,不能推导出模板参数。

      auto func()
          return 2;//OK
          return 2;//NO!,不支持推导统一初始化类型
      //c++14
      

      但不要搞混了,c++11是支持尾置返回值的,但不支持auto返回值,二者写法有点像:

      auto func()->int;//c++11,OK
      auto func();//c++11,NO!
      

二、auto与decltype的关系

说了那么久的auto,那么auto和decltype究竟有什么关系呢?

首先需要了解decltype是怎么类型推导的?他和模板类型推导/auto的规则还不一样,而是更nb一些。**decltype可以推导出表达式的准确类型。**不管有什么左值右值。。引用什么的,统统给你原汁原味推出来。

那么现在,思考这样一种需求(decltype+auto的经典场景):

我要设计一个函数,需要返回一个引用,而我的返回值是个右值(比如返回的是数组下标,是右值吧,而我还需要返回出去的是个左值引用!)。例如,c++11中可以这样完成这个需求:

template<typename Container,typename Index>
auto access(Container& c,Index i)->decltype(c[i])
    if(i>=c.size()) return c[c.size()-1];
    else if(i<0) return c[0];
    else return c[i];
//c++11

看上面的代码可能有点懵,上面的尾置返回类型强行推导c[i]的类型,虽然c[i]表达式本身是一个右值,但decltype(c[i])的结果是一个左值引用(因为c[i]的返回值是左值引用)。这样,向外返回的仍然是个左值引用,于是我们就可以写出这样的调用代码:

std::vector<int> vec1,2,3;
int a=access(vec,2);
access(vec,2)=3;//由于设计的返回左值,所以可以直接通过access修改vec[2]

在c++14中,为了完成上述需求,你可能会写出下面的代码:

template<typename Container,typename Index>
auto access(Container& c,Index i)
    if(i>=c.size()) return c[c.size()-1];
    else if(i<0) return c[0];
    else return c[i];
//c++14

上面说到,auto在c++14中可以直接作为返回值了,那么这么写能否完成我们想要的需求呢?

分析一下,表达式c[i]返回的是右值,对于一个传入一个右值,auto将推导其为值语义,即一份拷贝。意思就是,这个函数会把c[i]的值拷贝一份作为函数的返回值,返回之后就变成右值了。这。。明显达不到左值引用的要求啊!

因此,c++14为了简化c++11的代码,完成这个需求,发明了decltye(auto)这个“冤家组合”:

template<typename Container,typename Index>
decltye(auto) access(Container& c,Index i)
    if(i>=c.size()) return c[c.size()-1];
    else if(i<0) return c[0];
    else return c[i];
//c++14

这段代码啥意思呢?没啥意思,和c++11的尾置decltype一个意思。。。同样可以使access函数返回一个左值这个需求。

  • 最后,来谈一下c++11之后,用auto和decltype的一些小建议吧!

    1. auto是个好东西,当你觉得你的类型太长了(比如stl容器迭代器那种)你懒得写,那就用auto吧!

      std::vector<int> vec;
      auto it_vec=vec.begin();//it_vec的类型是:std::vector<int>::iterator
      
    2. c++11的范围for语句和auto是绝配

      std::vector<int> vec1,2,3,4;
      for(auto& item:vec)
          item=1;//item被推导为左值引用,可修改原数组的值
      
      
    3. lambda对象就要用auto了(除非你想使用临时lambda),因为你根本无法写出lambda的类型

      auto lam=[]();//无法写出"[]()"的类型
      

      当然了,你用c++11的函数对象(function)也可以接受一个lambda,但这不在本文的讨论范围。

    4. 用auto就必须初始化,因此类内成员就不要用auto了。(我试过类内的auto,不论是直接初始化、初始化列表初始化、还是构造函数中初始化,好像会报错)

以上是关于c++11/14 auto 与 decltype的主要内容,如果未能解决你的问题,请参考以下文章

C++ 11 auto 与 decltype

C++ 11 auto 与 decltype

C++11新特性:3—— 汇总auto和decltype的区别

C++11新特性:3—— 汇总auto和decltype的区别

C++11类型推导的关键字auto和decltype

C++11新特性:3—— C++ decltype类型推导完全攻略