详解C++11中的类型推断
Shawn-Summer 人气:0C++11中的类型推断
C++11中为了更好的支持泛型编程,提供了 auto
和decltype
两个关键词,目的就是提供编译阶段的自动类型推导。
1.auto
关键词的新意义
在C++98中,auto
是一个类型修饰符,用以显式声明自动变量(局部变量的),而在C++11中,这一用法已经弃用,现在auto
用于声明变量。
1.1 auto
声明变量
我们通过几个例子看一下,auto
声明变量时的规则。
int x; int *y; double foo(); int& bar(); auto *a=&x;//int * auto &b=x;//int & auto c=y;//int * auto *d=y;//int * auto e=bar();// int auto &f=bar();//int &
1.如果使得auto
声明的变量是引用类型,必须使用auto &
.
2.如果变量本身是指针类型,则auto *
和auto
是一样的,这里*
变成冗余符号
double foo(); float * bar(); const auto a=foo();//const double const auto & b=foo();//const double & volatile auto * const c=bar();//volatile float * const auto d=a;//double auto &e=a;//const double & auto f=c;//volatile float * volatile auto &g=c;//volatile float * &
3.声明为auto
的变量不能从其初始化表达式中带走顶层cv
限定符.
auto i=1,j=1.12f;//编译错误
4.auto
可以声明多个变量,不过这些变量必须类型相同
1.2 auto
无法推导的情况
#include<vector> using namespace std; void fun(auto x=1){}//错误 struct str { auto var=10;//错误 }; int main() { char x[3]; auto y=x; auto z[3]=x;//错误 vector<auto>V={1,2,3};//错误 }
auto
不能作为函数形参(这是模板函数的事情)类中,auto
不能用来声明非静态成员auto
不能声明数组auto
不能用于模板实参
2.decltype
类型推导
decltype
和auto
都是用来类型推导的,不过 decltype
的功能更加强大 ,下面就是decltype
的简单用法
2.1 decltype
的应用场景
一种情况是,decltype
和auto
一样来声明变量:
#include<typeinfo> #include<iostream> using namespace std; int main() { int i; decltype(i) j=0; cout<<typeid(j).name()<<endl;//i float a; double b; decltype(a+b) c; cout<<typeid(c).name()<<endl;//d }
另一种情况是:typedef
或using
配合使用decltype
using size_t=decltype(sizeof(0)); using ptrdiff_t=decltype((int*)0-(int*)0); using nullptr_t=decltype(nullptr);
顺便一提,在C++11中,using
已经完美替代typedef
:
#include<iostream> #include<type_traits> #include<string> #include<map> using namespace std; typedef unsigned int UINT; using uint=unsigned int; template<typename T> using Mapstring=map<T,char*>; Mapstring<int> number_string; int main() { cout<<is_same<uint,UINT>::value<<endl;//1 cout<<is_same<map<int,char*>,decltype(number_string)>::value<<endl;//1 }
typedef
能干的事情,using
都能干,但是using
能干的,比如给模板取一个别名,typedef
做不到
decltype
的另一种功能就是给匿名的结构体或者枚举推导类型来重用,
enum class {K1,K2,K3} anon_e; decltype(anon_e) as;
2.2 decltype
比auto
更加精确
首先decltype
和auto
最明显的不同就是,decltype(e)
它和sizeof()
差不多,可以接收一个参数,不过这里我讲的不同是,同一个变量,使用decltype
和auto
得到的结果不同。
说直接点,decltype
的类型推断比auto
准确
const int ic=0; decltype(ic) a;//const int auto b=ic;//int volatile int iv; decltype(iv) c;//volatile int auto d=iv;//int struct S { int i; }; const S cs={0}; decltype(cs.i) e;//int
auto
它不能带走变量的顶层cv
限定符,而decltype(e)
却可以带走e
的cv
限定符,所以说,decltype
的类型推断更加准确。还要一点细节,就是说类本身是用cv
限定符修饰的,而类成员使用decltype
时确推断不出来。
我们知道,auto
只能带走指针类型,却无法带走引用类型,而decltype
就可以同时带走引用和指针
#include<iostream> #include<type_traits> using namespace std; int main() { int i=1; decltype(i) & var1=i;// int & cout<<is_lvalue_reference<decltype(var1)>::value<<endl;//1 int &j=i; decltype(j) var2=i; decltype(j)& var3=i; cout<<is_same<decltype(j),decltype(j)&>::value<<endl;//1,`&`的冗余 int* p=&i; decltype(p) var4=&i;//int * decltype(p)* var5=&p;//int ** const int k=1; const decltype(k) var6=1;//const int `const`冗余 }
上面这段代码,信息量很大
首先,decltype(e)
可以带走e
的引用和指针类型
其次,decltype(e)
会对&
和cv
限定符产生冗余,而不会对*
产生冗余
最后,如果不确定decltype(e)
的类型,可以使用<type_traits>
头文件中的一些方法
总之,就是一句话:decltype(e)
能直接返回e
的准确类型
但是,如果decltype
更加优越,那么为什么还要auto
呢?
一种说法是
auto
用法更加简单,更重要的原因是,auto
和lambda
函数的配合使得,C++11相对于C++98,变得脱胎换骨,我个人认为C++11最重要的就是lambda
函数。
2.3 decltype
对于表达式的推断
我们知道在decltype(e)
中,e
被要求是一个表达式,即expression
,而在上面我们所讲的e
通常是一个名称,即id_expression
,如果e
是一个非名称的表达式,那么推断结果也会不同
int i; decltype(i) a;//int decltype((i)) b;//int &
在上面例子中,
i
就是一个id_expression
,而(i)
它不是id_expression
,而是一个左值表达式,所以上述推导结果会不同。
我们直接来看decltype(e)
的推导细则
- 如果
e
是id_expression
或者类成员表达式,decltype(e)
的结果就是e
本身的类型 - 否则,如果
e
是左值表达式,设它的类型是T
,那么decltype(e)
的结果就是T&
- 否则,如果
e
是将亡值,设它的类型是T
,那么decltype(e)
的结果就是T&&
- 否则,如果
e
是纯右值,设它的类型是T
,那么decltype(e)
的结果就是T
int i=4; int arr[5]={0}; int *ptr=arr; struct S { double d; }s; void foo(int); void foo(double); int && Rref();//函数返回值是将亡值 const bool Func(int); decltype(arr) var1;//int[5] decltype(ptr) var2;//int * decltype(s.d) var3;//double decltype(foo) var4;//无法通过编译,foo被重载 decltype(Rref()) var5;//int && decltype(true? i:i) var6;//int& decltype((i)) var7;//int & decltype(++i) var8;//int & decltype(arr[3]) var9;// int & decltype(*ptr) var10;//int & decltype("abc") var11;//const char(&) [4] decltype(1) var12;//int decltype(i++) var13;//int decltype(Func(1)) var14=true;//const bool
3.追踪返回类型
auto
和decltype
可以进行配合使用,来实现泛型编程中的追踪返回类型
template<class T1,class T2> decltype(t1+t2) sum(T1& t1,T2& t2) { return (t1+t2); }
上面这段代码,想法狠简单,但是它都无法通过C++11和C++98中的编译器,因为编译器是从左往右读的,读到
decltype(t1+t2)
时,t1
和t2
没有声明,所以无法通过编译,我们可以通过返回类型后置的方法实现上述功能
template<typename T1,typename T2> auto sum(T1& t1,T2& t2)->decltype(t1+t2) { return (t1+t2); }
上面就是追踪返回类型,最终
sum
函数的返回类型由decltype(t1+t2)
确定,而不是auto
确定,如果我们把->decltype(t1+t2)
去掉,那么最终返回类型就由auto
指定,我们其实很不希望这样,因为auto
并不精确,decltype
更加精确。
追踪返回类型其实就是返回类型后置,它的还有一种用法就是,提高函数指针的可读性:
#include<type_traits> #include<iostream> using namespace std; int (*(*pf())())(){ return nullptr; } auto pf1() ->auto (*)() -> int (*)() { return nullptr; } int main() { cout<<is_same<decltype(pf),decltype(pf1)>::value<<endl;//1 }
上述代码中,
pf
和pf1
都是函数指针,其返回的也是一个函数指针,该函数指针又返回一个函数指针,不过明显pf1
的定义方式可读性更高。
加载全部内容