1.1 类型推导
1.1.1 auto类型推导
1.auto关键字的新意义
auto关键字是一个类型指示符,用来提示编译器对此类型的变量做类型的自动推导。
auto x = 5; //OK:x是int 类型
auto pi = new auto(1); //OK:pi被推导为int*
const auto v = &x,u = 6; //OK:v是const int 类,iu是const int类型
static auto y = 0.0; //OK:y是double类型
auto int r; //error:auto不再表示存储类型提示符
auto s; //error:auto 无法推导出s的类型
2.auto的推导规则
它可以同指针,引用结合起来使用,还可以带上cv限定符(cv-qualifier,const和volatile限定符的统称)
int x = 0;
auto * a = &x; //a->int*,auto被推导为int
auto b = &x; //b->int*,auto被推导为int*
auto &c = x; //c->int&被推导为int
auto d = c; //d->int,auto被推导为int
const auto e = x; //e->const int
auto f = e; //f->int
const auto g = x; //g->const int &
auto& h = g; //h->const int&
有以下规则:
(1)当不声明为指针或引用时,auto的推导结果和初始化表达式抛弃引用和cv限定符后类型一致。
(2)当声明为指针或引用时,auto的推导结果将保持初始表达式的cv属性。
3,auto的限制
void func(auto a = 1){} //error:auto不能用于函数参数
struct Foo
{
auto var1_ = 0; //error:auto不能用于非静态成员变量
static const auto var2_ = 0; //OK:var2_->static const int
};
template <typename T>
struct Bar{};
int main()
{
int arr[10] = {0}
auto aa = arr; //OK:aa->int *
auto rr[10] = arr; //error:auto无法定义数组
Bar<int> bar;
Bar<auto> bb = bar; //error:auto 无法推出模板参数
return 0;
}
4,什么时候用auto
在C++11之前,定义了一个stl容器后,在遍历的时候常常会写这样的代码:
#include<map>
int main(void)
{
std::map<double,double> resultMap;
//...
std::map<double,double>::iterator it = resultMap.begin();
for(;it != resultMap.end();++it)
{
//do something
}
return 0;
}
使用了auto之后的写法:
#include<map>
int main(void)
{
std::map<double,double> resultMap;
//...
for(auto it = resultMap.begin();it != resultMap.end();++it)
{
//do something
}
return 0;
}
1.1.2 decltype关键字
1.获知表达式的类型
C++新增了decltype关键字,用来编译时推导出一个表达式的类型。它的语法格式如下:
decltype(exp)
其中,exp表示一个表达式(expression)。
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**
2.decltype的推导规则
推导规则1:exp是标识符,类访问表达式,decltype(exp)和exp的类型一致
推导规则2:exp是函数调用,decltype(exp)和返回值类型一致
推导规则3:其他情况,若exp是一个左值,则decltype(exp)是exp类型的左值引用,否则和exp类型一致。
(1)标识符表达式和类访问表达式
class Foo
{
public:
static const int Number = 0;
int x;
} ;
int n = 0;
volatile const int & x = n;
decltype(n) a = n; //a->int
decltype(x) b = n; //a->const volatile int&
decltype(Foo::number) c = 0; //c->const int
Foo foo;
decltype(foo.x) d = 0; //d->int ,类访问表达式
a,b,c,d均符合推导规则1
(2)函数调用
int& func_int_r(void); //左值(lvalue,可简单理解为可寻址值)
int&& func_int_rr(void); //x值(xvalue,右值引用本身是一个xvalue)
int func_int(void); //纯右值(prvalue,后续内容)
const int& func_cint_r(void); //左值
const int&& func_cint_rr(void); //右值
const int func_cint(void); //纯右值
const Foo func_cfoo(void); //纯右值
//下面是测试语句
int x = 0;
decltype(func_int_r()) a1 = x; //a1->int &
decltype(func_int_rr()) b1 = 0; //b1->int &&
decltype(func_int()) c1 = 0; //c1->int
decltype(func_cint_r()) a2 = x; //a2->const int &
decltype(func_cint_rr())b2 = 0; //b2->const int &&
decltype(func_cint()) c2 = 0; //c2->int
decltype(func_cfoo()) ff = Foo(); //ff->const Foo
按照推导规则2,decltype的结果和函数的返回值类型保持一致。这里需要注意的是,c2是int 而不是 const int。这是因为函数返回的int是一个纯右值。对于纯右值,只有类类型可以携带cv限定符,此外则一般忽略掉cv限定符。
(3)带括号的表达式和加法运算表达式
struct Foo{
int x;
};
const Foo foo = Foo();
decltype(foo.x) a = 0; //a->int
decltype((foo.x)) b = a; //b->const int &
int n = 0, m= 0;
decltype(n+m) c = 0; //c->int
decltype(n+=m)d = c; //d->int&
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&。
3,decltype的实际应用
范式编程定义可能存在问题的实例
#include <vector>
template <class ContainerT>
class Foo{
typename ContainerT::iterator it_; //类型定义可能有问题
public:
void func(ContainerT& container)
{
it_ = container.begin();
}
//...
};
int main(void)
{
typedef const std::vector<int> container_t;
container_t arr;
Foo<container_t> foo;
foo.func(arr);
return 0;
}
ContainerT::iterator并不能包括所有的迭代器类型,当ContainerT是一个const类型时,应当使用const_iterator,可以增加一个模板特化:
template <class ContainerT>
class Foo<const ContainerT>{
typename ContainerT::const_iterator it_;
public:
void func(Const ContainerT& container)
{
it_ = container.begin();
}
//...
};
若const类型的特化只是为了配合迭代器的类型限制,Foo的其他代码也不得不重新写一次。有了decltype,就可以这样写:
template <class ContainerT>
class Foo{
decltype(ContainerT().begin()) it_;
public:
void func(ContainerT& container)
{
it_ = container.begin();
}
//...
};
1.1.3返回类型后置语法——auto和decltype的结合使用
·考虑一下情景:
template <typename R,typename T,typename U>
R add(T t, U u)
{
return t+u;
}
int a = 1; float b = 2.0;
auto c = add<decltype(a+b)>(a,b);
我们并不关心a+b的类型是什么因此,只需要通过decltype(a+b)直接返回值类型即可。但是上述方法太过不方便。可以尝试:
template <typename T,typename U>
decltype(t+u) add(T t, U u) //error:t,u尚未定义
{
return t+u;
}
显然编译不通过,因为在t,u在参数列表中,C++的返回值是前置语法,在返回值定义的时候参数变量还不存在。
可行的写法如下:
template <typename T,typename U>
decltype(T()+U()) add(T t, U u)
{
return t+u;
}
考虑到T,U可能是无参构造函数的类,正确的写法应该是:
template <typename T,typename U>
decltype((*(T*)0)+(*(U*)0)) add(T t, U u)
{
return t+u;
}
在C++11中增加了返回类型后置的语法,将delctype和auto结合起来完成返回值的推导。
因此,上述add函数可以写成:
template <typename T,typename U>
auto add(T t, U u) ->decltype(t+u)
{
return t+u;
}