深入剖析decltype和decltype(auto)

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

decltype关键字是C++11新尺度引入的关键字,它和关键字auto的功能类似,也可以自动推导出给定表达式的类型,但它和auto的语法有些差别,auto推导的表达式放在“=”的右边,并作为auto所界说的变量的初始值,而decltype是和表达式结合在一起,语法如下:
  1. decltype(expr) var;
复制代码
它的语法像是函数调用,但它不是函数调用而是运算符,和sizeof运算符类似,在编译期间计算好,表达式expr不会被真正执行,因此不会产生汇编代码,如下的代码:
  1. int func(int);
  2. decltype(func());
复制代码
func函数不会真正被调用,只会在编译期间获取他的类型。decltype和auto在功能上大部门相似,但推导规则和应用场景存在一些区别,如用auto界说变量时必须提供初始值表达式,利用初始值表达式推导出类型并用它作为变量的初始值,而decltype界说变量时可以不必要初始值。还有利用auto作为值语义的推导时,会忽略表达式expr的引用性和CV属性,而decltype可以保存这些属性,关于auto的详细剖析,可以参考另一篇文章《深入剖析C++的auto自动类型推导》
decltype在普通代码中应用并不广泛,主要用在泛型编程中较多,因此没有auto利用得多,下面将先容decltype的推导规则,在先容过程中遇到和auto规则不一样的地方则将两者对照阐明,最后再先容decltype无法被auto替代的应用场景。
推导规则

我将decltype的推导规则归纳为两条,根据expr有没有带小括号分为两种情势,如以下的情势:
  1. decltype(expr)
  2. // 或者
  3. decltype((expr))
复制代码

  • expr没有带括号的情形
当expr是单变量的标识符、类的数据成员、函数名称、数组名称时,推导出来的结果和expr的类型一致,并且会保存引用属性和CV修饰词,如下面的例子:
  1. int func(int, int) {
  2.     int x;
  3.     return x;
  4. }
  5. class Base {
  6. public:
  7.         int x = 0;
  8. };
  9. int x1 = 1;                // (1) decltype(x1)为int
  10. const int& x2 = 2;        // (2) decltype(x2)为const int&
  11. const Base b;                               
  12. b.x;                        // (3) decltype(b.x)为int
  13. int a[10];                // (4) decltype(a)为int[10]
  14. decltype(func);                // (5) 结果为int(int, int)
复制代码
(1)式decltype(x1)的结果和x1的类型一致,为int类型。
(2)式的结果也是和x2一致,这里和auto的推导规则差别的是,它可以保存x2的引用属性和const修饰词,所以它的类型是const int&。
(3)式中界说的类对象b虽然是const的,但成员x的类型是int类型,所以结果也是int。
(4)和(5)都保存了原本的类型,这个也是和auto的推导结果差别的,利用auto推导的规则它们会退化为指针类型,这里则保存了它们数组和函数的类型。
当expr是一条表达式时,decltype(expr)的结果视expr表达式运算后的结果而定(在编译时运算而非运行时运算),当expr返回的结果是右值时,推导的结果和返回结果的类型一致,当expr返回的结果是左值时,推导的结果是一个引用,见下面的例子:
  1. int x1 = 1;
  2. int x2 = 2;
  3. decltype(x1 + x2);        // (1) int
  4. decltype(func());        // (2) int
  5. decltype(x1,x2);        // (3) int&
  6. decltype(x1,0);                // (4) int
  7. decltype(a[1]);                // (5) int&
复制代码
(1)式由于两个变量相加后返回一个数值,它是一个右值,所以推导结果和它的类型一致,这里换成加减乘除都是一样的。
(2)是一个函数调用,跟上面的利用函数名称差别,这里会调用函数(编译时),根据函数的返回结果来确定推导出来的类型,如果返回结果是引用大概指针类型,则推导结果也会引用大概指针类型,此函数返回的结果是int型,所以结果也是int型。
(3)和(4)是逗号表达式,它的返回结果是逗号后的那个语句,(3)是返回x2,它是一个变量,是一个左值,所以推导结果是int&,而(4)的返回结果是0,是一个右值,因此结果和它的类型一致。
(5)是访问数组中的元素,它是一个左值,因此推导结果是一个引用。

  • expr带括号的情形
当expr带上括号之后,它的推导规则有了变化,表达式加上括号后相当于去执行这条语句然后根据返回结果的类型来推导,见下面的例子:
  1. class Base {
  2. public:
  3.         int x = 0;
  4. };
  5. int x1 = 1;
  6. int x2 = 2;
  7. const Base b;
  8. b.x;
  9. decltype((x1+x2));         // (1) int
  10. decltype((x1));                // (2) int&
  11. decltype((b.x));        // (3) const int&
复制代码
(1)式中相加后的结果是一个右值,加上括号后依然是一个右值,因此推导结果是int。
(2)式中跟之前没有加括号的情况不一样,加上括号相当于是返回x1变量,因此是一个左值,推导结果是一个引用。
(3)式中也跟之前的结果不一样了,加上括号相当于返回类的数据成员x,因此是一个左值,推导结果是一个引用,但由于界说的类对象b是一个const对象,要保持它的内容不可被修改,因此引用要加上const修饰。
最后还有要注意一点的是,decltype和auto一样也可以和&和一起结合利用,但和auto的规则不一样,auto与&和结合表示界说的变量的类型是一个引用大概指针类型,而decltype则是保存这个符号并且和推导结果一起作为终极的类型,见下面的例子:
  1. int x1 = 1;
  2. auto *pi = &x1;                // (1) auto为int,pi为int*
  3. decltype(&x1) *pp;        // (2) decltype(&x1)为int*,pp为int**
