Decltype

在C++程序设计语言中，decltype作为类型查询操作符，用于获取表达式的数据类型。C++11标准正式引入decltype，主要是为泛型编程而设计，以解决泛型编程中有些类型由模板参数决定而难以（甚至不可能）书写的痛点。

从语义上看，decltype的设计既适合通用库编写者，也对普通开发者友好。当表达式是变量名或函数参数时，由decltype推导出的类型与源代码中声明的类型完全一致。与sizeof操作符一样，decltype不对操作数求值，仅在编译期分析类型。

## 简要理解

虽然C++规范或大多数的文档及书籍都将decltype定义为一个操作符，但是针对初学者而言，应将此操作符理解为一种特殊的类型声明符，而不是像一些强类型语言那样将此操作符理解为一个带返回值的函数。为说明此问题，举例如下：

对于其它语言的开发者，可能写出如下的代码：

```cpp
#include <iostream>

using std::cout;

int main()
{
	int n = 12;
	cout << decltype(n);//實際上decltype與js或C#的typeof完全不一樣
	getchar();
	return 0;
}
```

相反，可以将decltype视为一种特殊的类型声明方式，C++语言里面类似的功能为自动变量。

举例：设开发人员无法确定一个表达式exp的值类型，然而需要声明一个与之类型匹配的变量，则可以如下声明：

decltype(exp) x;

这与C++11引入的auto自动类型推导形成了互补：auto用于变量初始化时的类型省略，而decltype更擅长提取已有表达式的类型，常用于模板元编程和返回类型后置等场景。

## 设计构想

随着C++引入模板，以及由标准模板库引领的泛型编程逐渐兴起，实现一个能获取表达式类型的机制的需求便由此出现，而这一机制常称为typeof。在泛型编程中，若类型由函数参数决定，则获知之常非易事，在需要获取函数模板实例化的返回类型时尤然。

为此，许多编译器厂商都基于程序语言现有的功能，自行实现了这类操作符，其实现如typeof，以及一些功能有限，但更易移植的实现，以满足这一需求。早在C++还未完全标准化的1997年，布莱恩·帕克（Brian Parker）就基于sizeof操作符，提出了一种可移植的解决方案。对此，比尔·吉本斯（Bill Gibbons）则提出，这一方案仍有诸多限制，而且通常来说，直接引入typeof机制效果都更好。2000年10月，安德烈·亚历山德雷斯库在IT技术杂志《Dr. Dobb's Journal》上评论道：“（若）有typeof（操作符），撰写和理解模板代码就会便易许多。”他也提到“typeof和sizeof（操作符）有相同的后端，（这是）因为sizeof无论如何必须去计算类型。”Andrew Koenig (程序员)与芭芭拉·E·摩（Barbara E. Moo）也谈到内建于程序语言中的typeof功能非常有用，但也提醒道“使用时常会引入一些难以发觉的程序错误，且尚有无法解决的问题（即并非万用）。”并提出可以利用类型转换（如使用标准模板库所提供的typedef），更有效、更通用地实现这一功能。但是，史蒂夫·丹斯特（Steve Dewhurst）则称如此转换“在设计与发布上花费巨大”，而且“采用直接提取表达式类型的方法更简单”（大意）。2011年间，在一片关于C++0x的文章中，克尼格和摩预言道：“decltype将会广泛用于为每日的程序编写提供便利。”

2002年间，比雅尼·斯特劳斯特鲁普提议扩充C++程序语言，为之引入查询表达式类型，以及不必指明类型便可初始化对象的机制。斯特劳斯特鲁普注意到，在GCC与爱迪生设计集团编译器中，typeof所提供的“引用丢弃”（reference-dropping）语义可能存在问题；另一方面，若使用基于表达式左值性、返回一个引用类型的操作符实现之，又难以理解。于是，在呈交给C++标准委员会的初始提案中，便将两种实现方法杂糅起来：只有当表达式的声明类型包含一个引用时，操作符才会返回一个引用类型。为强调推导出的类型能确实反映表达式的声明类型，提案中提议将此操作符命名为decltype。提案还提及了decltype的一项主要设计初衷，也即让编写完美的转发函数成为可能。在编程时，程序员有时需要编写一个泛型转发函数，使之不论以何种类型实例化，都能返回同于包装函数的类型，而若无decltype操作符，就几乎不可能做到这一点。decltype的样例代码如下所示，其中利用了C++11标准中的“返回类型后置”（trailing-return-type）语法。

