1、  概述

  C++11里面引入了auto和decltype关键字来实现类型推导,通过这两个关键字不仅能方便的获取复杂的类型,还能简化书写,提高编码效率。

2、  auto

2.1 auto关键字的新定义

  auto关键字并不是一个全新的关键字,早在旧标准中就已经有定义:“具有自动储存期的局部变量”,不过用处并不大,如下:

auto int i = ;   //c++98/03,可以默认写成int i = 0;

static int j = ;

  上述代码中,auto int是旧标准中的用法,与之相对的是static int,代表了静态类型的定义方法,我们很少这样使用auto,因为非static的局部变量本身就具有自动存储期。

  所以,c++11中考虑到之前的auto使用较少,不再表示存储类型指示符(如static,mutable等),而是改成了类型指示符,用来提示编译器对此类型的变量做类型的自动推导。

  我们来看一组例子:

auto x = ;            //OK,x:int
auto pi = new auto();     //OK,pi:int *
const auto *v = &x, u = ;   //OK,v:const int *, u:const int
static auto y = 0.0; //OK,y:double
//auto int r; //error,auto不再表示存储类型指示符
//auto s; //error,无法推导出类型

  x被自动推导为int,并被初始化为5;

  pi被推导为int *,同时说明auto还支持new的类型推导;

  &x类型为int *,所以const auto *说明auto是int,所以v为const int *;

  因为v为int *,所以推导auto为ing,u也应该为int,这里需要注意的是,u的初始化必须和前面推导的auto类型相同,不然会出现二义性,否则编译器通不过;

  y被推导为double;

  auto已经不再是存储类型指示符,所以,r会提示错误;

  s没类型进行推导,所以会报错没有初始化。

  由列子可以得出以下结论:

  auto并不能代表一个实际的类型,只是一个类型申明的占位符;

  auto申明的变量必须马上初始化,让编译器在编译期间推导出实际类型,在编译的时候用实际的类型替换掉类型占位符auto。

2.2 auto推导规则

  auto可以同指针引用结合起来使用,还可以带上cv限定符(const,volatile的统称)。下面我们再来看一组列子:

int x = ;
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->const int
auto f = e; //f->int const auto &g = x; //g->const int &
auto &h = g; //f->const int &

  上述例子的推导结果如代码,从例子我们可以得出以下结论:

  auto可以自动推导指针类型;

  当不申明为指针或者引用时,auto的推导结果和初始化表达式抛弃引用和cv限定符后的类型一致;

  当申明为指针或者引用时,auto推导的结果将保持初始化表达式的cv属性。

2.3 auto的限制

  auto申明的时候必须初始化,那么auto肯定是不能作为函数参数的。

  void func(auto a = 2){}   //error:不能用于函数参数

  auto不能用于非静态成员变量。

struct Foo
{
  auto var1 = ; // error
  static const auto var2 = ; //OK,var2->static const int
}

  auto无法定义数组。

int arr[] = {};
auto aa = arr; //OK,aa->int *
auto arr2[] = {}; //error无法通过编译

  auto无法推导出模版参数。

std::vector<int> vec1;         //OK
std::vector<auto> vec2 = vec1; //error 无法通过编译

  在Foo中,auto仅能推导static const的整型或者枚举成员(因为其他静态类型在c++标准中无法就地初始化),c++11中可以接受非静态成员变量的就地初始化,但不支持auto类型非静态成员变量的初始化。

2.4 auto使用场景

  auto在我看来,最主要的是可以简洁代码。类型推导虽然说可以作自动推导,但是在真正写代码的时候,还要考虑可读性,auto并不能代码更多的好处。

  如果在c++11之前,我们定义一个map,在遍历的时候,通常需要这么写:

void func()
{
  map<int, int> test_map;
  map<int, int>::iterator it = test_map.begin();   for(it; it != test_map.end(); ++it)
  {
  //do something
  }
}

  那么我们在使用auto之后,代码就会很简单了,根据map.begin()就可以推导出类型。

