第一章:型别推导
2024年10月22日大约 5 分钟
《Effective Modern C++》阅读笔记(一)
条款1:理解模板型别推导
函数模板形式
// 声明形式
template<typename T>
void f(ParamType param);
// 调用形式
f(expr);
后文主要针对T
,ParamType
和decltype(expr)
这三种类型进行讨论。
ParamType
为指针或者引用,而非万能引用
函数的形式如下:
template<typename T>
void f(T& param); // param是一个引用
类型推导会这样进行:
- 如果
expr
的类型是一个引用,忽略引用部分,保留const
/volatile
属性; - 然后
expr
的类型与ParamType
进行模式匹配来决定T
。
这里举个例子:
// 变量定义
int x=27; // x是int
const int cx=x; // cx是const int
const int& rx=x; // rx是指向作为const int的x的引用
// 函数调用
f(x); // T是int,param的类型是int&
f(cx); // T是const int,param的类型是const int&
f(rx); // T是const int,param的类型是const int&
如果ParamType
带有const
/volatile
属性,则推导过程与上述过程类似:
template<typename T>
void f(const T& param); // param现在是reference-to-constint x = 27; // 如之前一样
const int cx = x; // 如之前一样
const int& rx = x; // 如之前一样
f(x); // T是int,param的类型是const int&
f(cx); // T是int,param的类型是const int&
f(rx); // T是int,param的类型是const int&
如果param
是一个指针,推导过程也是类似:
template<typename T>
void f(T* param); // param现在是指针
int x = 27; // 同之前一样
const int *px = &x; // px是指向作为const int的x的指针
f(&x); // T是int,param的类型是int*
f(px); // T是const int,param的类型是const int*
ParamType
为万能引用
函数的形式如下:
template<typename T>
void f(const T& param); // param现在是reference-to-const
推导过程参考引用折叠
ParamType
既非指针也非引用
函数的形式如下:
template<typename T>
void f(T param); // 以传值的方式处理param
类型推导会去除传入参数的所有属性,只保留值类型:
int x=27; // 如之前一样
const int cx=x; // 如之前一样
const int & rx=cx; // 如之前一样
f(x); // T和param的类型都是int
f(cx); // T和param的类型都是int
f(rx); // T和param的类型都是int
如果传入的是一个指针,则会去掉指针本身的const
/volatile
属性,保留指针指向对象的const
/volatile
属性:
template<typename T>
void f(T param); // 仍然以传值的方式处理param
const char* const ptr = // ptr是一个常量指针,指向常量对象
"Fun with pointers";
f(ptr); // 传递const char * const类型的实参
// T的类型为const char*
特殊情况
对于函数形式为:
template<typename T>
void f1(T param); // 以传值的方式处理param
template<typename T>
void f2(T& param); // 以引用的方式处理param
如果传入的是数组类型,则:
const char name[] = "J. P. Briggs"; // name的类型是const char[13]
f1(name); // T被推导为const char*
f2(name); // T被推到为const char (&)[13]
如果传入的是函数类型,则:
void someFunc(int, double); // someFunc是一个函数,
// 类型是void(int, double)
f1(someFunc); // param被推导为指向函数的指针,
// 类型是void(*)(int, double)
f2(someFunc); // param被推导为指向函数的引用,
// 类型是void(&)(int, double)
条款2:理解auto
类型推导
型别推导
auto
的型别推导与模板推导规则基本一致:
auto xx = var; // 值类型推导
auto& xx = var; // 引用类型推导
auto&& xx = var; // 引用折叠
初始化
int x1 = 27;
int x2(27);
int x3 = { 27 };
int x4{ 27 }; // 以上将x1,x2,x3,x4初始化为27
auto x1 = 27; //类型是int,值是27
auto x2(27); //同上
auto x3 = { 27 }; //类型是std::initializer_list<int>,值是{ 27 }
auto x4{ 27 }; //同上
auto x5 = { 1, 2, 3.0 }; //错误!无法推导std::initializer_list<T>中的T
对于大括号初始化表达式的处理方式,是auto
型别推导和模板型别推导唯一不同之处:
auto x = { 11, 23, 9 }; // x的类型是std::initializer_list<int>
template<typename T> // 带有与x的声明等价的
void f(T param); // 形参声明的模板
f({ 11, 23, 9 }); // 错误!不能推导出T
template<typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 }); // T被推导为int,initList的类型为
// std::initializer_list<int>
C++14允许auto
用于函数返回值并会被推导,而且C++14的lambda函数也允许在形参声明中使用auto
。但是在这些情况下auto
实际上使用模板类型推导的那一套规则在工作,而不是auto
类型推导,所以说下面这样的代码不会通过编译:
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
在推导时会保留完整类型,注意如果推导传入的是右值则会去掉引用转换为常量值,例如:
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&
decltype(auto)
是一种特殊的用法,常用作返回值类型推导,用来保留完整的返回值类型:
decltype(auto) foo() {
int x = 42;
return x; // 返回 int
}
decltype(auto) bar() {
int x = 42;
return (x); // 返回 int&
}
decltype(auto) bar(vector<int>& arr) {
return arr[0]; // 返回 int&
}
// 等效于
auto& bar(vector<int>& arr) {
return arr[0]; // 返回 int&
}
// 注意decltype(auto)可以接收任意类型的返回值,但是auto&只能接收非右值类型的返回值
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; //auto类型推导
//myWidget1的类型为Widget
decltype(auto) myWidget2 = cw; //decltype类型推导
//myWidget2的类型是const Widget&
条款4:掌握查看型别推导结果的方法
略。。。