C++11 新增 auto 关键字,使得人们不必再写那些冗长的型别,让代码获得更高的适应性,在源代码的一个地方对一个型别实施改动,可通过型别推导传播到其他地方。然而,它也可能导致代码比较难看懂,因为编译器推导出的型别,不像我们认为的那样显而易见。
型别推导涉及的语境不胜枚举:函数模板中、auto 出现的大多数场景中、decltype 表达式中等。这篇文章整理了有关型别推导的知识,解释了模板型别推导如何运作,auto 的推导如何构建在次之上。
template<typename T>
void f(ParamType param);
f(expr);
以上述代码为例,在编译期,编译器会通过 expr 推导两个型别:一个是 T 的型别,另一个是 ParamType 的型别,这两个型别往往不一样。因为 ParamType 常会包含一些饰词,如 const 或引用符号等。例如:
template<typename T>
void f(const T& param); //ParamType 是 const T&
int a = 10;
f(a); //这里T被推导为int,而 ParamType 被推导为 const int&
T 的推导结果,不仅仅依赖 expr 的型别,还依赖 ParamType 的形式。具体要分为三种情况讨论:
- ParamType 具有指针或引用型别,但不是个万能引用(万能引用解释见 《C++11之右值引用、移动语义和完美转发》这篇文章)。
- ParamType 是一个万能引用。
- ParamType 既不是指针也不是引用。
这种情况的运作规则是:
- 若 expr 具有引用型别,先将引用部分忽略。
- 然后,对 expr 的型别和 ParamType 的型别执行模式匹配,来决定 T 的型别。
例子如下:
template<typename T>
void f(T& param);
int x = 0;
const kx = x;
const int& rx = x;
f(x); //T的型别是int,paramtype是int&
f(kx); //T的型别是const int,paramtype是const int&
f(rx); //T的型别是const int,paramtype是const int&
这种情况的运作规则是:
- 如果 expr 是个左值,T 和 ParamType 都会被推导为左值引用。
- 如果 expr 是个右值,则按情况一处理。
例如:
template<typename T>
void f(T&& param);
int x = 0;
const kx = x;
const int& rx = x;
f(x); //T的型别是int&,paramtype是int&
f(kx); //T的型别是const int&,paramtype是const int&
f(rx); //T的型别是const int&,paramtype是const int&
f(0); //T的型别是int,paramtype是int&&
这种情况就是所谓的按值传递了,无论传入什么,param 都是个新的对象,因此规则如下:
- 若 expr 具有引用型别,将引用部分忽略。
- 忽略 expr 的 CV 限定(const/volatile)。
例如:
template<typename T>
void f(T param);
int x = 0;
const kx = x;
const int& rx = x;
f(x); //T的型别是int,paramtype是int
f(kx); //T的型别是int,paramtype是int
f(rx); //T的型别是int,paramtype是int
除了在一个例外情况下,auto 型别推导和模板型别推导一样。这里 auto 就扮演模板中 T 的角色,变量的型别饰词扮演 ParamType 的角色。所以这里也分为三种情况:
- 型别饰词是指针或引用,但不是万能引用。
- 型别饰词是万能引用。
- 型别饰词即非指针也非引用。
例如:
auto x = 0; //情况三,x型别是int
const auto kx = x; //情况三,kx型别是const int
const auto& rx = x; //情况一,rx型别是const int&
auto&& r = x; //情况二,r型别是int&
auto&& r2 = kx; //情况二,r2型别是const int&
auto&& r3 = rx; //情况二,r3型别是const int&
auto&& r4 = 0; //情况二,r4型别是int&&
例外情况,C++11 为了支持统一初始化,新增了 {} 初始化,auto 和模板型别推导的唯一区别在于,auto 会假定用大括号初始化代表一个 std::initializer_list,但模板推导不会。
例如:
auto x1 = 0; //x1 是int
auto x2(0); //x2 是int
auto x3 = {0}; //x3 是std::initializer_list<int>
auto x4{0}; //x4 是std::initializer_list<int>,C++17 起类型为 int