void func2()
{
  map<int, int> test_map;   for(auto it = test_map.begin(); it != test_map.end(); ++it)
  {
  //do something
  }
}

  是不是感觉简洁多了。更简洁的还在后面,我们在一个unordered_multimap中查找一个范围:

void func3()
{
  unordered_multimap<int, int> test_map;
  pair<unordered_multimap<int, int>::iterator, unordered_multimap<int, int>::iterator> range = test_map.equal_range();
}

  很明显,这个euqal_range返回的类型显得太繁琐,而实际上可能并不在乎这里的具体类型,这时就可以通过auto来简化书写。使用auto之后:

void func4()
{
  unordered_multimap<int, int> test_map;
  auto range = test_map.equal_range();
}

  如果在某些情况不知道返回值类型,我们可以通过auto来做推导,然后统一处理。

class Foo
{
public:
  static int get()
  {
    return ;
  }
}; class Bar
{
public:
  static const char *get()
  {
     return "";
  }
}; template <class A>
void func5()
{
  auto val = A::get();
  // do something
} void func6()
{   func5<Foo>();
  func5<Bar>();

  return;
}

  假如我们定义一个泛型函数func5,对具有静态方法get的类型A得到的结果做统一的后续处理,如果不使用auto,那么就必须再增加一个模板参数,并在外部手动指定get的返回值类型。

  auto是一个很强大的工具,但是如果不加选择的随意使用auto会导致代码可读性和可维护性严重下降,因此,在使用的时候,一定要权衡好使用的“度”,那么带来的价值会非常大。

3、  decltype

3.1 基本定义

  auto的申明必须要初始化才能确定auto代表的类型,那如果我们既需要得到类型,又不定义变量的时候怎么办呢?C++11提供了decltype来解决这个问题,它的定义如下:

decltype(exp)   //exp表示一个表达式。

  decltype有点像sizeof,不过sizeof是计算表达式类型大小的标识符。和sizeof一样,decltype也是在编译时期推导类型的,并且不会真正计算表达式的值。我们来看一组例子:

int x = ;
decltype(x) y = ; //y->int
decltype(x+y) z = ; //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 = &pi; // *pp->int *, pp->int **

  decltype和auto一样,可以加上引用指针,以及cv限定符进行推导。

3.2 推导规则

  网上各种版本的规则众多,下面是我简单整理的一些规则:

  exp是标识符、类访问表达式,decltype(exp)和exp的类型一致;

  exp是函数调用,decltype(exp)和函数返回值一致;

  exp是一个左值,则decltype(exp)是exp的一个左值引用,否则和exp的类型一致。

3.2.1 标识符表达式和类访问表达式

class Foo
{
public:
static const int miNum = ;
int ix;
}; int n = ; volatile const int &x = n;
decltype(n) a = n; //a->int
decltype(x) b = n; //b->const valatile int & decltype(Foo::miNum) c = ; //c->const int Foo foo;
decltype(foo.ix) d = ; //d->int,类访问表达式

  变量abc保留了表达式的所有属性(cv、引用),对于表达式,decltype的推导和表达式一致,而d是一个类访问表达式,因此也和表达式类型一致。

3.2.2 函数调用

int &func_int_r(void);                      //左值,lvalue,简单理解为可寻址值
int &&func_int_rr(void); //x值,xvalue,右值引用本身是一个xvalue
int func_int(void); //纯右值,pvalue const int &func_cint_r(void); //左值
const int && func_cint_rr(void); //x值
const int func_cint(void); //纯右值 cont Foo func_foo(void); //纯右值

//下面是测试语句

int x = ;

decltype(func_int_r())  al = x;         //al->int&
decltype(func_int_rr()) bl = ; //bl->int &&
decltype(func_int()) cl = ; //cl->int decltype(func_cint_r()) a2 = x; //a2->const int &
decltype(func_cint_rr()) b2 = ; //b2->const int &&
decltype(func_cint) c2 = ; //c2->int
decltype(func_foo()) ff = Foo(); //ff->const Foo

  这里需要注意的是,C2的推导是int而不是const int,这是因为函数的返回值int是个纯右值,对于纯右值而言,只有类类型可以携带cv限定符,除此之外一般忽略掉。所以func_foo推导出来的ff是const Foo。

