Item3 Understand decltype【Effective Modern C++中文版】

[复制链接]

该用户从未签到

759

主题

763

帖子

4660

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
4660
发表于 2018-1-5 19:37:17 | 显示全部楼层 |阅读模式

想要查看内容赶紧注册登陆吧!

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

x
decltype是用来推导变量的类型,但是不像auto和模板类型推导那样存在很多类型推导规则,decltype推导出来的类型和变量原来的类型一模一样,没有做任何改动。在C++11中decltype结合auto还可以完成函数返回值的类型推导。
  1. template<typename Container,typename Index>
  2. auto AccessContainer(Container& c,Index i) -> decltype(c[i]) {
  3.     return c[i];
  4. }
复制代码
到了C++14的时候就可以省略掉后面的-> decltype(c)了,变成下面的样子。
  1. template<typename Container,typename Index>
  2. auto AccessContainer(Container& c,Index i) {
  3.     return c[i];
  4. }
复制代码
上面的这个例子是用来访问容器中某个位置的元素,也就是容器的operator[]操作符返回的元素,按照容器的定义这个操作符应该返回的是一个引用,也就是说你可以像下面这样给某个位置赋值。
  1. std::vector<int> d;
  2. AccessContainer(d,5) = 100;
复制代码
但是很不幸,你会发现编译出错,原因则是罪魁祸首的auto,返回值遇到auto后,引用被忽略掉了,在Item1中的Case3中详细解释了这个规则,为了让上面的的例子可以正常运行,可以做如下改动。
  1. template<typename Container,typename Index>
  2. auto& AccessContainer(Container& c,Index i) {
  3.     return c[i];
  4. }
复制代码
返回的是一个auto&,至于为什么可以参考Item1中的Case1,除此之外还可以用C++14的另外一种写法如下:
  1. template<typename Container,typename Index>
  2. decltype(auto) AccessContainer(Container& c,Index i) {
  3.     return c[i];
  4. }
复制代码
通过decltype保证返回变量的本来类型这一特性,保证不丢失CV限制符,和引用等,因此在C++14中可以通过decltype和auto来声明变量,保证变量的类型和赋值的类型一模一样。
  1. int ia = 10;
  2. const int& iia = ia;
  3. auto autoia = cw;             //推导出的类型是int,引用和CV限制符都会忽略
  4. decltype(auto) deautoia = cw; //const int& 保证和cw的类型一模一样
复制代码
上面的方案通过decltype和auto让返回值的类型变的完美,但是如果用户传入一个const的容器,将会导致编译出错。因为AccessContainer的参数类型是非常量引用,为了让他可以接收常量和非常量,需要使用常量引用(神奇的常量引用)。
  1. template<typename Container,typename Index>
  2. decltype(auto) AccessContainer(const Container& c,Index i) {
  3.     return c[i];
  4. }
复制代码
这带来的另外一个问题就是,c,返回的是常量引用,无法修改。好在C++11中引入了右值引用,它还有另外一个名字叫做通用引用,通过名字就可以知道这个引用很通用,它可以接收左值,右值还有带const的。
  1. template<typename Container,typename Index>
  2. decltype(auto) AccessContainer(Container&& c,Index i) {
  3.     return c[i];
  4. }
复制代码
到此为止看似已经很完美了,可以接收任何类型的容器,返回值也和传入的类型一致,但是上述方案仍然有不足之处如果用户传入的是一个右值,通过移动语义传递给了AccessContainer的参数c,但是c本身其实是一个左值,如果在AccessContainer中需要把c再次传递给其他的函数的话就不能再次利用右值的移动语义了,带来了不必要的拷贝开销。C++11中的完美转发使得上面的方案变得完美,它可以将参数原封不动的传递给其他的函数。
  1. template<typename Container,typename Index>
  2. decltype(auto) AccessContainer(Container&& c,Index i) {
  3.     return std::forward<Container>(c)[i];
  4. }
复制代码
到此为止实现了一个完美的AccessContainer,关于完美转发可以参考这篇文章C++0x里的完美转发到底是神马?decltype如此完美,从文章的开篇我就一直强调decltype的好,它可以完美的得到目标变量的类型,但是在大多数人的眼中C++总是那么的不完美,decltype会打破这个定律吗? 当然没有,decltype也有例外的时候。
  1. int x = 0;
  2. decltype(x)     //得到的是int类型
  3. decltype((x))   //得到的是int&类型
复制代码
上面的变量x加了一个括号后就变成了一个引用类型了,根据官方如下:
  1. decltype ( entity ) (1) (since C++11)
  2. decltype ( expression ) (2) (since C++11)

  3. 1) If the argument is an unparenthesized id-expression or an unparenthesized class member access expression, then decltype yields the type of the entity named by this expression. If there is no such entity, or if the argument names a set of overloaded functions, the program is ill-formed.
  4. 2) If the argument is any other expression of type T, and
  5. a) if the value category of expression is xvalue, then decltype yields T&&;
  6. b) if the value category of expression is lvalue, then decltype yields T&;
  7. c) if the value category of expression is prvalue, then decltype yields T.


  8. [ Example:
  9. const int&& foo();
  10. int i;
  11. struct A { double x; };
  12. const A* a = new A();
  13. decltype(foo()) x1 = i;     // type is const int&&
  14. decltype(i) x2;             // type is int
  15. decltype(a->x) x3;          // type is double
  16. decltype((a->x)) x4 = x3;   // type is const double&
  17. —end example ]
复制代码
对于x来说,没有括号括起来,只是一个带有名字的变量,这是id-expression,它的类型是就是这个表达式的entity类型,所以符号第一条规则,类型就是x本身,而(x)不符号第一条,也不符合第二条,因为他是lvalue expression所以符合第三条,就是int&。具体内容可以参考stackoverflowcppreference哈哈,其实decltype真的还好了,C++11中至今感觉坑比较少的一个特性吧。








回复

使用道具 举报

快速回复高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表