型别推导

函数模板大致形如:

template<typename T>
void f(ParamType param);
f(expr);

什么是型别推导?就是推导模板中,在函数调用之类中T到底是啥。

template<typename T>
void f(const T& param);
int x = 0;
f(x);  // T 推导为int, ParamType推导为const int&

以下分三种情况讨论

1. ParamType是一个指针或者引用,但不是万能引用。

什么是万能引用呢? &&
推导步骤:

1.1 如果expr具有引用型别,先将引用部分忽略。

怎么理解这句话呢? 本来我们是这样定义模板的。

template <typename T>
void f(T& param);

expr就是

int x = 27;
const int cx = x;
const int& rx = x;

上面这些就叫做expr, 忽略引用就是,在推导的时候 const int& rx = x;不将引用带入推导。

1.2 而后,对expr和ParamType执行模式匹配,然后决定T的型别。

f(x); // param const int , T const int 
f(cx); // param 型别 const int&, 那么 T就是const int
f(rx); // param 型别 const int&, 那么 T就是const int  

1.3 结论

当向引用型别传入const对象时,T&传入的const对象是安全的。
换句人话说,就是如果传入的expr有const的也有非const的,又要保证const对象的const性,只需要

template <typename T>
void f(T& param);

没必要写成

template <typename T>
void f(const T& param);

那么具体的 const T&

template <typename T>
void f(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param const int , T const int 
f(cx); // param 型别 const int&, 那么 T就是int
f(rx); // param 型别 const int&, 那么 T就是int 

当然我们实际操作的使用的是ParamType 所以如无必要T&即可。
那么当param是指针的时候

template <typename T>
void f(T* param);
int x = 27;
const int *px = &x; // px const int * , px可变 px指向的内容不能变。
f(&x); // T 的型别 int, param 是int*
f(px); // T const int param const int*

实话说使用的时候我们关心param型别就好了。

2. ParamType是万能引用

  1. 这种情况,只需要expr, T和ParamType都会被推导成,左值引用。
  2. 如果expr是右值,按照情况1来区分。
template <typename T>
void f(T&& param);
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param const int& , T const int&
f(cx); // param 型别 const int&, 那么 T就是const int&
f(rx); // param 型别 const int&, 那么 T就是const int&
f(27); // param int&&, T int 

关键点区分param是左值还是右值。

3.ParamType不是指针也不是引用

这种情况比较简单,因为这种情况都是值传递。

template <typename T>
void f(T param); // param值传递,无论传什么,param都是实参的副本,全新对象。
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // param int , T int
f(cx); // param 型别 int, 那么 T就是int
f(rx); // param 型别 int, 那么 T就是int

注意不管实参是否带const,都不能决定cx,rx能否改变。
注意这种情况下带指针的情况

template <typename T>
void f(T param); // param值传递
cosnt char* const ptr = "hello";
f(ptr); // 传递型别为const char* const ,param 推导为const char*

比如我这样写是没毛病的

#include <iostream>

template <typename T>
void f(T param) {
  const char * ch = "world";
  param = ch;
}
int main() {
  const char* const ch_ptr = "hello";
  f(ch_ptr);
  return 0;
}

这说明当ParamType为非指针非引用的时候,传指针的时候需要注意,比如实参为const char* const类型,只会保证
不会通过该指针改变指针指向的内容,但是还是会改变指针本身的值。

4.数组到指针的退化

这种情况很简单,平时我们也会把指针和数组混用,比如 ptr[0] = ??? or *ptr = ???
这里需要注意的就是void f(T param)void f(T& param)对数组实参的推导

4.1. 当使用T时,数组实参推导结果是指针。
4.2. 当使用T&时,推导结果是带实际大小的数组的引用。
比如

template <typename T>
void f(T& param){}
const char ch[] = "Hello World!";
f(ch); // ParamType 型别 const char (&)[13];

于是我们可以得到这样一个应用,获取数组大小

#include <iostream>

template <typename T, std::size_t N> 
constexpr std::size_t GetArraySize(T (&)[N]) noexcept{
  return N;
}

int main() {
  const char ch[] = "Hello World!";
  std::cout << GetArraySize(ch) << std::endl;
  return 0;
}

运行结果

于是我们可以通过一个数组都构建另一个大小相同的数组

int keyVarl[] = {1, 2, 3};
int otherKeyVarl[GetArraySize(keyVarl)];

5. 函数实参的退化

这里函数实参退化和数组实参退化一样,也是分两种情况。一种是非引用非指针型别,这时候函数实参会退化成函数指针;
另一种是引用型别,这种就是函数引用的具体型别。

void someFunc(int, double);
template <typename T>
void f1(T param); //在f1中按照值传递
f1(someFunc); // param被推导为函数指针,具体型别为void (*)(int, double)
template <typename T>
void f2(T& param); //在f1中按照引用传递
f2(someFunc); // param被推导为函数引用,具体型别为void (&)(int, double)
本文内容全部来自《Effective Modern C++》,我只是在搬砖。
posted @ 2020-12-14 18:37  cyssmile  阅读(230)  评论(0编辑  收藏  举报