【Effective modern C++ 提炼】《类型推导》- Item1:《模板类型推导规则》

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

f(expr);    // 调用该模板函数

编译期间,编译器会经过expr推导2种类型(T 跟 ParamType); web

类型常常是不一样的,由于ParamType包括了不少修饰符(e.g. const or reference); 数组

如:svg

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

int x = 0;
f(x);           // call f with an int

那么,T被推导成int类型,而ParamType被推导成const int&类型;函数

但T的类型推导,不单单取决于expr,也取决于ParamType的型式; spa

【下面的3个案例,讲解了这种特性】: 指针

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

f(expr);    // 调用该模板函数
Case1. ParamType 是一个指针/引用类型(不是通用引用);
  • 若是expr类型是一个引用,则忽略引用部分;
  • 而后对着ParamType,模式匹配expr的类型,以肯定T.
template<typename T>
void f(T & param);   // param is a reference

int x = 27;         // int
int const cx = x;   // int const
int const & rx =x;  // refernce to x

f(x);   // T => int, param's type => int&
f(cx);  // T => int const, param's type => const int&
f(rx);  // T => int const, param's type => const int&
  1. 由于cx、ex为const int值,所以,T被推导为const int;
  2. 当传递const对象给引用参数时,须要保持不可更改性,所以T包含了const部分;
  3. 类型推导过程当中,会忽略引用类型,因此T不包含&;
template<typename T>
void f(T const &param);   // param is now a ref-to-const

int x = 27;         // int
int const cx = x;   // int const
int const & rx =x;  // refernce to x

f(x);   // T => int, param's type => int const &
f(cx);  // T => int, param's type => int const &
f(rx);  // T => int, param's type => int const &
  1. 由于如今假设ParamType为ref-to-const,因此const不用做为T的一部分推导;
  2. 照常,对于rx,在推导T的过程当中,忽略其引用类型;
template<typename T>
void f(T* param);   // param is now a pointer

int x = 27;           // int
int const * px = &x;  // int const *(这是一个指向常量的指针,int * const 则是一个指针常量,是一个常量)

f(&x);   // T => int, param's type => int *
f(&px);  // T => int const, param's type => int const *
  1. 对于指针,推导T的方式,跟引用是同样的;
Case2. ParamType 是一个通用引用;(区别于“左值引用”和“右值引用”)
  • 若是expr是一个左值,T和ParamType都被推导为左值引用;(a.这是仅有的T被推导为引用的状况 b.虽然ParamType的格式是右值引用,但它们的类型还是左值引用)
  • 若是expr是一个右值,则规则符合Case1;
template<typename T>
void f(T && param);   // param is now a universal reference

int x = 27;         // int
int const cx = x;   // int const
int const & rx =x;  // refernce to x

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

对于通用引用的类型推导规则,区别于expr是左值仍是右值,而不一样;
对于非通用引用的类型推导,则彻底没有这种特殊的规则;code

Case3. ParamType 既不是指针,也不是引用;
  • 若是expr的类型是引用,同样忽略引用部分;
  • 若是expr有const部分,也忽略之;
  • 若是expr有volatile部分,也忽略之;(经常使用于设备驱动)
template<typename T>
void f(T param);    // param is now passed by value

int x = 27;         // int
int const cx = x;   // int const
int const &rx =x;   // refernce to x

f(x);   // T => int, param's type => int
f(cx);  // T => int, param's type => int
f(rx);  // T => int, param's type => int

由于param是彻底独立于x、cx、rx的(即它是一份拷贝),这即是为何在推导时,忽略expr的const部分的缘由;
这是ParamType无任何修饰的状况下才成立的,即当为引用或指针时,const仍是没法忽略的;regexp

char* const ptr1 = "abc";        // char * const
const char* const ptr2 = "abc";  // const char * const

f(ptr1); // T => char*, param's type => char * f(ptr2); // T => char const *, param's type => char const *

这里要注意指针常量,跟常量指针,以及常量指针常量的区别;xml

以数组为参数

数组类型 于 指针类型,是不用的,虽然有些状况下它们是通用的;
不少状况下,一个数组能衰化成一个指向其第1个元素的指针; 对象

const char name[] = "Hello";    // const char[6]

const char * ptrToName = name;  // const char * 

虽然能将array转成pointer,但这二者的类型,是不同的;

放到函数参数中:

void myFunc(const char param[]);
void myFunc(const char * param);

myFunc(name);

这两个函数声明是如出一辙的,这是C所遗留到C++上的一个内容,所以,对于myFunc(name)的调用,这两个函数均可以,但只能写其中1个,不然会报错;

放到函数模板中:

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

f(name);   // T => char const *, param's type => char const *

对于Case3这种类型的模板函数来讲,传入的name数组,由于数组参数声明会被视为指针参数,因此会被推导成 const char *;

解决方法:

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

f(name);    // T => const char[6], param's type => const char(&)[6]

T会被推导成实际的数组类型,类型包括了数组的size;

以函数为参数

跟数组同样,函数类型也可以衰化成指针类型;

void someFunc(int,double);

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

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

f1(someFunc);   // void(*)(int,double)
f2(somefunc);   // void(&)(int,double)
Things to Rememb
  • 模板类型推导期间,引用的参数,则忽略引用;
  • 当推导“通用引用”参数时,左值参数要特殊对待;
  • 当推导“传值”参数时,const 或 volatile 要忽略之;
  • 模板类型推导期间,数组或函数名称做为参数,会衰化为指针,除非使用引用参数;