参考文章:https://blogs.oracle.com/pcarlini/entry/c_11_tidbits_decltype_part
decltype
是 GCC 实现的第一个 C++ 11 新特性。它实际上起源于一个相当古老的 GNU 扩展关键字——__typeof__
。这个非标准关键字也能够在 C 语言中使用,GNU Compiler Collection 的专业用户可能对它更熟悉一些。2008 年,GCC 4.3.x 就实现了这个特性,同时去除了__typeof__
的一些缺点。现在,decltype
和__decltype
两个关键字在 GCC 中都适用;前者只能用在 C++ 11 模式下,后者可以同时应用于 C++ 11 和 C++ 98 模式。__typeof__
则已经停止使用。
下面来看看decltype
的基本使用。简单来说,decltype
关键字用于查询表达式的类型。不过,这只是其基本用法。当这个简单的表述同 C++ 11 的其它特性结合起来之后,一些意想不到的有趣用法就此产生。
decltype
的语法是
decltype ( expression )
这里的括号是必不可少的。根据前面的说法,decltype
的作用是“查询表达式的类型”,因此,上面语句的效果是,返回 expression 表达式的类型。注意,decltype 仅仅“查询”表达式的类型,并不会对表达式进行“求值”。
先看一个最基础的例子:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1; // const int&& (1) decltype(i) x2; // int (2) decltype(a->x) x3; // double (3) decltype((a->x)) x4; // double& (4)
传统的__typeof__
有一个颇为诟病的地方,在于不能很好地处理引用类型。而decltype
则没有这个问题。而decltype
实际上更好地融入了 C++ 11 类型系统,来看一个比较复杂的例子:
int i; float f; double d; typedef decltype(i + f) type1; // float typedef decltype(f + d) type2; // double typedef decltype(f < d) type3; // bool
上面的例子清楚看出,decltype 能够很好地处理类型转换这里问题。
或许你会对上面代码中的 (4) 心生疑问。为什么decltype((a->x))
会是double&
?这是由 decltype 的定义决定的。decltype 判别的规律还是比较复杂的:
对于decltype( e )
而言,其判别结果受以下条件的影响:
- 如果
e
是一个标识符或者类成员的访问表达式,则decltype(e)
就是e
所代表的实体的类型。如果没有这种类型或者e
是一个重载函数集,那么程序是错误的(上例中的 (2) 和 (3)); - 如果
e
是一个函数调用或者一个重载操作符调用(忽略e
外面的括号),那么decltype(e)
就是该函数的返回类型(上例中的 (1)); - 如果
e
不属于以上所述的情况,则假设e
的类型是 T:当e
是一个左值时,decltype(e)
就是T&
;否则(e
是一个右值),decltype(e)
是T(上例中的 (4) 即属于这种情况。在这个例子中,e
实际是(a->x)
,由于有这个括号,因此它不属于前面两种情况,所以应当以本条作为判别依据。而(a->x)
是一个左值,因此会返回double &
)。
说了这么多,decltype
到底有什么用?事实上,decltype
在复杂的模板编程中非常有用。不过,这需要结合我们前面提到的auto
关键字。举个经典的例子,请看下面的代码:
template<typename T, typename U> ??? foo(T t, U u) { return t + u; }
问号这里该填写什么呢?
问题的关键在于,我们正在处理模板,因此我们根本不知道 T 和 U 的实际类型。即使这两个模板值实际都是 C++ 内置类型,我们也无法确切地知道它们的和的类型。在过去的 GNU C++ 运行时库中,我们可以使用前面说过的__typeof__
扩展,编写相当难看的代码:
template<typename T, typename U> decltype((*(T*)0) + (*(U*)0)) foo(T t, U u) { return t + u; }
而在 C++11 中,我们可以使用auto
关键字:
template<typename T, typename U> auto foo(T t, U u) -> decltype(t + u) { return t + u; }
看起来好多了吧!现在这已经是语言级别的支持了。
最后,我们来看一个更加实际的例子。在 GCC 的 C++11 运行时库中有这么一段代码:
template<typename _IteratorL, typename _IteratorR> inline auto operator-(const reverse_iterator<_IteratorL>& __x, const reverse_iterator<_IteratorR>& __y) -> decltype(__y.base() - __x.base()) { return __y.base() - __x.base(); }
现在,这段代码应该更加清晰了。这实际上解决了 C++ 98 实现的一个真正的 bug。在 GCC 的 C++ 98 版本中,这段代码是这样的:
template<typename _IteratorL, typename _IteratorR> inline typename reverse_iterator<_IteratorL>::difference_type operator-( const reverse_iterator<_IteratorL>& __x, const reverse_iterator<_IteratorR>& __y) { return __y.base() - __x.base(); }
这段代码只有在这两方的difference_type
相同时才适用。
4 评论
纠正个排版错误,简单的例子和稍微复杂的例子的示例代码反了
从代码来看,应该是后者比较复杂吧
template
auto foo(T t, U u) -> decltype(t + u) { return t + u; }
这个看不懂。像lambda又不像lambda。豆豆大哥可以解释一下么么
这是 C++ 11 新增函数声明语法,函数返回值是 auto 类型,实际类型则是在 -> 后面标记出来。可以查阅最新的一篇文章。