1. 条款1:模板类型推导
template<typename T>
void func(ParamType param); //ParamType可以是T,T&,T&&
1️⃣:ParamType
是个引用或指针(T&),但不是个万能引用(T&&)。类别推导会这样运作:
- 如果实参具有引用类别,则将引用部分忽略。
- 之后则执行模式匹配,来决定T的类别。
template<typename T>
void func(T& param);
const int& rx = 1;
func(rx); //T的类型是const int
2️⃣paramType
是个万能引用(T&&)。
- 如果实参是个左值,则T和
ParamType
都会被推导为左值引用。 - 如果是个右值,则应用1️⃣的规则。
template<typename T>
void func(T&& param);
const int& rx = 1;
func(rx); //T的类型是const int&,param也是const int &
func(27); //T的类型是int,param的类型是int&&
3️⃣paramType
既非指针,也非引用,也就是按值传递。
- 一如之前,忽略其引用部分(如果有)。
- 也要忽略其
const
和volatile
。(合理,因为值传递的是副本,所以本就不应该保留const
)。
这里还有个特殊情况,那就是传数组。一般来说,数组参数会退化成指向数组首位的指针,所以按值传递,会将其考虑为指针。
但如果按引用方式,则会推导成实际的数组类别!
template<typename T>
void func(T& param);
const char name[] = "xxxx";
func(name); //T的类别推导结果是 const char [13]。而func的形参param则
//被推导成 const char (&)[13]。
有意思的是,可以用这个特点,创造出一个模板,用来推导数组含有的元素个数:
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
return N;
}
...
int keyV[] = {1, 2, 3};
int mapV[arraySize(keyV)];
2. 条款2:理解auto型别别推导
1️⃣除了一个特例情况,auto
类别推导就是模板类别推导。具体例子见书 P25。
2️⃣特例情况:声明一个int
,其初始值为27
,有下面四个方式:
int x1 = 27;
int x2(27);
//为了支持统一初始化
int x3 = {27};
int x4{27};
但如果换成auto
,后两者的推导类别为std::initializer_list<int>
,且含有单个值为27的元素。(这也是模板型别推导和它的区别,前者对于大括号,并不会产生如上推导,而是直接推导失败)。
3️⃣函数返回值推导,lambda表达式中的推导,这两者使用的auto
都是使用模板型别推导,而不是自身的原有推导。(所以特例情况也会失败)。
3. 条款3:理解decltype
1️⃣和模板型别推导和auto
推导相反,decltype
一般只会鹦鹉学舌,返回给定名字或表达式的确切型别:
A c; //decltype(c)是A。
A& c; //decltype(c)是A&。
2️⃣主要用途大概就是在于声明那些返回值类型依赖于形参类型的函数模板。一般是结合尾置返回类型使用(这里的auto
只是起到说明作用,并不需要型别推导):
template<typename Container, typename index>
auto func(Container a, index b) -> decltype(a[b]);
3️⃣如果直接使用auto
来推导返回类型,会有个问题,那就是左值引用会被忽略?那么我们本意是函数可以赋值,但却会报错:
template<typename Container, typename index>
auto func(Container a, index b);
//
func(a,b) = 10; //忽略左值引用,则返回值是一个右值。
解决方法是使用decltype(auto)
,指出进行型别推导,但却是按照decltype
的推导规则来:
template<typename Container, typename index>
decltype(auto) func(Container a, index b);
4️⃣最完美的方法,是再加上完美引用,具体理由,见书 P 32:
template<typename Container, typename index>
decltype(auto) func(Container a, index b)
{
//...
return std::forward<Container>(a)[i];
}
这个版本可以实现我们想要的一切。
5️⃣对于型别为T的左值表达式,除非该表达式仅有一个名字,不然decltype
总是得出型别T&
。所以要慎用:
decltype(auto) f1()
{
int x = 0;
return x; //返回int
return (x); //返回(int&),返回一个局部变量的引用!!!
}
4. 条款4:掌握查看型别推导结果的方法
具体见书。主要是为了说明前面三条条款的重要性。
5. 条款5:优先选用auto,而非显式型别声明
1️⃣有些变量的型别过长,又或者干脆是C++
的闭包类型(lambda表达式的返回式),使用auto
会方便很多。看看下面这个酷酷的写法:
auto c = [](const auto& p1, const auto& p2) {return *p1 < *p2; };
2️⃣有些杠精会说:std::function
也能做到啊。但使用这个会固定产生一个std::function
实例(大小固定),一般会使用更多内存(相对auto
),而且更慢。
复习:std::function是函数指针的推广,可以指涉任何可调用对象。
3️⃣避免型别捷径问题。总结来说,不用考运行环境问题。还有很多其他好处。
6. 条款6:当auto推导的型别不符合要求时,使用带显示型别的初始化物习惯用法
具体见书 P 49。
总结来说:
-
隐形的代理型(例如智能指针)可以导致
auto
根据初始化表达式推导出错误的型别。 -
带有显示型别的初始化物习惯用法强制auto推导出你想要的型别
auto index = static_cast<int>(d * c.size());