C++精要分析decltype的作用及用法
获取表达式的类型
在编写程序的过程中,我们可能会有一种需求,就是希望可以根据一个变量的类型,来定义具有相同类型的变量。例如定义int x = 0;
,那么我们是否可以不使用int
关键字,仅使用x就定义一个新的整型变量y呢?
答案是可以的,C++11新增的decltype
关键字就是干这个用的。上述需求用代码实现如下:
int x = 0; decltype(x) y = 2; // y的类型为int
decltype
是在编译期用来推导表达式类型的。其语法格式为:decltype(expression)
。大家可以看到,decltype
是可以对一个表达式取类型的,并不仅是单个的变量。所以,把形式再扩展一下:
int x = 0; decltype(x) y = 2; decltype(x + y) z = 3; // z的类型为int
到这一步,相信大家已经可以基本掌握其特性,在工作中能运用了。当然,仅知道这些还是不够的,作为C++程序员怎么能停下探索的脚步呢。
推导规则
decltype
的推导规则,是面试中最容易挖坑的地方。你要是不信,那就先回答下面这些问题吧:
const int func_one(); decltype(func_one()) a1 = 0; // a1是什么类型? struct TestData { int x;}; cosnt TestData b_node = TestData(); decltype(b_node.x) b1 = 0; // b1是什么类型? decltype((b_node.x)) b2 = b1; // b2是什么类型? int n = 0, m = 0; decltype(n + m) c1 = 0; // c1是什么类型? decltype(n += m) c2 = c1; // c2是什么类型?
注释中有五个问题,如果你全都答对而且不是蒙的,那请开班授课吧,我会第一个报名。先公布一下答案,看看自己答对了多少吧。
a1: int
b1: int
b2: const int &
c1: int
c2: int &
如果这个答案让你觉得有些晕头转向,不要紧,先来看下规则描述吧:
- 如果expression表达式是标识符、类访问表达式,
decltype(exp)
和exp的类型一致; - 如果expression是函数调用,则
decltype(exp)
和返回值的类型一致; - 其他情况,如果expression是一个左值,则
decltype(exp)
是exp类型的左值引用,否则和exp类型一致。
现在,将规则理解之后,再看一遍代码和答案,是否找到规律了呢?相信在面试中遇到这样的问题,你已经可以应对自如了。
返回类型后置
在说明decltype
的一个高级用法之前,我们先了解C++11的一个新特性,就是函数返回类型后置。与之相对的,就是返回类型前置,这是我们最熟悉的函数声明格式。例如:int foo();
而返回类型后置的示例如下:
auto foo() -> int { return 0; }
在上面的代码中,auto关键字是一个占位符,int是其实际返回类型。初看起来,后置声明与前置声明在功能上是一样的,那它难道是一个多余的设计吗?它当然自有用武之地。
在需要返回比较复杂的类型时,使用后置式声明可以简化代码并使其可读性更好。例如要返回的类型是函数指针,前置式声明就必须先用typedef
进行预定义,否则语法不允许。而后置式声明则可以直接实现,无需预定义,如下代码所示。
int exam(bool b) { int ret = -1; if (b) { ret = 0; } else if (!b) { ret = 1; } return ret; } auto foo() -> int(*)(bool) { return exam; } int main() { auto fn = foo(); cout << fn(true) << endl; cout << fn(false) << endl; }
高级用法
现在正式介绍decltype
与函数返回类型后置相结合,在模板编程中的用法,就是用于推导函数模板的返回类型。之所以将此归为高级用法,也是因为模板在C++中虽然功能强大,但属实复杂不易理解。一般是编写基础功能库或是算法库时,使用到模板的特性。
先看一段示例代码:
template<class T1, class T2> auto sum(T1 t1, T2 t2) -> decltype(t1 + t2) { return t1 + t2; } int main() { auto ret = sum(4.6, 123); cout << ret << endl; }
其精髓之处,就在于可以灵活支持T1与T2不同类型的组合,而不必为每种返回类型都去写一个实现。例如int+int, double+int, string+string
等各种组合情况。
但如果是把decltype(t1 + t2)
以前置写法替换auto,则会产生编译错误。道理很简单,编译器对t1+t2
的参数类型还一无所知,只有在解析到返回值时,才能最终确定函数的返回类型,这就是decltype
加上函数返回类型后置在模板编程中的妙用。