[Effective Modern C++] Item 1. Understand template type deduction - 了解模板类型推断

条款一 了解模板类型推断

基本情况

首先定义函数模板和函数调用的形式如下,在编译期间,编译器推断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 = ;
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 = ;
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 = ;
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 = ;
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 = ;
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 -> rvalue, T -> int, param -> int&&

  当使用通用引用的时候,左值实参与右值实参的类型推断不同,但是对于非通用引用则没有区别

  • ParamType既不是指针又不是引用

  如果expr是引用类型,则忽略引用部分

  在忽略引用部分后,如果expr是const或volatile,也同时忽略

template<typename T>
void f(T param); int x = ;
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
  • 在模板类型推断的过程中,数组或函数的实参退化为指针,除非他们被用来初始化引用
上一篇:Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)


下一篇:day12:usermod及用户密码管理