decltype用于生成变量名或者表达式的类型,其生成的结果有的是显而易见的,能够预测的,容易理解,有些则不容易理解。大多数状况下,与使用模板和auto时进行的类型推断相比,decltype做用于变量名或者表达式只是重复了一次变量名或者表达式的确切类型:html
const int i = 0; // decltype(i) 为 const int bool f(const Widget& w); // decltype(w) 为 const Widget& // decltype(f) 为 bool(const Widget&) struct Point { int x, y; // decltype(Point::x) 为 int }; // decltype(Point::y) 为 int Widget w; // decltype(w) 为 Widget if (f(w)) … // decltype(f(w)) 为 bool template<typename T> // std::vector 的简易实现 class vector { public: … T& operator[](std::size_t index); … }; vector<int> v; // decltype(v) 为 vector<int> … if (v[0] == 0) … // decltype(v[0]) 为 int&
上面的结果都在乎料之中,很好理解。C++11中,decltype的主要用于声明模板函数,此模板函数的返回值类型依赖于其参数类型。例如,看一个例子:咱们须要实现一个模板函数,此模板函数的参数包括一个支持方括号("[]")索引的容器加一个int索引值,中间须要作一些验证操做,最后函数返回类型应该同容器索引操做的返回类型相同。c++
一个元素类型为T的容器,operator []的返回值类型应该为T&。std::queue容器都知足这个要求,std::vector大部分状况下都知足(std::vector<bool>为一个例外,operator[]并不返回bool&,而是一个全新的对象),所以注意这里的容器操做符operator[]的返回值类型依赖于容器类型。函数
使用decltype能够很方便的实现此模板函数,此模板须要作一些改进,后面讨论:性能
template<typename Container, typename Index> // 此函数能够工做,但能够改进。 auto authAndAccess(Container& c, Index i) -> decltype(c[i]) { authenticateUser(); return c[i]; }
注意这里的auto并无作任何类型推断,只是用来代表这里使用的是C++11 的拖尾返回类型(trailing return type)语法,也就是函数返回类型将在参数列表以后进行声明(在"->"以后),优势是可使用函数参数来声明函数返回类型(若是将返回类型放置于函数以前,这里的参数c和i尚未被声明,所以不能被使用)。code
C++14中能够忽略拖尾返回类型了,这样上面的实现就只剩下auto了。使用这种形式的声明就意味着要进行类型推断。编译器将会根据函数的实现来推断函数返回类型:htm
template<typename Container, typename Index> // C++14,可是不正确 auto authAndAccess(Container& c, Index i) { authenticateUser(); return c[i];//根据c[i]推断返回类型 }
上一边帖子的最后解释了,使用auto做为函数返回类型,编译器将会使用模板类型推断推断返回类型。这种状况下上面的函数就有问题了。对于大多数元素类型为T的容器,operator[]返回T&,可是模板类型推断
中解释了,用于初始化的表达式的引用属性会被忽略掉。看下面的代码:对象
std::deque<int> d; … authAndAccess(d, 5) = 10; // 返回 d[5],赋值10,编译会出错
这里的d[5]会返回int&,可是authAndAccess中的auto返回类型推断将会把引用剔除掉,最后的返回值类型为一个右值int。C++中禁止将10赋值给一个右值int,所以编译失败。blog
为了获得咱们想要的,也就是不使用拖尾返回类型,咱们须要对返回类型使用decltype类型推断,也就是要指定函数authAndAccess和表达式c[i]返回相同的类型。C++14中咱们使用decltype(auto)标志符来达到目的。它的意义是:auto代表要进行类型推断,decltype说明推断过程当中将会使用decltype推断规则。最后实实现autoAndAccess以下:索引
template<typename Container, typename Index> // C++14,正确的实现,仍然能够改进 decltype(auto) authAndAccess(Container& c, Index i) { authenticateUser(); return c[i];//根据c[i]推断返回类型 }
如今authAndAccess将会返回c[i]所返回的。若是c[i]返回一个T&,authAndAccess也会返回T&。若是c[i]返回一个对象,authAccess也会返回一个对象。get
decltype(auto)的使用并不限制于函数返回类型,也可以用于变量的声明:
Widget w; const Widget& cw = w; auto myWidget1 = cw; // auto 类型推断,myWidget1的类型为 Widget,引用和const属性被忽略掉了 decltype(auto) myWidget2 = cw;//myWidget2的类型为const Widget& ,由于这里使用了decltype推断推着
在authAndAccess的最后一个版本中,咱们提到了此函数仍然能够改进,如何作呢?再看一眼函数声明:
template<typename Container, typename Index> decltype(auto) authAndAccess(Container& c, Index i);
这里函数参数为按指向非const左值的引用进行传递,返回容器中元素的引用到客户端就容许客户端对其进行修改。既然是左值引用咱们就不可以向这个函数传递右值。可是传递右值到函数中多是有意义的,客户端可能只想得到容器中元素的一份拷贝,看下面的例子:
std::deque<std::string> makeStringDeque(); // 工厂函数 // 获取从makeStringDeque中返回的deque中第五个元素的拷贝 auto s = authAndAccess(makeStringDeque(), 5);
所以咱们须要对函数进行修订,使此函数即可以接受左值,也能接受右值。可使用重载(一个函数声明一个左值引用参数,一个函数声明一个右值引用参数),可是须要维护两个函数。咱们可使用universal reference参数类型来避免这种状况,由于 此参数类型便可以绑定到右值,也能够绑定到左值,最后authAndAccess能够声明成下面这个样子:
template<typename Container, typename Index> decltype(auto) authAndAccess(Container&& c, Index i);
在这个模板函数中,咱们不知道须要操做的容器类型,也固然不知道容器内的元素类型,对一个不了解其类型的对象采用按值传递,可能会带来没必要要的拷贝形成的性能问题,还有可能有对象切片问题,可是这里咱们使用容器索引获取函数返回值,仿照标准模板库中的实例来实现看上去是合理的(例如,std::string,std::vector,std::deque,),所以咱们坚持使用按值传递。
为了从返回值中传递右值属性,咱们须要对univversal reference使用std::forward:
template<typename Container, typename Index> // C++14,最终版本 decltype(auto) authAndAccess(Container&& c, Index i) { authenticateUser(); return std::forward<Container>(c)[i]; }
上面的函数须要使用C++ 14的编译器,若是没有,也可使用C++11中的模板版本,与C++ 14不一样的是须要你本身指定返回类型:
template<typename Container, typename Index> // C++14,最终版本 decltype(auto) authAndAccess(Container&& c, Index i) ->decltype(std::forward<Container>(c)[i]) { authenticateUser(); return std::forward<Container>(c)[i]; }
咱们还须要说明另一个问题,文章开始说起了,decltype大多数状况下会返回你所指望的类型,可是还有一些例外,为了更好的理解decltype,咱们也须要熟悉这些状况。
将decltype应用于变量名会生成同此变量名相同的类型。这种状况下没有例外。可是对于左值表达式来讲状况就有些复杂了,decltype会确保其做用于左值表达式时,生成的类型为一个左值引用。也就是说若是一个左值表达式(而非变量名)的类型为T,那么decltype(左值表达式)的类型就是T&。大多数状况下这不会有任何影响,由于大多数左值表达式都会显示的包含一个左值引用标识符。例如,函数返回左值时,一般会返回左值引用,也就包含一个&标识符。
可是有一种状况须要注意:
int x =0;
x是变量名,所以decltype(x)的类型为int。可是用括号()将x括起来将会生成一个表达式,表达式(x)也为左值,所以decltype((x))为int&。
进一步考虑c++14中的decltype(auto):
decltype(auto) f1() { int x = 0; … return x; // decltype(x) 为int,f1返回int } decltype(auto) f2() { int x = 0; … return (x); // decltype((x)) 为 int&, f2 返回 int& }
注意第二种状况不只仅返回值发生了变化,并且返回的是指向本地变量的引用。所以要警觉这种错误的发生。
最后总结一下: