> 文档中心 > C++11新特性之decltype类型推导

C++11新特性之decltype类型推导

​    

目录

一、decltype关键字

二、decltype的推导规则

1、表达式为单独变量

2、表达式为函数调用

3、表达式为左值,或者被()包围

三、 decltype的应用


        在前面一章,小编已经对auto类型推导这个关键字进行了介绍,相信小伙伴们都知道了auto的用法以及限制了吧,不知道的小伙伴回头自己去搜索一下~~~。

一、decltype关键字

        在这一章节中,小编要告诉你另外一个关键字,这个关键字也是作为类型推导用的,它是C++11新特性新加的一个关键字。这个关键字就是decltype,它和auto的功能一样,也是在编译器编译时期进行自动类型的推导。其实你可以把这个关键字看成是declare和type的缩写,这样是不是显得更加明确了呢?

        看到这里,你肯定有一个疑问就是,为什么有了auto类型推导,还要有decltype这个关键字的出现呢?其实细心一点的话你可以发现auto关键字有一些限制,也就是上章节有讲到的限制,像定义变量的时候一定要初始化,不能作用于类的非静态变量。所以我们总结了auto并不是适用于所有的自动类型推导的场景,在某些特定环境下auto使用起来非常的不方便,甚至无法使用它,所以C++11新特性才会引出decltype这个关键字。

        我们都知道auto定义变量的语法为:

auto serven_1 = 10;

        可以看到auto是根据=右边的初始值来自动推导变量的类型,所以auto在定义的时候必须初始化;而同样decltype也有自己的语法,它的语法为:

decltype (exp) varname = value;

        其中,varname表示变量名,value表示赋值变量的值,exp表示一个表达式,这就是和auto最大的区别了,decltype是根据表达式exp来自动推导出变量的类型,跟=右边的value没有关系,所以decltype在定义的时候可以不用初始化,因此,decltype定义变量也可以写成:

decltype (exp) varname;

        这里需要注意exp表达式,通常一说到表达式,我们脑子里面想到的就是一个普通的表达式,像 x = 1等等,但是关键字decltype关键字中的表达式可以是复杂的表达式,复杂到你怀疑人生嘿嘿嘿。但是有一点需要注意的是必须保证表达式exp的结果是有类型的,不能是void。像exp在调用一个返回值类型为void的函数时,exp的结果也是void类型,此时编译器会给你直接报错了

void show(){  cout<<"三贝勒文子!"<<endl;}decltype (show()) serven_2;            // 编译器会一脸问号的问你,这是啥玩意???

如果使用下面的代码:

int show(){  cout<<"Serven"<<endl;  return 6;}​int main(){   int serven_1 = 1;   decltype(serven_1) serven_2 = 3;   decltype(show()) serven_3 = 5;   cout<<serven_3<<endl;    // 会打印5,但是不会打印函数show中的内容}

decltype用法:

int serven_1 = 0;decltype (serven_1) serven_2 = 1; // serven_2被推导为 intdecltype (1.2) serven_3;                 // serven_3被推导为 double,不初始化也可以decltype (serven_3 + 100) serven_4;      // serven_4被推导为 double

        从上面的例子可以看到,decltype能够根据变量、字面量、带有运算符的表达式或者函数的返回类型推导出变量的类型。

二、decltype的推导规则

上面的例子让我们知道了decltype的一些初级用法,下面我们来了解一下decltype的高级用法吧,它的高级用法可以非常的复杂,当我们使用关键字来进行类型推导的时候,编译器有三个规则:

  • 如果表达式exp是一个不被括号()包围的表达式,或者是个类成员访问表达式,或者是一个单独的变量,那么decltype(exp)的类型就是和exp一致的,这应该不难理解;

  • 如果exp是函数调用,那么decltype(exp)的类型就和函数的返回值类型一致,但是注意一点就是函数的返回值不能是void;

  • 如果exp是一个左值,或者被括号()包围,那么decltype(exp)的类型就是exp的引用,假如exp的类型为T,那么decltype(exp)的类型就是T&。

1、表达式为单独变量

#include #include #includeusing namespace std;​​class SERVEN_PAR{    public:    static int ser1;      // 类静态变量在类中不能初始化    string ser2;    int ser3;    float ser4;};​int SERVEN_PAR::ser1 = 1; // 初始化类的静态变量​int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);​    int n = 0;    const int& r = n;    SERVEN_PAR SERVEN; decltype(n) b = n;   // n为int类型,b被推导为int类型    decltype(r) c = n;   // r为const int&类型,c被推导为const int类型    decltype(SERVEN_PAR::ser1) d = 0;  // ser1位类的一个int类型成员,那么d被推导为int类型    decltype(SERVEN.ser2) e = "https://www.baidu.com";      // ser2位类的一个string类型,那么e被推导为string类型        return a.exec();}

2、表达式为函数调用

#include #include #includeusing namespace std;​​int& func_int_first(int, char);   // 返回值为int&int&& func_int_second(void);      // 返回值为int&&int func_int(double);      // 返回值为int​const int& func_cint_first(int,int,int); // 返回值为const int&const int&& func_cint_second(void);      // 返回值为const int&&​​int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);​    int n = 10;    decltype(func_int_first(100,'S')) b = n; // b的类型为int&, 这里要赋初始值,因为int&引用需要初始化的,但是这里不能直接赋数值,因为这是左值引用    decltype(func_int_second()) c = 0;// c的类型为int&&,这里需要赋初始值,这是右值引用,所以可以直接赋数值    decltype(func_int(10.5)) d = 0;   // d的类型为 int        decltype(func_cint_first(1,2,3))  x = n; // x 的类型为 const int &    decltype(func_cint_second()) y = 0;      // y 的类型为 const int&&​    return a.exec();}​

        值得注意的是:这里的表达式只使用了函数的返回类型,不会执行函数里面的函数体内容的。代码中decltype(func_int_first(100,'S')) b = n;b的类型为int&, 这里要赋初始值,因为int&引用需要初始化的,但是这里不能直接赋数值,因为这是左值引用。而decltype(func_int_second()) c = 0;c的类型为int&&,这里需要赋初始值,这是右值引用,所以可以直接赋数值。

3、表达式为左值,或者被()包围

#include #include #includeusing namespace std;​​class SERVEN_PAR{public:    int x;};​​int main(int argc, char *argv[]){    QCoreApplication a(argc, argv); SERVEN_PAR SERVEN; /* 带有括号的表达式 */    decltype(SERVEN.x) b;    decltype((SERVEN.x)) c = b;/* 加法表达式 */    int n = 0, m = 0;    decltype(n+m) d = 0;    // n+m得到一个右值,复合推导规则一,推导结果为int    decltype(n = n+m) e = c;// n=n+m得到一个左值,复合推导规则三,推导结果为int   return a.exec();}

​  上面的代码中包括了带有括号的表达式,和加法表达式两种,其中加法表达式里面分为了左值和右值,左值是指那些在表达式执行结束后依然存在的数据,也就是持久性的数据;右值是指那些在表达式执行结束后不再存在的数据,也就是临时性的数据,对表达式取地址操作,如果编译器报错了那就是右值,如果编译器不报错那就是左值。

三、 decltype的应用

        我们知道了auto的使用比较简单,所以在一般的类型推导过程中,使用auto会比decltype更加方便。但是auto只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型的话,那么就得使用decltype关键字了,下面是一个模板的定义:

#include using namespace std;​​template class SERVEN_PAR {public:  void func(T& container) {    m_it = container.begin();  }private:  typename T::iterator m_it;  //注意这里};​int main(){  const vector v;  SERVEN_PAR<const vector> obj;  obj.func(v);  return 0;}

    单独看SERVEN_PAR类中的m_it成员的定义,很难看出有什么错误,但是在使用SERVEN_PAR类的时候,如果传入一个const类型的容器,编译器马上就会弹出一大堆错误。原因就在于T::iterator并不能包括所有的迭代器类型,当T是一个const容器时,应用使用const_iterator。要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

template class SERVEN_PAR {public:  void func(T& container) {    m_it = container.begin();  }private:  decltype(T().begin()) m_it;  //注意这里};

欢迎关注小编的微信公众号 “三贝勒文子”,每天学习C++