This commit is contained in:
Yi Yang 2023-01-29 11:38:33 +08:00
parent dfaea82379
commit 305485e38e
5 changed files with 5 additions and 5 deletions

View File

@ -41,7 +41,7 @@ f(x); //用一个int类型的变量调用f
我们可能很自然的期望`T`和传递进函数的实参是相同的类型,也就是,`T`为`expr`的类型。在上面的例子中,事实就是那样:`x`是`int``T`被推导为`int`。但有时情况并非总是如此,`T`的类型推导不仅取决于`expr`的类型,也取决于`ParamType`的类型。这里有三种情况:
+ `ParamType`是一个指针或引用,但不是通用引用(关于通用引用请参见[Item24](../5.RRefMovSemPerfForw/item24.md)。在这里你只需要知道它存在,而且不同于左值引用和右值引用)
+ `ParamType`一个通用引用
+ `ParamType`一个通用引用
+ `ParamType`既不是指针也不是引用
我们下面将分成三个情景来讨论这三种情况,每个情景的都基于我们之前给出的模板:

View File

@ -173,7 +173,7 @@ decltype(auto) f2()
当使用`decltype(auto)`的时候一定要加倍的小心,在表达式中看起来无足轻重的细节将会影响到`decltype(auto)`的推导结果。为了确认类型推导是否产出了你想要的结果,请参见[Item4](../1.DeducingTypes/item4.md)描述的那些技术。
同时你也不应该忽略`decltype`这块大蛋糕。没错,`decltype`(单独使用或者与`auto`一起用)可能会偶尔产生一些令人惊讶的结果,但那毕竟是少数情况。通常,`decltype`都会产生你想要的结果,尤其是当你对一个名字使用`decltype`时,因为在这种情况下,`decltype`只是做一件本分之事:它产出名字的声明类型。
同时你也不应该忽略`decltype`这块大蛋糕。没错,`decltype`(单独使用或者与`auto`一起用)可能会偶尔产生一些令人惊讶的结果,但那毕竟是少数情况。通常,`decltype`都会产生你想要的结果,尤其是当你对一个变量使用`decltype`时,因为在这种情况下,`decltype`只是做一件本分之事:它产出变量的声明类型。
**请记住:**

View File

@ -128,7 +128,7 @@ for(const auto& p : m)
````
这样无疑更具效率,且更容易书写。而且,这个代码有一个非常吸引人的特性,如果你获取`p`的地址,你确实会得到一个指向`m`中元素的指针。在没有`auto`的版本中`p`会指向一个临时变量,这个临时变量在每次迭代完成时会被销毁。
面这两个例子——应当写`std::vector<int>::size_type`时写了`unsigned`,应当写`std::pair<const std::string, int>`时写了`std::pair<std::string, int>`——说明了显式的指定类型可能会导致你不看到的类型转换。如果你使用`auto`声明目标变量你就不必担心这个问题。
面这两个例子——应当写`std::vector<int>::size_type`时写了`unsigned`,应当写`std::pair<const std::string, int>`时写了`std::pair<std::string, int>`——说明了显式的指定类型可能会导致你不看到的类型转换。如果你使用`auto`声明目标变量你就不必担心这个问题。
基于这些原因我建议你优先考虑`auto`而非显式类型声明。然而`auto`也不是完美的。每个`auto`变量都从初始化表达式中推导类型,有一些表达式的类型和我们期望的大相径庭。关于在哪些情况下会发生这些问题,以及你可以怎么解决这些问题我们在[Item2](../1.DeducingTypes/item2.md)和[6](../2.Auto/item6.md)讨论所以这里我不再赘述。我想把注意力放到你可能关心的另一点使用auto代替传统类型声明对源码可读性的影响。

View File

@ -93,7 +93,7 @@ void processPointer<const char>(const char*) = delete;
```
如果你想做得更彻底一些,你还要删除`const volatile void*`和`const volatile char*`重载版本,另外还需要一并删除其他标准字符类型的重载版本:`std::wchar_t``std::char16_t`和`std::char32_t`。
有趣的是,如果类里面有一个函数模板,你可能想用`private`经典的C++98惯例来禁止这些函数模板实例化但是不能这样做因为不能给特化的成员模板函数指定一个不同于主函数模板的访问级别。如果`processPointer`是类`Widget`里面的模板函数, 你想禁止它接受`void*`参数那么通过下面这样C++98的方法就不能通过编译
有趣的是,如果类里面有一个函数模板,你可能想用`private`经典的C++98惯例来禁止这些函数模板实例化但是不能这样做因为不能给特化的成员模板函数指定一个不同于主函数模板的访问级别。如果`processPointer`是类`Widget`里面的模板函数, 你想禁止它接受`void*`参数那么通过下面这样C++98的方法就不能通过编译
```cpp
class Widget {

View File

@ -106,7 +106,7 @@ auto result2 = lockAndCall(f2, f2m, NULL); //错误!
...
auto result3 = lockAndCall(f3, f3m, nullptr); //没问题
````
代码虽然可以这样写,但是就像注释中说的,前两个情况不能通过编译。在第一个调用中存在的问题是当`0`被传递给`lockAndCall`模板,模板类型推导会尝试去推导实参类型,`0`的类型总是`int`,所以这就是这次调用`lockAndCall`实例化出的`ptr`的类型。不幸的是,这意味着`lockAndCall`中`func`会被`int`类型的实参调用,这与`f1`期待的`std::shared_ptr<Widget>`形参不符。传递`0`给`lockAndCall`本来想表示空指针,结果`f1`得到的是和它相差十万八千里的`int`。把`int`类型看做`std::shared_ptr<Widget>`类型给`f1`自然是一个类型错误。在模板`lockAndCall`中使用`0`之所以失败是因为在模板中,传给的是`int`但实际上函数期待的是一个`std::shared_ptr<Widget>`。
代码虽然可以这样写,但是就像注释中说的,前两个情况不能通过编译。在第一个调用中存在的问题是当`0`被传递给`lockAndCall`模板,模板类型推导会尝试去推导实参类型,`0`的类型总是`int`,所以这就是这次调用`lockAndCall`实例化出的`ptr`的类型。不幸的是,这意味着`lockAndCall`中`func`会被`int`类型的实参调用,这与`f1`期待的`std::shared_ptr<Widget>`形参不符。传递`0`给`lockAndCall`本来想表示空指针,但是实际上得到的一个普通的`int`。把`int`类型看做`std::shared_ptr<Widget>`类型给`f1`自然是一个类型错误。在模板`lockAndCall`中使用`0`之所以失败是因为在模板中,传给的是`int`但实际上函数期待的是一个`std::shared_ptr<Widget>`。
第二个使用`NULL`调用的分析也是一样的。当`NULL`被传递给`lockAndCall`,形参`ptr`被推导为整型(译注:由于依赖于具体实现所以不一定是整数类型,所以用整型泛指`int``long`等类型),然后当`ptr`——一个`int`或者类似`int`的类型——传递给`f2`的时候就会出现类型错误,`f2`期待的是`std::unique_ptr<Widget>`。