Item3 Understand decltype

来源:互联网 发布:项目管理流程软件 编辑:程序博客网 时间:2024/06/01 10:51

这个系列的文章来自于Effective Modern C++的读书笔记,我抽取了其中比较重要的,不容易理解的,平常我们开发过程中也不太在意的一些Item进行分析。

decltype是用来推导变量的类型,但是不像auto和模板类型推导那样,存在很多类型推导规则,decltype推导出来的类型和变量原来的类型一模一样,没有做任何改动。在C++11中decltype结合auto还可以完成函数返回值的类型推导。

template<typename Container,typename Index>auto AccessContainer(Container& c,Index i) -> decltype(c[i]) {    return c[i];}

到了C++14的时候就可以省略掉后面的-> decltype(c[i])了,变成下面的样子。

template<typename Container,typename Index>auto AccessContainer(Container& c,Index i) {    return c[i];}

上面的这个例子是用来访问容器中某个位置的元素,也就是容器的operator[]操作符返回的元素,按照容器的定义这个操作符应该返回的是一个引用,也就是说你可以像下面这样给某个位置赋值。

std::vector<int> d;AccessContainer(d,5) = 100;

但是很不幸,你会发现编译出错,原因则是罪魁祸首的,auto,返回值遇到auto后,引用被忽略掉了,在Item1中的Case3中详细解释了这个规则,为了让上面的的例子可以正常运行,可以做如下改动。

template<typename Container,typename Index>auto& AccessContainer(Container& c,Index i) {    return c[i];}

返回的是一个auto&,至于为什么可以参考Item1中的Case1,除次之外还可以用C++14的另外一种写法如下:

template<typename Container,typename Index>decltype(auto) AccessContainer(Container& c,Index i) {    return c[i];}

通过decltype保证返回变量的本来类型这一特性,保证不丢失CV限制符,和引用等,因此在C++14中可以通过decltypeauto来声明变量,保证变量的类型和赋值的类型一模一样。

int ia = 10;const int& iia = ia;auto autoia = cw;             //推导出的类型是int,引用和CV限制符都会忽略decltype(auto) deautoia = cw; //const int& 保证和cw的类型一模一样 

上面的方案通过decltypeauto让返回值的类型变的完美,但是如果用户传入一个const的容器,将会导致编译出错。因为AccessContainer的参数类型是非常量引用,为了让他可以接收常量和非常量,需要使用常量引用(神奇的常量引用)。

template<typename Container,typename Index>decltype(auto) AccessContainer(const Container& c,Index i) {    return c[i];}

这带来的另外一个问题就是,c[i],返回的是常量引用,无法修改。好在C++11中引入了右值引用,它还有另外一个名字叫做通用引用,通过名字就可以知道这个引用很通用,它可以接收左值,右值还有带const的。

template<typename Container,typename Index>decltype(auto) AccessContainer(Container&& c,Index i) {    return c[i];}

到此为止看似已经很完美了,可以接收任何类型的容器,返回值也和传入的类型一致,但是上述方案仍然有不足之处如果用户传入的是一个右值,通过移动语义传递给了AccessContainer的参数c,但是c本身其实是一个左值,如果在AccessContainer中需要把c再次传递给其他的函数的话就不能再次利用右值的移动语义了,带来了不必要的拷贝开销。C++11中的完美转发使得上面的方案变得完美,它可以将参数原封不动的传递给其他的函数。

template<typename Container,typename Index>decltype(auto) AccessContainer(Container&& c,Index i) {    return std::forward<Container>(c)[i];}

到此为止实现了一个完美的AccessContainer,关于完美转发可以参考这篇文章C++0x里的完美转发到底是神马?

decltype如此完美,从文章的开篇我就一直强调decltype的好,它可以完美的得到目标变量的类型,但是在大多数人的眼中C++怎是那么的不完美,decltype会打破这个定律吗? 当然没有,decltype也有例外的时候。

int x = 0;decltype(x)     //得到的是int类型decltype((x))   //得到的是int&类型

上面的变量x加了一个括号后就变成了一个引用类型了,根据官方如下:

The type denoted by decltype(e) is defined as follows:  — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5),     decltype(e) is the type of the entity named by e. If there is no such entity, or if e names     a set of overloaded functions, the program is ill-formed;  — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;  — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;  — otherwise, decltype(e) is the type of e.The operand of the decltype specifier is an unevaluated operand (Clause 5).[ Example:const int&& foo();int i;struct A { double x; };const A* a = new A();decltype(foo()) x1 = i;     // type is const int&&decltype(i) x2;             // type is intdecltype(a->x) x3;          // type is doubledecltype((a->x)) x4 = x3;   // type is const double&end example ]

对于x来说,没有括号括起来,所以符号第一条规则,类型就是x本身,而(x)不符号第一条,也不符合第二条,因为他是左值所以符合第三条,所有就是int&。具体内容可以参考stackoverflow

哈哈,其实decltype真的还好了,C++11中至今感觉坑比较少的一个特性吧。

0 0