复制代码
(1)式中的auto推导结果为int而不是int,要将pi界说为指针类型必要明确写出auto
(2)式的decltype(&x1)的推导结果为int,它会和界说中的(*pp前面的星号)结合在一起,因此终极的结果是int**。
decltype的利用场景

上面提到decltype和auto的一个区别就是利用auto必须要有一个初始值,而decltype在界说变量时可以不必要初始值,这在界说变量时暂时无法给出初始值的情况下非常有效,见下面的例子:
  1. #include <map>
  2. #include <string>
  3. template<typename ContainerT>
  4. class Object {
  5. public:
  6.     void init(ContainerT& c) { it_ = c.begin(); }
  7. private:
  8.     decltype(ContainerT().begin()) it_;
  9. };
  10. int main() {
  11.     std::map<std::string, int> m;
  12.     Object<std::map<std::string, int>> obj;
  13.     obj.init(m);
  14. }
复制代码
在界说类的成员it_时还没有初始值,这时无法利用auto来推导它的类型,况且这里也无法利用auto来界说类的数据成员,由于现在还不支持利用auto来界说非静态的数据成员的,但利用decltype却是可以的。
还有一种情形是利用auto无法做到的,就是auto在利用值语义的推导规则的时候会忽略掉引用属性和CV修饰词,好比:
  1. int i = 1;
  2. const int& j = i;
  3. auto x = j;        // auto的结果为int
复制代码
这里x无法推导出和变量j一样的类型,你可能会说,如果要利用引用类型,那可以如许写:
  1. const auto& x = j;        // auto的结果为int, x的类型const int&
复制代码
但这又会带来其它的问题,如许界说出来的变量的类型永远都是const引用的类型,无法做到根据差别的表达式推导出相应的类型,如果利用decltype则可以做到:
  1. int i = 1;
  2. const int& j = i;
  3. decltype(j) x = j;        // x的类型为const int&
  4. decltype(i) y = i;        // y的类型为int
复制代码
上面的代码利用decltype就可以根据差别的初始值表达式来推导出差别的结果。但你可能会觉得初始值表达式要在左右两边写上两遍,比力累赘,单个变量的还好,如果是个长表达式的话就会显得代码很冗余,也不优雅,好比:
  1. int x = 1;
  2. int y = 2;
  3. double z = 5.0;
  4. decltype(x + y + z) i = x + y + z;
复制代码
如果上面的例子中表达式再长点就更难看也更麻烦了,幸好C++14尺度提出了decltype和auto结合的功能,也就是decltype(auto)的用法。
decltype(auto)的利用剖析

自动推导表达式的结果的类型

decltype(auto)的利用语法规则如下:
  1. decltype(auto) var = expr;
复制代码
它的意思是界说一个变量var,auto作为类型占位符,利用自动类型推导,但推导的规则是按照decltype的规则来推导。因此上面的代码可以如许来写:
  1. decltype(auto) j = x + y + z;
复制代码
它的用法跟利用auto一样,利用右边的表达式来推导出变量j的类型,但是推导规则利用的是decltype的规则。这对必要保持右边表达式的引用属性和CV修饰词时就非常有效,上面的代码可以改为:
  1. int i = 1;
  2. const int& j = i;
  3. decltype(auto) x = j;        // x的类型为const int&
  4. decltype(auto) y = i;        // y的类型为int
复制代码
decltype(auto)用于推导函数返回值的类型

decltype(auto)可以用于推导函数返回值的类型,auto也可以用于推导函数的返回值类型,在讲解auto的那篇文章中就已讲解过。但auto有个问题就是会忽略掉返回值的引用属性,但如果你用auto&来推导返回值类型的话,那所有的类型都将是引用类型,这也不是实际想要的结果,有没有办法做到如果返回值类型是值类型时就推导出值类型,如果返回值类型是引用则推导出结果是引用类型?假设有一个处理容器元素的函数,它接受一个容器的引用和一个索引,函数处理完这个索引的元素之后再返回这个元素,一般来说,容器都有重载了“[]"运算符,但有的容器可能返回的是这个元素的值,有的可能返回的是元素的引用,如:
  1. T& operator[](std::size_t index);
  2. // 或者
  3. T operator[](std::size_t index);
复制代码
这时我们就可以用decltype(auto)来自动推导这个函数的返回值类型,函数的界说如下:
  1. template<typename Container, typename Index>
  2. decltype(auto) process(Container& c, Index i) {
  3.     // processing
  4.     return c[i];
  5. }
复制代码
当传进来的容器的operator[]函数返回的是引用时,则上面的函数返回的是引用类型,如果operator[]函数返回的是一个值时,则上面的函数返回的是这个值的类型。
decltype(auto)利用陷阱

最后,对于decltype(auto)能够推导函数返回值为引用类型这一点,必要提示一下的是,小心会有下面的陷阱,如下面的函数:
  1. decltype(auto) func() {
  2.     int x;
  3.     // do something...
  4.     return x;
  5. }
复制代码
这里推导出来的返回值类型是int,并且会拷贝局部变量x的值,这个没有问题。但如果是如许的界说:
  1. decltype(auto) func() {
  2.     int x;
  3.     // do something...
  4.     return (x);
  5. }
复制代码
这个版本返回的是一个引用,它将引用到一个即将销毁的局部变量上,当这个函数返回后,所返回的引用将引用到一个不存在的变量上,造成引用空悬的问题,步伐的结果将是未知的。无论是故意的还是无意的返回一个引用,都要特别小心。
此篇文章同步发布于我的微信公众号:深入剖析decltype和decltype(auto)
如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享大概微信号iTechShare并关注,大概扫描以下二维码关注,以便在内容更新时直接向您推送。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表