2.了解auto技术

前言

 
auto的类型推衍法则除了一个特例之外与template function保持一致。


问题实例

 
在上一节中,template function的类型推衍有实例如下:

1
2
3
4
template<typename T>
void f(ParamType param);

f(expr);

在函数调用过程中,编译器将使用expr推衍T与ParamType的类型。

当开发者使用auto来声明一个变量时,auto将扮演T在template中的角色,同时,type specifier(类型说明符)则类似于ParamType,如下所示:

1
2
3
auto x = 27;//type specifier为auto
const auto cx = x;//type specifier为const auto
const auto&rx = x;//type specifier为const auto&

为了推衍出x、cx、rx的类型,编译器会像调用fun template那样执行操作:
1
2
3
4
5
6
7
8
9
10
11
template<typename T>
void func_for_x(T param);//conceptual template
func_for_x(27);//conceptual call

template<typename T>
void func_for_cx(const T param);
func_for_cx(x);

template<typename T>
void func_for_rx(const T& param);
func_for_rx(cx);


问题剖析

 
在template function类型推衍中我们根据ParamType的特点将情况分为了三种,使用auto的变量声明表达式与之类似,根据type specifier也分为了三种情况:

  1. type specifier是一个pointer或一个reference,但不是一个universal reference;
  2. type specifier是一个universal reference
  3. type specifier既不是pointer也不是reference;

显然,前文所述的实例分为对应为:

1
2
3
auto x = 27;//case 3
const auto cx = x;//case 3
const auto& rx = cx;//case 1

Case2有推衍实例如下:
1
2
3
auto&& uref1 = x;//x is int and lvalue,so uref1's type is int&
auto&& uref2 = cx;// cx is const int and lvalue,so urfe2's type is const int&
auto&& uref3 = 27;//27 is int and rvalue,so urfe3's type is int&&

在Item1中描述了数组名与函数名在遇到非引用type specifier时会退化为指针的情况,这一特性在auto类型推衍过程中也同样适用:
1
2
3
4
5
6
7
const char name[]="R.N.Briggs";
auto arr1 = name;//arr1's type is const char*
auto& arr2 = name;//arr2's type is const char(&)[13]

void someFunc(int,double);
auto func1 = someFunc;//func1's type is void(*)(int,double)
auto& func2 = someFunc;//func2's type is void(&)(int,double)


auto与template function类型推衍的不同之处

 
如果我们想要声明一个值为27的int型变量,C++98为我们提供了两种方式:

1
2
int x1 = 27;
int x2(27);

C++11引入了列表初始化的概念,因此增加了两种声明方式:
1
2
int x3 = {27};
int x4{27};

我们将会在Item5中说明,使用auto来声明变量比指定类型更佳,那我们试着把上述声明改为:
1
2
3
4
auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};

上述声明均能够通过编译,只是其含义略有差别,前两个声明式仍然保持着声明一个值为27的int变量的含义,但后两个却发生了变化:
1
2
auto x3 = {27};//x3's type is std::initializer_list<int>,value is {27}
auto x4{27};//ditto

造成这一变化的根本原因在于auto的推衍法则:当被声明的变量值被大括号包围时,其变量类型会被推衍为std::initializer_list,当包括中的类型不一致时,类型推衍将无法完成:
1
auto x5 = {1,2,3.0};//error can not deduce

需要明确的是在上述语句中发生了两种类型推衍,第一种类型推衍将x5推衍为initializer_list类型(因为其值被大括号包围),但initializer_list本身是一个template,所以我们需要继续执行类型推衍。

这种将大括号初始化推衍为initializer_list是auto与template function唯一不同的地方,后者不允许这种推衍方式:

1
2
3
4
5
6
auto x = {11,23,9};//x's type is initializer_list<int>

template<typename T>
void f(T param);

f({11,23,9});//error!can't deduce type for T

但是如果你将template function中的参数声明为std::initializer_list,只是其元素类型T不明确的话,template function可以完成推衍:
1
2
3
4
template<typename T>
void f(std::initializer_list<T> param);

f({11,23,9});//T's type is int

综上,auto与template function唯一的推衍区别在于:auto总是假定一个大括号初始化列表代表着一个initializer_list,而template function不作这种假设。


C++14中的auto

 
在C++14中,auto还可以用来表征函数的返回值类型,并且lambda表达式也经常使用auto来声明参数。不过这两种auto的使用方式遵从template function类型推衍法则,而非auto推衍法则,因此返回一个初始化列表的函数将无法用auto推衍:

1
2
3
auto createInitList(){//error,can't deduce type for {1,2,3}
return {1,2,3};
}

lambda表达式中亦是如此:
1
2
3
4
5
std::vector<int> v;
...
auto resetV = [&v](const auto& newValue){ v = newValue;}
...
resetV({1,2,3});//error,can't deduce type for {1,2,3}


总结

 

  1. auto的推衍法则类似于template function,唯一的不同在于auto会假定大括号初始化列表为std::initializer_list,template function则不会。
  2. 对函数返回值或lambda表达式参数使用auto时遵从template function类型推衍法则,而非auto自己的法则。