前言
auto
的类型推衍法则除了一个特例之外与template function保持一致。
问题实例
在上一节中,template function的类型推衍有实例如下:1
2
3
4template<typename T>
void f(ParamType param);
f(expr);
在函数调用过程中,编译器将使用expr推衍T与ParamType的类型。
当开发者使用auto
来声明一个变量时,auto
将扮演T在template中的角色,同时,type specifier(类型说明符)则类似于ParamType,如下所示:1
2
3auto 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
11template<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也分为了三种情况:
- type specifier是一个pointer或一个reference,但不是一个universal reference;
- type specifier是一个universal reference;
- type specifier既不是pointer也不是reference;
显然,前文所述的实例分为对应为:1
2
3auto x = 27;//case 3
const auto cx = x;//case 3
const auto& rx = cx;//case 1
Case2有推衍实例如下:1
2
3auto&& 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
7const 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
2int x1 = 27;
int x2(27);
C++11引入了列表初始化的概念,因此增加了两种声明方式:1
2int x3 = {27};
int x4{27};
我们将会在Item5中说明,使用auto
来声明变量比指定类型更佳,那我们试着把上述声明改为:1
2
3
4auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};
上述声明均能够通过编译,只是其含义略有差别,前两个声明式仍然保持着声明一个值为27的int变量的含义,但后两个却发生了变化:1
2auto 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
6auto 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
4template<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
3auto createInitList(){//error,can't deduce type for {1,2,3}
return {1,2,3};
}
lambda表达式中亦是如此:1
2
3
4
5std::vector<int> v;
...
auto resetV = [&v](const auto& newValue){ v = newValue;}
...
resetV({1,2,3});//error,can't deduce type for {1,2,3}
总结
- auto的推衍法则类似于template function,唯一的不同在于auto会假定大括号初始化列表为std::initializer_list,template function则不会。
- 对函数返回值或lambda表达式参数使用auto时遵从template function类型推衍法则,而非auto自己的法则。