完美转发
Reference collapse and perfect forwarding
转发问题
假设我们有一个工厂函数,工厂函数里面会new一个对象并返回,并将接受到的参数转发给构造函数:
1 | template<typename T, typename S> |
这样实现会有两个问题,
S&
限制了只能传入一个左值。func
中传入arg是个左值,这样会触发拷贝构造而非移动构造。我们想要传入的引用和转发的引用类型一致。
所以,我们需要一种方案,来实现传入任意左值或右值作为参数,并且在转发参数时不改变其值的类型。
Reference collapsing
在c++ 11之前未引入引用折叠时,“引用的引用”这种表示形式是不合法的。但在引入引用折叠后,“引用的引用”这种表达会被编译器翻译掉:
- T& & –> T&
- T& && –> T&
- T&& & –> T&
- T&& && –> T&&
也就是说,只有两个右值引用会被折叠为右值引用,其他情况都会折叠为左值引用。
Universal references
万能引用是基于引用折叠实现的,其形式和右值引用一样,都是&&
。万能引用能同时匹配左值引用和右值引用。
1 | template<typename T> |
万能引用中只在模板中并且需要类型推导的时候生效,若是特化或者有函数重载的时候,万能引用是无效的。
当传入的是左值引用时,推导后的类型为T&& &
,会被折叠为T&
;当传入的是右值引用时,推导后的类型为T&& &&
,折叠后的类型为T&&
。
std::forward
c++11引入了一个新的函数forward
来帮助实现完美转发。下面是forward
的实现(c++14):
1 | template< class T > |
注:c++11中为
std::remove_reference<T>::type
以传入左值引用为例,当我们特化一个左值引用A&
的forward
函数时:
1 | constexpr A& && forward(typename std::remove_reference_t<A&>& __t) noexcept { |
经过remove reference和引用折叠后,forward
变为:
1 | constexpr A& forward(typename A& __t) noexcept { |
右值引用依然如此:
1 | constexpr A&& && forward(typename std::remove_reference_t<A&&>& __t) noexcept { |
之后forward
变为:
1 | constexpr A&& forward(typename A& __t) noexcept { |
需要注意的是,std::forward
只能特例化后使用,不能进行类型推导。
至此,我们一开始的转发问题便得到了解决:
1 | template<typename T, typename S> |