```cpp
int& foo(int& i);
float foo(float& f);

template <class T> auto transparent_forwarder(T& t) −> decltype(foo(t)) {
  return foo(t);
}
```

decltype便是本段代码的核心部分，用于保存“包装函数是否返回一个引用类型”这一信息。

## 语义

类似于sizeof操作符，decltype不对其操作数求值。粗略来说，decltype(e)返回类型前，进行了如下推导：

- 若表达式e为一个无括号的变量、函数参数、类成员访问，那么返回类型即为该变量或参数或类成员在源程序中的“声明类型”。
- 否则的话，根据表达式的值分类（value categories），设T为e的类型：
- 若e是一个左值（lvalue，即“可寻址值”），则decltype(e)将返回T&。
- 若e是一个临终值（xvalue），则返回值为T&&。
- 若e是一个纯右值（prvalue），则返回值为T。

这些语义是为满足通用库编写者的需求而设计，但由于decltype的返回类型总与对象（或函数）的定义类型相匹配，这对编程新手来说也更为直观。更正式地说，规则1只适用于不带括号的标识符表达式（id-expression）与类成员访问表达式。

示例：括号对类型推导的影响

```cpp
const int&& foo(); //返回右值引用
const int bar();//返回值类型
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1; // 类型为const int&&
decltype(bar()) x2; // 类型为int
decltype(i) x3; // 类型为int
decltype(a->x) x4; // 类型为double
decltype((a->x)) x5; // 类型为const double& ← 注意这里多了一层引用
```

关键差异在于最后一行：最后两个对decltype的调用，返回结果有所不同。这是因为，带括号的表达式(a->x)既非“标识符表达式”，亦非类访问表达式，因而未指向一个命名对象，而是一个左值，于是推导类型便为“指向表达式类型的引用”，亦即const double&。

在2008年12月，雅克·雅尔维（Jaakko Järvi）向标准委员会指出一个问题：在C++中，“带限定标识符”（qualified-id）无法由decltype作成，而这正与“decltype(e)可作‘类型定义名’（typedef-name）看待”的设计初衷不一致。在评论标准委员会为C++0x（C++11前名）制定的正式草案时，日本ISO会员成员提到，“一个定义域操作符（::）不适用于decltype，但本应适用才对。（若能解决这一问题，则）这在需要从实例中获取成员类型（嵌套类型）很有用，如下所示”：

```cpp
vector<int> v;
decltype(v)::value_type i = 0; // int i = 0;
```

这一问题，以及其他相似问题（关于decltype无法在衍生类声明和析构函数调用中使用），都交由大卫·范德沃德（David Vandevoorde）处理，并在2010年3月投票纳入工作日程表。解决办法是：如果表达式是纯用户定义类型（plain user defined type），即不是引用、指针、函数等，则decltype表达式返回类名。这意味着可以访问嵌套类型。甚至可以用`decltype(expr)`作为派生时的基类型。

## 可用性

decltype包含于当前的C++标准C++11中，并由许多编译器以扩展的形式提供：微软在Visual C++ 2010编译器中提供了decltype操作符，基本实现了标准委员会提案中所描述的语义，并且在托管代码或原生代码中都可使用。据其文档称，这一实现“主要对编写模板库的开发者有用。”从2008年3月5日发布的4.3版开始，GCC C++编译器也加入了decltype操作符。这一操作符也已纳入了CodeGear的C++ Builder 2009、Intel C++编译器与Clang。

## 外部链接

- 比雅尼·斯特劳斯特鲁普的“C++11 FAQ”中关于decltype的部分