auto用于通过一个表达式在编译时确定待定义的变量类型,auto所修饰的变量必须被初始化,编译器需要通过初始化来确定auto所代表的类型,即必须要定义变量。若仅希望得到类型,而不需要(或不能)定义变量的时候就要使用C++11新增的decltype关键字了,用于在编译时推导出一个表达式的类型。它的语法格式如下:
decltype (exp)
其中,exp表示一个表达式。
从格式上来看,decltype很像sizeof---用来推导表达式类型大小的操作。类似于sizeof,decltype的推导过程是在编译期完成的,并且不会真正计算表达式的值。
int x = 0;
decltype (x) y = 1; // y -> int
decltype (x + y) z = 0; // z -> int
const int& i = x;
decltype (i) j = y; // j -> const int &
const decltype (z) * p = &z; // *p -> const int, p -> const int *
decltype (z) * pi = &z; // *pi -> int,pi -> int*
decltype (pi) * pp = π // *pp -> int*,pp -> int **
y和z的结果表明decltype可以根据表达式直接推导出它的类型本身。
j的结果表明decltype通过表达式得到的类型,可以保留住表达式的引用以及const限定符。实际上,对于一般的标记符表达式,decltype将精确的推导出表达式定义本身的类型,不会像auto那样在某些情况下舍弃引用和cv限定符。
p,pi的结果表明decltype可以像auto一样,加上引用和指针,以及cv限定符。
pp的推导则表明,当表达式是一个指针的时候,decltype仍然推导出表达式的实际类型(指针类型),之后结合pp定义的指针标记,得到的pp是一个二维指针类型。
介绍decltype(exp)的推导规则:
推导规则1:exp是标识符,类访问表达式,decltype(exp)和exp的类型一致。
推导规则2:exp是函数调用,decltype(exp)和返回值的类型一致。
推导规则3:其他情况,若exp是一个左值,则decltype(exp)是exp类型的左值引用,否则和exp类型一致。
为了更好的讲解这些规则的使用场景,下面根据上面的规范分3种情况一次讨论:
1、标识符表达式和类访问表达式。
2、函数调用(非标识符表达式,也非类访问表达式)。
3、带括号的表达式和加法运算表达是否(其他情况)。
标识符表达式和类访问表达式示例:
class Foo
{
public static const int Number = 0;
int x;
};
int n = 0;
volatile const int& x = 0;
decltype(n) a = n; //a -> int
decltype(x) b = n; //b -> volatile const int &
decltype(Foo::Number) c = 0; // c -> const int
Foo foo;
decltype(foo.x) d = 0; // d -> int,类访问表达式
变量a,b,c保留了表达式的所有属性(cv,引用)。这里的结果是很简单的,按照推导规则1,对于标识符表达式而言,decltype的推导结果就和这个变量的类型定义一致。
d是一个类访问表达式,也符合推导规则1。
函数调用示例:
int & func_int_r(); //左值(可简单理解为可寻址值)
int func_int(); //纯右值
const int& func_cint_r(); //左值
const int func_cint(); //纯右值
const Foo func_cfoo(); //纯右值
int x = 0;
decltype (func_int_r()) a1 = x; //a1 -> int &
decltype(func_int()) c1 = 0; // c1 -> int
decltype (func_cint_r()) a1 = x; //a1 -> const int &
decltype(func_cint()) c1 = 0; // c1 -> int
decltype(func_cfoo()) ff = Foo; //ff -> const Foo
可以看到,按照推导规则2,decltype的结果和函数的返回值类型保持一致。
带括号的表达式和加法运算表达式示例:
struct Foo {int x;};
const Foo foo = Foo();
decltype(foo.x) a = 0; // a -> int
decltype((foo.x)); // b -> const int &
int n = 0, m = 0;
decltype(n + m) c = 0; // c -> int
decltype(n += m) d = c; // d -> int&
a和b的结果:仅仅多了一对括号,它们得到的类型却是不同的。
a的结果很直接的,根据推导规则1,a的类型就是foo.x的定义类型。
b的结果并不适用于推导规则1和2.根据foo.x是一个左值,可知括号表达式也是一个左值。因此可以按照推导规则3,知道decltype的结果将是一个左值引用。
foo的定义是const Foo,所以foo.x是一个const int类型左值,因此decltype的推导结果是const int&.
同样,n+m返回一个右值,按照推导规则3,decltype的结果为int。
最后,n + m返回一个左值,按照推导规则3,decltype的结果为int &。