template<typename T>
void f(ParamType param);
f(expr); // 调用该模板函数
编译期间,编译器会通过expr推导2种类型(T 跟 ParamType);
类型经常是不同的,因为ParamType包括了很多修饰符(e.g. const or reference);
如:
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
的型式;
【下面的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&
- 因为cx、ex为const int值,因此,T被推导为const int;
- 当传递const对象给引用参数时,需要保持不可更改性,因此T包含了const部分;
- 类型推导过程中,会忽略引用类型,所以T不包含&;
template<typename T>
void f(T const ¶m); // 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 &
- 因为现在假设
ParamType
为ref-to-const,所以const不用作为T的一部分推导;- 照常,对于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 *
- 对于指针,推导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是左值还是右值,而不同;
对于非通用引用的类型推导,则完全没有这种特殊的规则;
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还是无法忽略的;
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 *
这里要注意指针常量,跟常量指针,以及常量指针常量的区别;
以数组为参数
数组类型 于 指针类型,是不用的,虽然有些情况下它们是通用的;
很多情况下,一个数组能衰化成一个指向其第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 要忽略之;
- 模板类型推导期间,数组或函数名称作为参数,会衰化为指针,除非使用引用参数;