auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

1
2
auto varname = value;
decltype(exp) varname = value;

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。

auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。decltype 可以写成下面的形式:

1
decltype(exp) varname;

decltype 推导规则

上面的例子让我们初步感受了一下 decltype 的用法,但你不要认为 decltype 就这么简单,它的玩法实际上可以非常复杂。当程序员使用 decltype(exp) 获取类型时,编译器将根据以下三条规则得出结果:

  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。

  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。

    需要注意的是,exp 中调用函数时需要带上括号和参数,但这仅仅是形式,并不会真的去执行函数代码。

  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。

decltype 的实际应用

auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便,你可以转到《C++ auto》查看很多类似的例子,本节仅演示只能使用 decltype 的情形。

我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。下面是一个模板的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <vector>
using namespace std;

template <typename T>
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
typename T::iterator m_it; // 注意这里
};

int main()
{
const vector<int> v;
Base<const vector<int>> obj;
obj.func(v);
return 0;
}

单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。

要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

1
2
3
4
5
6
7
8
9
template <typename T>
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; //注意这里
};

参考:http://c.biancheng.net/view/7151.html