类型推导
条款 1:模板类型推导
-
在模板类型推导时,具有引用类型的实参会被视为非引用类型,即引用会被忽视。
-
1 2 3 4 5 6 7 8 9 10
template<typename T> void f(T & param); int x = 27; //x是int const int cx = x; //cx是const int const int & rx = cx; //rx是指向const int的引⽤ f(x); //T是int,param的类型是int& f(cx); //T是const int,param的类型是const int & f(rx); //T是const int,param的类型是const int &
-
-
对万能引用形参进行推导时,左值参数会特殊处理。
- 左值:T 和 paramType 都为左值引用。
- 右值:与第一条原则相同,即 paramType 为右值引用,T 为非引用类型。
-
对按值传递的形参进行推导时,若实参类型中带有 const 或 volatile 饰词,会将其忽视,当作不带这些饰词的类型。
-
在模板类型推导时,数组或者函数类型的实参会退化成对应的指针,除非它们用来初始化引用。
-
1 2 3 4 5 6 7 8 9 10 11 12
void someFunc(int, double); //someFunc是一个函数, //类型是void(int, double) template<typename T> void f1(T param); //传值给f1 template<typename T> void f2(T & param); //传引用给f2 f1(someFunc); //param被推导为指向函数的指针, //类型是void(*)(int, double) f2(someFunc); //param被推导为指向函数的引用, //类型是void(&)(int, double)
-
条款 2:auto 类型推导
-
在正常情况下,auto 类型推导和模板类型推导的结果是一样的。但是也有例外,当我们使用大括号括起的初始化表达式时,auto 会将其推导为 std::initializer_list,而模板推导则不会。
-
1 2 3 4 5 6
auto x = { 11, 23, 9 }; //x的类型是std::initializer_list<int> template<typename T> //带有与x的声明等价的 void f(T param); //形参声明的模板 f({ 11, 23, 9 }); //错误!不能推导出T
若是想要推导出 T 的类型,则需要将模板中的 param 指定为 std::initializer_list<T> :
1 2 3 4 5
template<typename T> void f(std::initializer_list<T> initList); f({ 11, 23, 9 }); //T被推导为int,initList的类型为 //std::initializer_list<int>
-
-
在函数返回值或 lambda 表达式中使用 auto 时,使用模板类型推导而非 auto 类型推导。
-
自从 C++ 14 开始允许使用 auto 来推导函数返回值,但是其虽然是 auto,但是实际上确实模板类型推导。我们怎样确认呢?可以尝试返回一个大括号括起的初始化表达式。
1 2 3 4 5 6 7 8 9 10 11
auto createInitList() { return { 1, 2, 3 }; //错误!不能推导{ 1, 2, 3 }的类型 } std::vector<int> v; … auto resetV = [&v](const auto& newValue){ v = newValue; }; //C++14 … resetV({ 1, 2, 3 }); //错误!不能推导{ 1, 2, 3 }的类型
-
条款 3:decltype
-
通常 decltype 会得出变量的类型并且不做任何修改。
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
const int i = 0; //decltype(i)是const int bool f(const Widget& w); //decltype(w)是const Widget& //decltype(f)是bool(const Widget&) struct Point{ int x,y; //decltype(Point::x)是int }; //decltype(Point::y)是int Widget w; //decltype(w)是Widget if (f(w))… //decltype(f(w))是bool template<typename T> //std::vector的简化版本 class vector{ public: … T& operator[](std::size_t index); … }; vector<int> v; //decltype(v)是vector<int> … if (v[0] == 0)… //decltype(v[0])是int&
-
-
对于类型为 T 的左值表达式,decltype 总是推导出类型 T&。
-
1 2 3 4 5 6 7 8 9 10 11 12
decltype(auto) f1() { int x = 0; … return x; //decltype(x)是int,所以f1返回int } decltype(auto) f2() { int x = 0; return (x); //decltype((x))是int&,所以f2返回int& }
-
-
C++ 14 支持 decltype(auto),与 auto 一样,它会依据初始化表达式来推导类型,但它使用的是 decltype 的规则。
-
1 2 3 4 5 6 7 8
Widget w; const Widget& cw = w; auto myWidget1 = cw; //auto类型推导 //myWidget1的类型为Widget decltype(auto) myWidget2 = cw; //decltype类型推导 //myWidget2的类型是const Widget&
-
条款 4:查看类型推导结果的方法
-
利用 IDE 编辑器、编译器错误消息 和 Boost.TypeIndex 库通常能够查看到推导的类型。
-
工具判断的结果可能不准确,因此理解 C++ 类型推导规则是必要的。
auto
条款 5:优先选用 auto,而非显式类型声明
-
auto 变量必须初始化,它通常可以避免⼀些移植性和效率性的问题,也使得重构更⽅便,比显式指定类型更加简洁。
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
//大大的简化了代码 //使用auto auto derefUPLess = [](const std::unique_ptr<Widget> &p1, const std::unique_ptr<Widget> &p2) { return *p1 < *p2; }; //显式执行类型 std::function<bool(const std::unique_ptr<Widget> &, const std::unique_ptr<Widget> &)> derefUPLess = [](const std::unique_ptr<Widget> &p1, const std::unique_ptr<Widget> &p2) { return *p1 < *p2; }; //可能会避开一些潜在的风险 unordered_map<std::string, int> m; //看起来没有问题,但是实际unordered_map的key是cosnt std::string //类型不匹配,虽然没有报错,但是编译器会将m的值拷贝到一个临时变量中,再将临时变量的引用绑定到p上,且在循环结束后再将这个临时变量销毁,增加了大量拷贝、销毁资源的成本 for(const std::pair<std::string, int>& p : m) { … //用p做一些事 } for(const auto& p : m) { … }
-
-
auto 类型的变量有着条款 2 和 条款 6 中描述的毛病。
条款 6:当 auto 推导的类型不符合要求时,使用显式类型初始化
-
隐形的代理类可能会导致 auto 根据初始化表达式推导出错误的类型。
-
1 2 3 4 5 6
std::vector<bool> features(const Widget& w); Widget w; … bool var_a = features(w)[5]; //bool,存在隐式类型转换 auto vat_b = features(w)[5]; //代理类型std::vector<bool>::reference,如果不明确类型则可能导致未定义的行为。
对于 std::vector<bool> 来说,为了节省空间,底层存储时使用了 1 bit 而非 1 byte 的 bool 类型来存储数据。由于 C++ 禁止对 bits 的引用,因此当我们调用
operator[]
时返回的其实是一个代理类型 std::vector<bool>::reference,其能够模拟 bool& 的行为。
-
-
显式类型的初始化惯用法会强制 auto 推导出想要的类型。
-
1 2 3 4
double calcEpsilon(); //返回公差值 float ep = calcEpsilon(); //double到float隐式转换 auto ep = static_cast<float>(calcEpsilon()); //显式类型初始器惯用法
-