3.2.3 带括号的表达式和加法运算表达式

struct Foo{ int x;};
const Foo foo = Foo(); decltype(foo.x) a = ; //a->int
decltype((foo.x)) b = a; //const int & int n = , m = ;
decltype(n+m) c = ; //c->int
decltype(n+=m) d = c; //d->int &

  这里需要注意的是,b的推导,括号表达式中的foo.x是一个左值,所以decltype的结果是一个左值引用,foo的定义是const Foo,所以foo.x是一个const int类型,所以b是一个const int &;

  n+m返回的是一个右值,所以结果是int,n+=m返回一个左值,所以推导出d是int &。

3.3 decltype实际应用

  decltype多出现在泛型变成中,我们来看一个例子,假如我们编写一个泛型类:

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 ;
}

  问题很明显,当我们传入一个const容器的时候,我们的定义的iterator不适用,编译器会报错,所以当传入const容器的时候,我们应该使用const_iterator。

  如果在C++98/03要解决这样的问题,我们必须增加一个泛型类:

template<class ContainerT>
class Foo<const ContainerT >
{
typename ContainerT::const_iterator it; //类型的定义可能有问题
public:
void func(const ContainerT &container)
{
it = container.begin();
}
}

  这样虽然说可以解决问题,但是太麻烦,Foo的其他代码也不得不重新写一次,如果我们用decltype来解决这个问题:

template<class ContainerT>
class Foo
{
decltype(ContainerT().begin()) it; //类型的定义可能有问题
public:
void func(ContainerT &container)
{
it = container.begin();
}
}

  decltype也经常用在通过变量表达式抽取变量类型上:

vector<int> v;

decltype(v)::value_type I = ;

  冗长的代码中,我们只需要关心变量本身,不需要关心它的具体类型,如例子,我们只需要之道v是一个容器,可以提取value_type就OK,而不是到处都需要出现vector<int>这种精确的类型名称。

4、  auto和decltype混编

  在泛型编程中,通过auto和decltype的混编来提升灵活性。通过参数的运算来获得返回值类型。

template<typename R, typename T, typename U>
R add(T t, U u)
{
return t + u;
} int a = ;
float b = 2.0; auto c = add<decltype(a + b)>(a, b);

  我们不关心a+b类型是什么,通过decltype(a+b)来推导返回值类型。我们还可以通过add函数的定义来获得返回值类型。

template<typename T, typename U>
decltype(t+u) add(T t, U u) //error :t、u尚未定义
{
  return 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*)) + (*(U*))) add(T t, U u)
{
return t + u;
}

  这样可以成功的使用decltype来完成返回值的推导,但是代码可读性比较差,并增加使用难度。

  c++11增加了返回类型后置语法,讲decltype和auto结合起来完成返回值类型的推导。我们再来改善上面的add函数:

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}

  我们再来看一个例子:

int &foo(int &i);
float foo(float &f); template<typename T>
auto func(T &val) -> decltype(foo(val))
{
return foo(val);
}

  使用decltype结合返回值后置语法很容易推导出foo(val)可能出现的返回值,并用到func上。返回值类型后置语法,是为了解决函数返回值类型依赖与参数而导致难以确定返回值类型的问题,可以很清晰的描述返回值类型,而不是c++98/03那样晦涩难懂的语法来解决问题。

