模板类型推导

template<typename T>
void f(ParamType param)

f(expr)

T的类型是什么?由T推导出ParamType的类型是什么。

ParamType 是引用或者指针,但不是&&

  1. 如果expr是引用,在类型推导时忽略它的引用部分
  2. 然后用expr的类型匹配ParamType

比如模板定义是这样的:

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

这种可以看作是f的参数一定是一个引用

通过以下几种方式调用:

int x = 2;
const int constx = 2;
const int &constrefx = x;
  • f(x) T的类型是int, param的类型是int&
  • f(cx) T的类型是const int, param的类型是const int&
  • f(constrefx); T的类型是const int, param的类型是const int&

如果模板的参数是已经包含了一个const:

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

因为param的参数一定是一个const &,所以在推导T的类型时就不用包含const了:

template<typename T> 
void f(const T& param); // 模板参数变成了const &
   
int x = 27;
const int cx = x;
const int& rx = x;

f(x);  // T is int, param's type is const int&
f(cx); // T is int, param's type is const int&
f(rx); // T is int, param's type is const int&

其实就是保证T的类型在推导出param的类型时一定是const &

模板中的const和&,可以看成是T的类型最终推导出的param类型一定是const和&,以此来确定T的类型。

指针也是一样的:

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

int x=1;
const int *px = &x;

f(&x) // T的类型是int, param的类型是int *
f(px) // T的类型是const int, param的类型是const int*

ParamType是一个Universal Reference

template<typename T> 
void f(T&& param);
  • 如果调用参数是左值,那么param的类型就是一个左值引用:&,同时如果调用参数有const修饰那么param的类型也会包含const变成const &
  • 如果调用参数是一个右值,那么param的类型就是一个右值
template<typename T>
void f(T&& param); // param is now a universal reference

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

f(x);  // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int, param's type is therefore int&&

ParamType不是引用也不是指针

template<typename T>
void f(T param); // param is now passed by value
  • 如果调用参数是一个引用&,则忽略引用&
  • 如果调用参数是一个const,也忽略这个const修饰
int x = 27;
const int cx = x;
const int& rx = x;

f(x);  // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int

这个可以认为是,模板强制参数是无const的值传递,所以无论你传的是const还是引用,都会被忽略,然后推导成一个普通的值类型

通过const指针调用呢:

const char* const ptr =  // ptr is const pointer to const object
     "Fun with pointers";
   f(ptr);

推导步骤:

  • 忽略ptr本身的const得到const char*(要明白**指针不能变**和**指针指向的值不能变**),去掉ptr的const后ptr就可以被赋值了。
  • T的类型就是const char*,得到param的类型是const char *

数组参数

如果模板参数是值类型:

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

const char name[] = "J. P. Briggs";
f(name)

这种情况数组直接当成指针进行推导,得到T的类型是 const char*,而不是数组

如果模板参数是引用类型:

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

const char name[] = "J. P. Briggs";
f(name)

这种情况,T的类型则是真正的数组const char[13], param类型则是这个数组的引用:const char (*) [], 通过这种方式式就可以得到取得数组长度的模板函数:

// return size of an array as a compile-time constant. (The
// array parameter has no name, because we care only about
// the number of elements it contains.)
template<typename T, std::size_t N> 
constexpr std::size_t arraySize(T (&)[N]) noexcept 
{
	return N;
}

函数指针作为参数

void someFunc(int, double);

template<typename T>
void f1(T param);

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

f1(someFunc) // param推导为 ptr to func:void (*) (int, double)
f2(someFunc) // param推导为 ref to func:void (&) (int, double)

就是说对于函数参数:

  • 如果模板定义为一个值参数则推导为一个指针
  • 如果模板定义为一个引用则推导为引用

auto 类型推导

auto类型推导和模板一样对于:

auto a = ...

可以看成是:

template <typename T>

void f(T a)

来推导a的类型

  • auto type deduction is usually the same as template type deduction, but auto type deduction assumes that a braced initializer represents a std::initial izer_list, and template type deduction doesn’t.

  • auto in a function return type or a lambda parameter implies template type deduction, not auto type deduction.

decltype类型声明

  • decltype(varname)的类型推导和变量的类型一模一样,包含const和&都会保留下来
  • 对于decltype(expression)一个表达式:
    • 如果expression产生一个右值引用&&,则结果也是一个右值引用
    • 如果expression产生一个左值,则结果是一个左值引用
    • 如果expression产生一个右值,则结果是一个不是引用的普通类型

参考

effective modern c++