条款一 了解模板类型推断
基本情况
首先定义函数模板和函数调用的形式如下,在编译期间,编译器推断T和ParamType的类型,两者基本不相同,因为ParamType常常包含const、引用等修饰符
template<typename T> void f(ParamType param); // 函数模板形式 f(expr); // 函数调用
存在T的类型即为expr类型的情况,如下T为int
templat<typename T> void f(const T& param); int x = 0; f(x); // T -> int
但是T的类型的推断不仅与expr有关,还和ParamType有关。有如下三种情况:
- ParamType是指针或引用类型,但不是通用引用(universal reference)
如果expr是引用类型,则忽略引用部分
然后通过模式匹配expr与ParamType来决定T
向函数模板传递一个const对象是合法的,T会添加对应的const,如下所示
template<typename T> void f(T& param); int x = 27; const int cx = x; const int& rx = x; f(x); // T -> int, param -> int& f(cx); // T -> const int, param -> const int& f(rx); // T -> const int, param -> const int&
以上示例演示的是左值引用形参,对于右值应用也一样,当然只有右值实参能传入右值引用形参,但对于类型推断没有影响
const T&的情况
template<typename T> void f(const T& param); int x = 27; const int cx = x; const int& rx = x; f(x); // T -> int, param -> int& f(cx); // T -> int, param -> const int& f(rx); // T -> int, param -> const int&
T*的情况
template<typename T> void f(T* param); int x = 27; const int *px = &x; f(&x); // T int, param -> int* f(px); // T const int, param -> const int*
- ParamType是通用引用
如果expr是左值,则T与ParamType被推断为左值引用。这非常不同寻常,首先这是模板类型的唯一情境T被推断为引用,然后即使ParamType的语法是一个右值引用,它推断出的类型也是左值引用
如果expr是右值,则适用情况一的规则
template<typename T> void f(T&& param); int x = 27; const int cx = x; const int& rx = x; f(x); // x -> lvalue, T -> int&, param -> int& f(cx); // x -> lvalue, T -> const int&, param -> const int& f(rx); // x -> lvalue, T -> const int&, param -> const int& f(27); // 27 -> rvalue, T -> int, param -> int&&
当使用通用引用的时候,左值实参与右值实参的类型推断不同,但是对于非通用引用则没有区别
- ParamType既不是指针又不是引用
如果expr是引用类型,则忽略引用部分
在忽略引用部分后,如果expr是const或volatile,也同时忽略
template<typename T> void f(T param); int x = 27; const int cx = x; const int& rx = x; f(x); // T -> int, param -> T f(cx); // T -> int, param -> T f(rx); // T -> int, param -> T
由于是拷贝,实参的const不再生效
以下是一个特殊情形分析
template<typename T> void f(T param); const char* const ptr = "Fun with pointers"; f(ptr); // T -> const char*, param -> const char*
ptr是按值传递,则ptr的const需要舍弃,则param的类型为const char*
数组实参
尽管数组类型与指针类型在有时候可以转换(许多情境下数组退化为指向第一个元素的指针,array-to-pointer decay rule),但依旧值得探讨一些细节问题。
实际上没有数组类型的形参,其会转换为指针
一般数组情形
template<typename T> void f(T param); const char name[] = "J. P. Briggs"; f(name); // T -> const char*, param -> const char*
引用数组情形
template<typename T> void f(T& param); const char name[] = "J. P. Briggs"; f(name); // T -> const char[13], param -> const char(&)[13]
以下函数在编译器能直接获取已知数组的长度
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); // T -> void (*)(int, double), param -> void (*)(int, double) f2(someFunc); // T -> ?, param -> void (&)(int, double)
总结
- 在模板类型推断过程中,引用实参被当做非引用处理,所以他们的引用性被忽略
- 当推断通用引用的形参时,左值实参特殊对待
- 当推断传值形参的类型时,const、volatile的实参忽略其const、volatile
- 在模板类型推断的过程中,数组或函数的实参退化为指针,除非他们被用来初始化引用