Effective Modern C++ 条款33 对需要std::forward的auto&&参数使用decltype
来源:互联网 发布:sql零基础视频教程 编辑:程序博客网 时间:2024/05/21 11:28
对需要std::forward的auto&&参数使用decltype
泛型lambda(generic lambda)是C++14最令人兴奋的特性之一——lambda可以在参数说明中使用auto。这个特性的实现很直截了当:闭包类中的operator()函数是一个模板。例如,给定这个lambda,
auto f = [](auto x) { return func(normalize(x)); };
闭包类的函数调用操作符看起来是这样的:
class SomeCompilerGeneratedClassName {public: template<typename T> auto operator()(T x) const // 关于auto返回类型看条款3 { return func(normalize(x)); } ... // 闭包类的其他功能};
在这个例子中,lambda对x
做的唯一的一件事就是把它转发给normalized
。如果normalized
区别对待左值和右值,这个lambda这样写是不合适的,因为即使传递给lambda的实参是个右值,lambda总是传一个左值(形参x
)给normalized
。
写这个lambda的正确方式是把x
完美转发给normalized
,这样做需要在代码中修改两个地方。第一,x要改成通用引用(看条款24),第二,借助std::forward(看条款25)把x
转发到normalized
。在概念上,修改成这样:
auto f = [](auto&& x) { return func(normalized(std::forward<???>(x))); };
但是,在概念和实现之间,有一个问题,就是你传递给std::forward的参数是什么类型,这决定了我上面写的???的地方会变成怎样。
一般来说,当你使用完美转发时,你是在一个接受类型形参T的模板函数中,所以你只是写std::forward<T>
。而在泛型模板中,没有你可以拿到的类型参数T。在lambda生成的闭包内,模板化operator()函数有一个T,但是在lambda中无法指定它,所以它对你没有任何帮助。
条款28解释过如果把一个左值传递给通用引用,通用引用的类型会变为左值引用;如果把一个右值传递给通用引用,通用引用会变为一个右值引用。那意味着在我们的lambda中,我们可以通过检查x
的类型来判断传递进来的实参是左值还是右值,decltype就可以让我们这样做。如果传递给lambda的是个左值,decltype(x)
将会产生一个左值引用;如果传递给lambda的是个右值,decltype(x)
将会产生一个右值引用。
条款28也解释了当使用std::forward时,有一个规则:传给它的类型参数是个左值引用时,表明返回的是个左值,传递给它的类型参数是个非引用类型时,表明返回的是个右值。在我们的lambda中,如果x
绑定的是一个左值引用,decltype(x)
将产生一个左值引用,这符合规则。不过,如果x
绑定的是个右值,decltype(x)
将会产生一个右值引用,而不是常规的非引用。(要注意的是,条款28中传递给std::forward的类型参数是T,而在lambda中无法使用T,也无法使用auto,所以只能将decltype(x)作为std::forward的类型参数。)
但看回在条款28中,std::forward的C++14实现:
template<typename T> // 在命名空间stdT&& forward(remove_reference_t<T>& param){ return static_cast<T&&>(param);}
如果一个用户想要完美转发一个Widegt类型的右值,它正常地用Widget类型(即非引用类型)实例化std::forward,然后std::forward产生这个函数:
Widget&& forward(Widget& param) // 当T为Widegt时,std::forward的实例化{ return static_cast<Widget&&>(param);}
不过,请思考如果用户代码想要完美转发一个Widget类型的右值,但是这次它没有服从规则将T指定为非引用类型,而是将T指定为右值引用,这会发生什么?那是,思考把T替换成Widget&&将会发生什么。在std::forward实例化、应用了std::remove_reference_t之后,引用折叠(再次看条款28,看懂!)之前,std::forward看起来是这样的:
Widget&& && forward(Widget& param) // 当T为Widget&&,std::forward的实例化{ // 还没发生引用折叠 return static_cast<Widget&& &&>(param);}
如果你用这个实例化和把T设置为Widget的std::forward那个实例化进行比较,你会发现它们是相同的。那意味着用一个右值引用实例化std::forward,和用一个非引用类型实例化std::forward产生的结果相同。
那是个很棒的消息,因为当一个右值实参传递给lambda形参x时,decltype(x)
会产生一个右值引用。我们在上面已经确认了把一个左值传给lambda时,decltype(x)
会产生一个可以传给std::forward的常规类型,而现在我们认识到对于右值,把decltype(x)
产生的类型传递给std::forward的类型参数是不传统的,不过,它产生的结果与传统类型产生的结果相同。所以无论是左值还是右值,把decltype(x)
传递给std::forward都能得到我们想要的结果,因此我们的完美转发lambda可以这样写:
auto f = [](auto&& param) { return func(normalize(std::forward<decltype(pram)>(param))); };
在这份代码加上6个点,就可以让我们的完美转发lambda接受多个参数了,因为C++14的lambda的参数是可变的:
auto f = [](auto&&... params) { return func(normalized(std::forward<decltype(params)>(params)...)); };
总结
需要记住的1点:
- 对需要std::forward的auto&&参数使用decltype(Use decltype on auto&& parameters to std::forward them.)。
- Effective Modern C++ 条款33 对需要std::forward的auto&&参数使用decltype
- Effective Modern C++ 条款25 对右值引用使用std::move,对通用引用使用std::forward
- 《Effective Modern C++》翻译--条款3: 理解decltype
- Effective Modern C++ 条款3 理解decltype
- Effective Modern C++ 条款23 理解std::move和std::forward
- Effective Modern C++ 条款20 把std::weak_ptr当作类似std::shared_ptr的、可空悬的指针使用
- [effective modern c++][3]理解decltype
- Effective Modern C++: Item 3 ->弄清decltype
- 完美转发std::forward与auto/decltype的一些小知识
- Effective Modern C++ 条款6 当auto会推断出不合理的类型时使用显式类型初始化语法
- 《Effective Modern C++》翻译--条款2: 理解auto自动类型推导
- Effective Modern C++ 条款34 比起std::bind更偏向使用lambda
- 条款25:对右值引用使用std::move,对统一引用使用std::forward
- Effective Modern C++ 条款18 用std::unique_ptr管理独占所有权的资源
- Effective Modern C++ 条款19 用std::shared_ptr管理共享所有权的资源
- Effective Modern C++ 条款36 如果异步执行是必需的,指定std::launch::async策略
- Effective Modern C++ 条款2 理解auto类型推断
- Effective Modern C++ 条款21 比起直接使用new,更偏爱使用std::make_unique和std::make_shared
- codeforces 190D Non-Secret Cypher 双指针
- thinkPHP查询操作
- 一篇很好的EDP入门介绍文章——了解AUX,PSR,ASSR 以及EDP版本的差异
- CSS浮动
- 看懂UML类图和时序图
- Effective Modern C++ 条款33 对需要std::forward的auto&&参数使用decltype
- android判断手势方向详解
- (转)作为一个新人,怎样学习嵌入式Linux?(韦东山)
- DatePicker、DatePickerDialog的使用
- Androidmainfest.xml
- ndk编程(1)--编译
- 微信浏览网站缓存清理
- 修改用户密码
- Rs232/rs485/rs422接口比较总结