C11简洁之道:类型推导的更多相关文章

  1. C11简洁之道:lambda表达式

    1.  定义 lambda表达式是C++11非常重要也是很常用的特性之一,来源于函数式编程的概念,也是现代编程语言的一个特点.它有如下特点: 声明式编程风格:就地匿名定义目标函数或者函数,不需要额外写 ...

  2. C11简洁之道:循环的改善

    1.  for循环的新用法 在C++98/03中,通过for循环对一个容器进行遍历,一般有两种方法,常规的for循环,或者使用<algorithm>中的for_each方法. for循环遍 ...

  3. C11简洁之道:模板改进

    1.  右尖括号 我们在C++98/03中使用泛型编程的时候,经常遇到“>>”被当作右移操作符,而不是模板参数的结尾.假如我们有如下代码: template <typename T& ...

  4. C11简洁之道:tupe元祖

    tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...

  5. C11简洁之道:初始化改进

    1.  C++98/03初始化 我们先来总结一下C++98/03的各种不同的初始化情况: //普通数组 ] = {, , }; //POD(plain old data) struct A { int ...

  6. C11简洁之道:函数绑定

    1.  可调用对象 在C++中,有“可调用对象”这么个概念,那么什么是调用对象呢?有哪些情况?我们来看看: 函数指针: 具有operator()成员函数的类对象(仿函数): 可以被转换为函数指针的类对 ...

  7. 《Clean Code》 代码简洁之道

    作者介绍 原文作者: Robert C. Martin, Object Mentor公司总裁,面向对象设计.模式.UML.敏捷方法学和极限编程领域的资深顾问,是<敏捷软件开发:原则.模式.与实践 ...

  8. JavaScript 代码简洁之道

    摘要: 可以说是<Clean Code>的JS代码示例了,值得参考. 原文:JavaScript 代码简洁之道 作者:缪宇 Fundebug经授权转载,版权归原作者所有. 测试代码质量的唯 ...

  9. Effective Modern C++翻译(2)-条款1:明白模板类型推导

    第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...

随机推荐

  1. addeventlistener和attachevent

    区别: 1.ie8及以下版本前者无效,只能使用后者: 2,关于第三个参数,如果是true则捕获状态触发,为false;则为冒泡状态触发 何为冒泡,何为捕获? 这就好比捕鱼,冒泡吗,鱼向上吐泡泡,所以当 ...

  2. maven把项目打包成jar包后找不到velocity模板的bug

    使用springmvc 开发时候要实现发送velcotiy模板邮件,在配置正常后,在本地测试正常后,使用maven打包成jar包后,报以下错误, Caused by: org.apache.veloc ...

  3. iOS- 多线程技术的概述及优点

    1.概述 在iOS开发中: •耗时操作,例如网络图片.视频.歌曲.书籍等资源下载 •游戏中的声音播放   我们可以利用多线程: •充分发挥多核处理器的优势,并发(同时执行)执行任务让系统运行的更快.更 ...

  4. TCP系列06—连接管理—5、TCP fastopen(TFO)

    一.TFO背景 当前web和web-like应用中一般都是在三次握手后开始数据传输,相比于UDP,多了一个RTT的时延,即使当前很多应用使用长连接来处理这种情况,但是仍然由一定比例的短连接,这额外多出 ...

  5. 在64位的环境下利用Jet来操作Access,Excel和TXT

    For example, you have a 32-bit application that uses the Microsoft OLE DB Provider for Jet. If you m ...

  6. 3ds Max学习日记(八)

      再来更新一波学习进度.   之前玩了一下3dsmax里的灯光,不过由于和教程里的版本不同,教程里的我的没有,我有的教程又没有,所以只能瞎jb玩一玩.   最近又想建个人物模型玩玩,于是上网搜一下有 ...

  7. VS2012完全卸载

    1.先交VS2012的ISO通过Ultraiso载入2.DOS命中输入 I:\vs_ultimate.exe /uninstall /force 或 I:vs_ultimate.exe /uninst ...

  8. stm32f407启动文件分析

    ; Amount of memory (in bytes) allocated for Stack; Tailor this value to your application needs; < ...

  9. 【bzoj1616】[Usaco2008 Mar]Cow Travelling游荡的奶牛 bfs

    题目描述 奶牛们在被划分成N行M列(2 <= N <= 100; 2 <= M <= 100)的草地上游走,试图找到整块草地中最美味的牧草.Farmer John在某个时刻看见 ...

  10. 原生JS表单序列化

    // 表单序列化,IE9+ HTMLFormElement.prototype.serialize = function() { var form = this; // 表单数据 var arrFor ...