4.3.4 cv限制符的继承与冗余的符号

与auto类型推导时不能“带走”cv限制符不同,decltype是能够“带走”表达式的cv限制符的。不过,如果对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员不会继承const或volatile限制符。我们可以看看如代码清单4-27所示的例子。

代码清单4-27


include <type_traits>

include <iostream>

using namespace std;

const int ic=0;

volatile int iv;

struct S{int i;};

const S a={0};

volatile S b;

volatile S*p=&b;

int main(){

cout<<is_const<decltype(ic)>::value<<endl;//1

cout<<is_volatile<decltype(iv)>::value<<endl;//1

cout<<is_const<decltype(a)>::value<<endl;//1

cout<<is_volatile<decltype(b)>::value<<endl;//1

cout<<is_const<decltype(a.i)>::value<<endl;//0,成员不是const

cout<<is_volatile<decltype(p->i)>::value<<endl;//0,成员不是volatile

}

//编译选项:g++ -std=c++11 4-3-12.cpp


代码清单4-27的例子中,我们使用了C++库提供的is_const和is_volatile来查看类型是否是常量或者易失的。可以看到,结构体变量a、b和结构体指针p的cv限制符并没有出现在其成员的decltype类型推导结果中。

而与auto相同的,decltype从表达式推导出类型后,进行类型定义时,也会允许一些冗余的符号。比如cv限制符以及引用符号&,通常情况下,如果推导出的类型已经有了这些属性,冗余的符号则会被忽略,如代码清单4-28所示。

代码清单4-28


include <type_traits>

include <iostream>

using namespace std;

int i=1;

int&j=i;

int*p=&i;

const int k=1;

int main(){

decltype(i)&var1=i;

decltype(j)&var2=i;//冗余的&,被忽略

cout<<is_lvalue_reference<decltype(var1)>::value<<endl;//1,是左值引用

cout<<is_rvalue_reference<decltype(var2)>::value<<endl;//0,不是右值引用

cout<<is_lvalue_reference<decltype(var2)>::value<<endl;//1,是左值引用

decltype(p)*var3=&i;//无法通过编译

decltype(p)var3=&p;//var3的类型是int*

autov3=p;//v3的类型是int

v3=&i;

const decltype(k)var4=1;//冗余的const,被忽略

}

//编译选项:g++ -std=c++11 4-3-13.cpp


在代码清单4-28中,我们定义了类型为decltype(i)&的变量var1,以及类型为decltype(j)&的变量var2。由于i的类型为int,所以这里的引用符号保证var1成为一个int&引用类型。而由于j本来就是一个int&的引用类型,所以decltype之后的&成为了冗余符号,会被编译器忽略,因此j的类型依然是int&。

这里特别要注意的是decltype(p)的情况。可以看到,在定义var3变量的时候,由于p的类型是int,因此var3被定义为了int*类型。这跟auto声明中,也可以是冗余的不同。在decltype后的*号,并不会被编译器忽略。

此外我们也可以看到,var4中const可以被冗余的声明,但会被编译器忽略,同样的情况也会发生在volatile限制符上。

总的说来,decltype算得上是C++11中类型推导使用方式上最灵活的一种。虽然看起来它的推导规则比较复杂,有的时候跟auto推导结果还略有不同,但大多数时候,我们发现使用decltype还是自然而亲切的。一些细则的区别,读者可以在使用时遇到问题再返回查验。而下面的追踪返回类型的函数定义,则将融合auto、decltype,将C++11中的泛型能力提升到更高的水平。