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是指针或者引用,但不是通用引用

  1. 如果A是引用,则忽略引用部分
  2. 匹配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); 

格式看起来是右值引用,但是在模板函数的语境下,表示通用引用。如其名字,通用引用,既可以表示左值又可以表示右值引用,但对两者的规则不同。

  1. 如果A是lvalue,T和P都会被推倒为lvalue reference。这是T唯一可以被推倒为reference类型的场景。虽然P是&&的格式,但是被推倒的类型是lvalue.
  2. 如果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的语意,也就是函数的参数是一份拷贝来处理。

  1. 如果A是引用类型,则忽略引用部分。
  2. 在出去引用部分后,再除去最外层的constvolatile修饰符。和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++