类型自动推导是C++模板编程中的一个重大特性,很多时候我们不会显式特化模板参数类型,而是由编译器根据表达式类型自动推导出正确的模板参数类型。而理解模板类型推导原则可以帮助我们更有效地理解和使用C++模板编程。
首先我们要清楚认识到模板类型推导的重要性。
- 函数重载与
SFINAE
。(包含函数模板的重载匹配) - 模板特化。最常见的在STL中针对指针类型的特化。
- 右值引用的支持。
perfect forwarding
我们常见的模板类型推导的例子如下:
template<typename T> void f(ParamType param); // declaration or definition
f(expr); // caller
三个重要术语: T
和 ParamType
和 expr
。
在编译期,编译器会根据 expr
的类型 自动推导 T
和 ParamType
的类型。
非数组和函数指针类型的 expr
,其推导规则有3类。其中有一类涉及到universal reference
这种C++11引入的新型引用,我们首先来了解一下什么是 universal reference
。
universal reference
universal reference
有时也被称为 forwarding reference
,根据 expr
的不同,可能导致 T
是一个左值引用,或者一个右值引用。
同时满足以下两个条件的引用 才是 universal reference
- 必须存在类型推导
ParamType
只能为T&&
,const T&&
都不行。
一般情况下只出现在 模板函数 和 auto
这两种情况下。
template<typename T> void f(T&& param) // univeral reference
auto&& t2 = t1; // universal reference
void f(T&& param) // rvalue reference. (no type deduction)
template<typename T>
void f(vector<T>&& param) // rvalue reference (no T&&)
template<typename T>
void f(const T&& param) // rvalue reference (no T&&)
非数组类型推导
根据ParamType
,类型推导 可以分为 3 大类:
1、ParamType
是一个 引用(非 universal reference)或者指针。
template<typename T> void f(T& param) // lvalue reference
template<typename T> void cf(const T& param) // const-lvalue reference
template<typename T> void rf(vector<T>&& param) // rvalue reference
template<typename T> void pf(T* param) // pointer
推导原则(指针和引用相同):
1.1. if expr
’s type is reference, ignore the reference part.
1.2. then, pattern-match expr
’s type against ParamType
to determine T
. ignore the ref part.
例子:
int x = 27;
const int cx = x;
const int& xr = x;
f(x) // expr(x) is int, T is int
f(cx) // expr(cx) is const int, T is const int
f(xr) // expr(xr) is const int&, ignore reference, T is const int
cf(x) // ParamType is const int &, ignore &, T is int
cf(cx) // T is int
cf(xr) // T is int
2、ParamType
是一个 universal reference
template<typename T> void f(T&& param)
推导规则:
- if
expr
is lvalue, bothT
andParamType
are lvalue reference. - if
expr
is rvalue, same with case1.
例子:
int x = 27;
const int cx = x;
const int& xr = x;
f(x) // x is lvalue, T and ParamType are int&
f(cx) // cx is lvalue, T and ParamType are const int&
f(xr) // xr is lvalue, T and ParamType are const int&
f(27) // 27 is rvalue, T is int, so ParamType is int&&
3、ParamType
既不是引用也不是指针
template<typename T> void f(T param)
这种pass-by-value
的情况,会创建一个新的对象,从而和expr
的关系不是很紧密了。
推导规则:
3.1. if expr
’s type is reference, ignore reference
3.2. then, remove expr
’s const and volatile.
f(x) // T and param's type are both int
f(cs) // T and param's type are both int
f(xr) // T and param's type are both int
// some tricky
const char* const prt = "Fun with pointer";
f(ptr) // expr is a const-pointer and pointer to a const object,
// just remove the constness of ptr, not the object pointer to.
// T and param's type are const char*
数组类型推导
1. pass-by-value
数组类型会 decay 为指针类型。
template<typename T> void f(T param);
const char name[] = "Steve Jobs";
f(name) // expr is const char[11], but T deducted to const char*
2、 pass-by-reference
数组类型就是数组类型,不会 decay
template<typename T> void f(T& param);
const char name[] = "Steve Jobs";
f(name) // expr is const char[11], and T deducted to const char[11].
// So ParamType is const char(&)[11]
函数类型推导
这种情况和 数组类型推导一样。 pass-by-value
时,会 decay 为函数指针。
void someFunc(int, double);
template<typename T> void f1(T param);
template<typename T> void f2(T& param);
f1(someFunc) // T and ParamType deducted to void (*)(int, double)
f2(someFunc) // T deducted to void ()(int, double), ParamType is void (&)(int, double)