前言
decltype是一个神奇的创造。给它一个name或者expression,它将告诉你name或者expression的类型。正如书中写到的
decltype typically parrots back the exact type of the name or expression you give it
decltype的基本用法有以下两种:
decltype(entity )
decltype(expression )
基本用法
例如:
const int i = 0; // decltype(i) is const int
bool f(const Widget& w); // decltype(f) is bool(const Widget& w)
struct Point{
int x, y; // decltype(Point::x) or decltype(Point::y) is int;
};
这里有一点值得注意的是,如果当我们使用decltype推导函数模板(function templates)的时候,推导返回的类型依赖于它的参数类型。例如
vector<int> v; // decltype(v) is vector<int>
但如果是 decltype(v[0])呢?此时返回的类型是int&。这是由于vector容器中operator[]返回的是一个T&。这里还需要注意的一点是,对于
std::vector<bool> vb;
decltype(vb[0])并不返回bool&。
decltype与auto
相信很多人都知道模板的强大,结合decltype我们可以让模板变得更加强大。如果我们想返回一个容器内的元素,利用decltype我们可以很轻松的推导出容器内元素的类型。例如
// C++11代码
template <typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i])
{
// Some operations
return c[i];
}
在上面的代码中,auto对类型推导没有任何作用。相反,它表示正在使用C++11尾置返回类型的语法特性。如果将上面代码改为:
template <typename Container, typename Index>
decltype(c[i]) authAndAccess(Container& c, Index i)
{
// Some operations
return c[i];
}
在C++11中无法编译通过,原因是C++的返回值是前置语法,在返回值定义的时候参数变量还不存在。C++11允许对单语句的lambda表达式进行类型推导。在C++14中,拓展为所有lambda和所有函数,包括多个语句(甚至多个返回,提供所有产出相同的推导类型)。这就意味着,在C++14中我们可以忽略尾置返回类型,单单只留下auto,例如:
// C++14中编译通过
template <typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
// Some operations
return c[i];
}
但也同样在C++14中会产生一个问题,这个问题是由auto引起的。在C++11的代码中,我们使用decltype配合auto使用,这时用auto推导出来的类型为T&,例如:
std::vector<int> v = {1, 2, 3};
authAndAccess(v, 0) = 2; // C++11代码编译通过
代码编译通过,这是由于decltype(v[0])的类型为int&,是一个右值引用,相当于
v[0] = 2
但在C++14的代码中
std::vector<int> v = {1, 2, 3};
authAndAccess(v, 0) = 2; // C++14代码编译出错
这是由于auto返回的类型推导会将引用剥离,使得返回的类型是int,而不是int&,具体的auto类型推导请看上一节。这里函数返回的类型为int,是一个右值,而2是一个右值整形。对右值赋值,在C++中这是禁止的,代码编译不能通过。
要想在C++14中实现类似C++11的用法,应该将代码改为:
// C++14中编译通过
template <typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i)
{
// Some operations
return c[i];
}
std::vector<int> v = {1, 2, 3};
authAndAccess(v, 0) = 2; // C++14代码编译通过
decltype(auto)用法
这里讲下为什么会产生这种用法。在C++14中,函数使用auto返回类型,会导致引用剥离的现象,为了指定一个函数(表达式,变量)准确的返回相同的类型(例如:表达式c[i]的类型)。C++标准的制定者,在C++14中通过使用decltype(auto)来实现这一目的。
由于不局限于函数,它也可以用在变量中,例如:
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction:
// myWidget1's type is Widget
decltype(auto) myWidget2 = cw // decltype type deduction:
// myWidget2's type is const Widget&
回过头来看看,C++14的代码:
template <typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);
容器由非常量左值引用(lvalue-reference-to-non-const)传递,但像第一节所讲的如果是一个右值传递呢?现在我们修改一下代码
template <typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i); // c is now a universal reference
c现在是一个通用引用。在这个模板中,我们不知道正在操作的容器的类型,这也意味着我们对它使用的索引对象的类型同样无知。为了使得它满足各种各样的情况,我们需要再次修改代码:
// C++14代码
template <typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
// Some operations
return std::forward<Container>(c)[i];
}
// C++11代码
template <typename Container, typename Index>
auto authAndAccess(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i])
{
// Some operations
return std::forward<Container>(c)[i];
}
至于std::forward的用法,可以自行查找资料
decltype(x) 和 decltype((x))的区别
例如:
int x = 0;
如果使用:
decltype(x);
由于x是一个变量的名字,所以decltype(x)是一个int
如果使用:
decltype((x));
由于(x)是一个表达式,x是一个左值,在C++中,(c)也是一个左值。因此,decltype((x))是一个int&。
decltype(auto) f1()
{
int x = 0;
// ...
return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
int x = 0;
// ...
return (x); // decltype((x)) is int&, so f2 returns int&
}
这里要注意的是,f2和f1不单单是返回类型的不同,f2也返回了一个局部变量的引用,这将导致未定义的行为,要谨慎使用。