C++11新特性之decltype类型推导
目录
一、decltype关键字
二、decltype的推导规则
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); // 返回值为intconst 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++