C++ 模板函数类型推倒
1. 模板类型推倒(template argument deduction)
// 模板定义
template<typename T>
void f(P param);
// 函数调用
A a;
f(a);
T和P可能是不同的,P中可能会包含const或者引用等修饰符。
如果模板声明为:
template<typename T>
void f(const T& param); // P是 const T&
调用为:
int x = 0;
f(x);
T会被推倒为int
,P会被推倒为const int &
.
T的推倒很重要,因为在模板函数的一些变量会被声明为T。
T的推倒不但依赖A,而且依赖P。下面分三种情况讨论T的推倒:
- P是指针或者引用,但不是通用引用(&&)
- P是通用引用
- P不是指针也不是引用
2. Case 1 : P是指针或者引用,但不是通用引用
- 如果A是引用,则忽略引用部分
- 匹配A和P来的到T的类型。
template<typename T>
void f(T& param); // P是引用
...
int x = 27;
const int cx = x;
const int& rx = x;
...
f(x); // A = int, P = T&, T 是 int, trivial conversion (T -> T&)
f(cx); // A = const int, P = T&, T是const int, trivial conversion (T -> T&)
f(rx); // A = const int &, P = T&, 去除A的引用部分的到A' = const int, 如果T是int,不可被推倒,因为不存在 const T -> T的trvial conversion. T是const int
比较有趣的是f(cx)
和f(rx)
的例子可以看到,当传入的参数为const时,T也被推倒为const,保持const不变。
以上的例子指针对lvalue引用类型的,但类型推倒对rvalue引用类型同样适用。
我们看看如果P是const T&
会怎样
template<typename T>
void f(const T& param); // P是const T&
...
int x = 27;
const int cx = x;
const int& rx = x;
...
f(x);// A是int, P = const T&, T 是 int, trivial conversion(T -> const T&),
f(cx); // A是 const int, P = const T&, T 是 int可以被推倒,trivial conversion(T -> T&)
f(rx); // A是 const int &, A' = const int, P = const T&, T是int 可以被推倒, trivial conversion(T -> T&)
如果P是指针类型,也一样适用
template<typename T>
void f(T* param); // P = T*
...
int x = 27;
const int * px = &x;
...
f(&x); // A = int*, P = T*, T = int
f(px); // A = const int *, P = T*, T = const int
其实回头想一下,这个规则是非常符合直觉的,如果P是引用或者指针,要保持P的参数在函数内的const不变性。在P上是否声明为const只影响函数内T类型是否为const。
3. Case 2: P是通用引用
通用引用在template函数的声明格式如下:
template<typename T>
void f(T&& param);
格式看起来是右值引用,但是在模板函数的语境下,表示通用引用。如其名字,通用引用,既可以表示左值又可以表示右值引用,但对两者的规则不同。
- 如果A是
lvalue
,T和P都会被推倒为lvalue reference
。这是T唯一可以被推倒为reference
类型的场景。虽然P是&&
的格式,但是被推倒的类型是lvalue
. - 如果A是
rvalue
,转为case 1
处理。
template <typename T>
void f(T&& param);
...
int x = 27;
const int cx = x;
const int& rx = x;
...
f(x); // x is lvalue, A = int, P = T&, T = int
f(cx); // x is lvalue, A = const int, P = T&, T = const int &, T被推倒为reference
f(rx); // x is lvalue, A = const int&, P = T&, T = const int & , T被推倒为reference
f(27); // x is rvalue, goto case 1, A = int, P = T&&, T = int
4. case 3:P不是指针,也不是引用。
如果P不是指针,也不是引用,那么我们正在处理的就是pass-by-value
这种形式,所以在做推倒的时候,要保持pass-by-value
的语意,也就是函数的参数是一份拷贝来处理。
- 如果A是引用类型,则忽略引用部分。
- 在出去引用部分后,再除去最外层的
const
和volatile
修饰符。和P做对比,得到T。
template<typename T>
void f(T param);
...
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // P = T = int
f(cx); // A = const int, A' = int, P = T, T = int
f(rx); // A = const int &, A' = int, P = T, T = int
也就是除去A的reference,和最外层 cv修饰符,因为要保证pass-by-value
的语意。
5. Array和Function类型的推倒
P可以是Array的引用类型,或者是array衰退后的指针类型。
当P不是Array引用类型时,P可以衰退为Array的指针类型。
当P是Array的引用类型时,P可以表示Array的引用类型。
/////// pass-by-value
template<typename T>
void f(T param); // pass-by-value
const char name[] = "J. P. Briggs"; // type of const char[13]
f(name); // T is const char*
/////// pass-by-reference
template<typename T>
void f(T& param); // pass-by-reference
f(name); // P可以是Array类型的引用,所以不用衰退。P 是 const char[13]&, T是 const char[13]
当A是函数类型时,推倒过程中,函数类型会衰退为ptr-to-func
或者ref-to-func
类型。
void someFunc(int, double); // type is void(int, double)
template<typename T>
void f1(T param); // pass-by-value
template<typename T>
void f2(T& param); // pass-by-reference
f1(someFunc); // P is ptr-to-func, T is void(*)(int, double)
f2(someFunc); // P is ref-to-func, T is void(&)(int, double)
Reference
[1] Effective Modern C++