理解decltype

decltype 只是表达出变量名或表达式的类型,例如:

class Person;
Person objPerson; // decltype(objPerson)是Person

int nTmp = 10; // decltype(nTmp)是int

bool IsDie(); // decltype(IsDie())是bool

在C++11中, decltype 最主要的用处可能就是用来声明一个函数模板,在这个函数模板中返 回值的类型取决于参数的类型,通过一个例子认识decltype,假设我们想写一个模板函数,这个函数中接受一个支 持方括号索引(也就是”[]”)的容器作为参数,验证用户的合法性后返回索引结果。这个函数 的返回值类型应该和索引操作的返回值类型是一样的,操作[] 作用在一个对象类型为 T 的容器上得到的返回值类型为 T&

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

此处使用了C++11的尾随返回类型技术,即函数的返回值类型在函数参数之后声明(“->”后边)。尾随返回类型的一个优势是在定义返回值类型的时候使用函数参数。例如在函数 GetValAtContainer 中,我们使用了 c 和 i定义返回值类型。在传统的方式下,我们在函数名前面声明返回值类型, c 和 i 是得不到的,因为此时 c 和 i 还没被声明。

C++11 允许单语句的 lambda 表达式的返回类型被推导,在 C++14 中之中行为被拓展到包括多 语句的所有的 lambda·表达式和函数。在上面 GetValAtContainer 中,意味着在 C++14 中我们可以忽略尾随返 回类型,仅仅保留开头的 auto。使用这种形式的声明,意味着将会使用类型推导,而我们知道对使用auto来表明函数返回类型的情况,编译器使用模板类型推导。 对绝大部分对象类型为 T 的容器, [] 操作子返回 的类型是 T& ,在模板类型推导的过程中,初始表达式的引用会被忽略。例如下面的调用

std::deque<int> d;
GetValAtContainer(d,1) = 1000;

d[1] 返回的是 int& ,但是 GetValAtContainer 的 auto 返回类型声明将会剥离这个引用, 从而得到的返回类型是 int 。 int 作为一个右值成为真正的函数返回类型。上面的代码尝试 给一个右值 int 赋值为1000。这种行为是在 C++ 中被禁止的,所以代码无法编译通过

为了让 GetValAtContainer 按照我们的预期工作,我们需要为它的返回值使用 decltype 类型推 导,即指定 GetValAtContainer 要返回的类型正是表达式 c[i] 的返回类型,C++14支持decltype(auto)而使用decltype类型推导规则,所以上面的模板函数可以这样写:

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

decltype(auto) 并不仅限使用在函数返回值类型上。当想对一个表达式使用 decltype 的推 导规则时,它也可以很方便的来声明一个变量:

Person objPerson;
const Person& objRefPerson = objPerson;
auto objPerson1 = objRefPerson;
decltype(auto) objPerson2 = objRefPerson;

对于这样的推导auto objPerson1 = objRefPerson;objPerson1的类型是 Person

对于这样的推导decltype(auto) objPerson2 = objRefPerson;objPerson2的类型是const Person&

上面提到的模板函数对参数类型支持有限,更加通用的写法是既支持左值引用又支持右值引用,C++11实现:

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

C++14实现:

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

这里使用了forward,关于forward可以参考这篇文章,对于这个模板函数提供这样的支持,存在着风险,如果传入的是个临时对象(临时对象是个右值),在函数最后临时对象释放,而返回的又是个引用,可能导致未定义行为。不过可以在其他语意的函数实施这样的写法

查看类型


查看推导出来的类型可以通过IDE、或者输出的方式,但是IDE的提示有可能不精确,对于运行时输出还是使用boost库吧,例如:

#include <boost/type_index.hpp>

template<class T>
void TemplateFun(const T& param)
{
	using boost::typeindex::type_id_with_cvr;

	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;

	cout<<"param = "<<type_id_with_cvr<decltype(param)>().pretty_name()<<endl;
}

这个模板函数 boost::typeindex::type_id_with_cvr 接受一个类型参数(我们想知道的类型信息)来正常工作,它不会去除 const , volatile 或者引用特性(这也就是模板中的“ cvr ”的意思)。返回的结果是个 boost::typeindex::type_index 对象,其中的 pretty_name 成员函数 产出一个 std::string 包含一个对人比较友好的类型展示的字符串

最后


对一个变量名使用decltype得到这个变量名的声明类型。变量名属于左值表达式,然而,对于一个比变量名更复杂的左值表达式,decltype 保证返回的类型是左值引用。因此说,如果一个非变量名的类型为T的左值表达式,decltype报告的类型是T& ,例如:

int nTmp = 100;
// decltype(nTmp)是int
// decltype((nTmp))是int&,给一个变量名加上括号会改变 decltype 返回的类型

总结下来:

  1. decltype 几乎总是得到一个变量或表达式的类型而不需要任何修改

  2. 对于非变量名的类型为 T 的左值表达式, decltype 总是返回 T&

  3. C++14 支持 decltype(auto) ,它的行为就像 auto ,从初始化操作来推导类型,但是它推导类型时使用 decltype 的规则

  4. 类型推导的结果常常可以通过IDE的编辑器,编译器错误输出信息和Boost TypeIndex库的结果中得到

  5. 一些工具的结果不一定有帮助性也不一定准确,所以对C++标准的类型推导法则加以理解是很有必要的