C++11中引入了auto&decltype关键字实现类型推导,通过这两个字可以方便的获取复杂的类型还可以简化代码,提高编码效率。
C++98/03中一直就有auto关键字了,只不过在C++98/03中auto关键字用于标识具有自动存储期的局部变量,它的作用并不大,在实际的编码过程中我们基本没有用过auto关键字,比如
auto int i = 0; //在C++98/03中 它的定义和int i = 0;是一样一样的,因此通常为了方便我们都省略了auto关键字,与之相对应的是
static int i = 0; //标识i是一个静态类型,存储在堆的全局数据区。
在C++11中auto关键字被重新利用了,用作类型推导,比如通常我们定义一个变量:
int i = 0; //强类型定义 改用auto关键字的话就可以 auto i= 20;这样定义了,我们就不需要关系对于每个值定义成什么类型,编译器自动会去根据初始化表达式的值的类型去推导变量的具体类型,因此也就意味着,编译器要推导出变量的类型,那么该变量就必须初始化,否则会编译失败。
看下面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 *类型
static auto y = 0.0; //OK:y是double类型
auto int r; //error 无法通过编译
auto s; //error 无法通过编译
使用VC2013 IDE在main返回之前打个断点,可以再VC的local窗口看到变量的类型,如下:
因此,由上可知,auto不能代表一个实际的类型声明,如s编译错误,仅仅是用作类型声明的一个占位符,在编译期间,编译器会根据后面初始化值的具体类型替换auto占位符。
int x = 0;
auto *a = &x; //a的类型是int*,auto被推导为int
auto b = &x; //b的类型int*, auto被推导为int*
auto &c = x; //c -> int的引用,auto被推导为int
auto d = c; //d -> int auto被推导为int
const auto e = x; //e -> int auto 被推导为int
auto f = e; //f -> int auto被推导为int
const auto & g = x; //g ->const int &
auto &h = g; //h -> const int &
void func(auto a = 1){}; //error: auto不能用于函数形参,哪怕是带默认参数的形参
struct MyStruct
{
auto var = 1; //error: auto不能修饰类或者结构体中的非静态成员
static auto var2 = 2;
};
template
struct Bar{};
int _tmain(int argc, _TCHAR* argv[])
{
int arr[10] = { 0 };
auto a = arr; //OK: a ->int *
auto r[10] = arr; //error : auto不能定义数组
Bar bar;
Bar bb = bar; //error: auto 不能作为模板参数
return 0;
}
由上面例子可以看出,auto的使用限制为:
std::map m_map;
//add element to map
std::map::iterator it = m_map.begin();
for (; it != m_map.end(); it++)
{
//dosomething
}
通过上面的迭代器,通过迭代器的begin方法其实就已经知道了变量it的类型了,我们却还是要书写一串常常的类型声明才能通过编译,而有了map之后你就可以这样写了:
std::map m_map;
//add element to map
for (auto it = m_map.begin(); it != m_map.end(); it++)
{
//dosomething
}
系不系清爽很多,再也不用书写冗长的迭代器类型声明了。
class A
{
public:
static int Get()
{
return 0;
}
};
class B
{
public:
static char* Get()
{
return "abcdefg";
}
};
template
void func(void)
{
auto val = T::Get();
//....
}
int _tmain(int argc, _TCHAR* argv[])
{
func();
func();
return 0;
}
在上例中我们希望定义一个泛型函数func,对所有具体静态方法Get的类型T在得到Get结果后做统一的处理,如果不使用auto,就不得不再增加一个模板参数,并在外部调用时手动指定Get的返回值类型。
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 *
decltype(z) *pi = &z; //pi -> int *
decltype(pi) *pp = &pi //pp -> int **
class Foo
{
public:
static const int number = 0;
int x;
};
int _tmain(int argc, _TCHAR* argv[])
{
int n = 0;
volatile const int &x = n;
decltype(n) a = n; //a -> int
decltype(x) b = n; //b -> volatile const int &
decltype(Foo::number) c = 0; //const int
Foo foo;
decltype(foo.x) d = 0; //d ->int 类访问表达式
return 0;
}
int& func_r(void); //返回值类型是一个左值
int && func_rr(void); //返回值类型是一个右值
int func(void); //返回值类型是一个纯右值
const int& func_cint_r(void); //左值
const int && func_cint_rr(void);//右值
const int func_cint(void); //纯右值
int _tmain(int argc, _TCHAR* argv[])
{
int x = 0;
decltype(func_r()) a1 = x; //a1 -> int &
decltype(func_rr()) b1 = 0; //b1 -> int &&
decltype(func()) 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
return 0;
}
从上面可以看出如果expr是一个函数调用的话,则decltype的推导结果与函数的返回值类型一致,并携带相应的CV限定符。需要注意的是c2的类型是int 而不是const int。这是因为函数返回的是一个纯右值,对于纯右值,只有类类型可以携带CV限定符,其他一般忽略掉CV限定符。
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 &
template
class Foo
{
typename ContainerT::iterator _it;
public:
void func(ContainerT& container)
{
it = container.begin();
}
//...
};
int _tmain(int argc, _TCHAR* argv[])
{
typedef const std::vector(int) container_t;
container_t arr;
Foo foo;
foo.func(arr);
return 0;
}
单独去看Foo中的it定义,很难看出会出什么错误,但是根据main中程序传入的的是一个const的容器类型,bebin函数返回的是一个const类型的迭代器,而it成员是一个普通的迭代器类型,编译器会弹出一大堆错误信息,要解决上面的问题,在C++98/03中只能通常是增加一个模板特化的模板函数专门用于处理const类型的容器迭代器,这样的方法实在不是一个特别好的办法,const的模板仅仅是为了处理迭代器类型的限制而增加的而Foo的代码却又不得不重写一次,增加了代码冗余不说,代码可读性也会变差,而又了decltype类型推导以后你就可以这样写上面的代码了。
template
class Foo
{
decltype(ContainerT::begin()) _it;
public:
void func(ContainerT& container)
{
it = container.begin();
}
//...
};
int _tmain(int argc, _TCHAR* argv[])
{
typedef const std::vector(int) container_t;
container_t arr;
Foo foo;
foo.func(arr);
return 0;
}
template
R add(T t, U u)
{
return t + u;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = 1; float b = 2.0;
auto c = add(a, b);
return 0;
}
我们并不关心a+b的类型是啥,只需要通过decltype(a+b)的类型直接得到返回值的类型就可以了,但是上面的使用方法十分不方便吧,我们外部其实并不知道模板函数add中的实际运算是怎么进行了,因此也就不知道add函数返回值应该如何推导,那么在add函数的定义上能不能拿到函数的返回值呢?比如是否可以像下面这样定义呢》?
template < typename T, typename U>
decltype(t+u) add(T t, U u)
{
return t + u;
}
遗憾的是上面的定义是编译不过了,编译错误显示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中增加了返回值类型后置(trailing-return-type 又叫跟踪返回类型)语法,将decltype和auto结合完成返回值类型的推导,极大的增加了代码的可读性。
template < typename T, typename U>
auto add(T t, U u) -> decltype(t+u)
{
return t + u;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = 1; float b = 2.0;
auto c = add(a, b);
return 0;
}