完美转发

Reference collapse and perfect forwarding

转发问题

假设我们有一个工厂函数,工厂函数里面会new一个对象并返回,并将接受到的参数转发给构造函数:

1
2
3
4
template<typename T, typename S>
T* factory(S& arg) {
return new T(arg);
}

这样实现会有两个问题,

  1. S&限制了只能传入一个左值。
  2. func中传入arg是个左值,这样会触发拷贝构造而非移动构造。我们想要传入的引用和转发的引用类型一致。

所以,我们需要一种方案,来实现传入任意左值或右值作为参数,并且在转发参数时不改变其值的类型。

Reference collapsing

在c++ 11之前未引入引用折叠时,“引用的引用”这种表示形式是不合法的。但在引入引用折叠后,“引用的引用”这种表达会被编译器翻译掉:

  • T& & –> T&
  • T& && –> T&
  • T&& & –> T&
  • T&& && –> T&&
    也就是说,只有两个右值引用会被折叠为右值引用,其他情况都会折叠为左值引用。

Universal references

万能引用是基于引用折叠实现的,其形式和右值引用一样,都是&&。万能引用能同时匹配左值引用和右值引用。

1
2
3
4
5
6
7
8
template<typename T>
void func(T&& arg) {
// do something
}

int a = 1;
func(a); // ok
func(3); // ok

万能引用中只在模板中并且需要类型推导的时候生效,若是特化或者有函数重载的时候,万能引用是无效的。

当传入的是左值引用时,推导后的类型为T&& &,会被折叠为T&;当传入的是右值引用时,推导后的类型为T&& &&,折叠后的类型为T&&

std::forward

c++11引入了一个新的函数forward来帮助实现完美转发。下面是forward的实现(c++14):

1
2
3
4
5
template< class T >
constexpr T&& forward( std::remove_reference_t<T>& t ) noexcept;

template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;

注:c++11中为std::remove_reference<T>::type

以传入左值引用为例,当我们特化一个左值引用A&forward函数时:

1
2
3
constexpr A& && forward(typename std::remove_reference_t<A&>& __t) noexcept {
return static_cast<A& &&>(__t);
}

经过remove reference和引用折叠后,forward变为:

1
2
3
constexpr A& forward(typename A& __t) noexcept {
return static_cast<A&>(__t);
}

右值引用依然如此:

1
2
3
constexpr A&& && forward(typename std::remove_reference_t<A&&>& __t) noexcept {
return static_cast<A&& &&>(__t);
}

之后forward变为:

1
2
3
constexpr A&& forward(typename A& __t) noexcept {
return static_cast<A&&>(__t);
}

需要注意的是,std::forward只能特例化后使用,不能进行类型推导。

至此,我们一开始的转发问题便得到了解决:

1
2
3
4
template<typename T, typename S>
T* factory(S&& arg) {
return new T(std::forward<S>(arg));
}

Reference

std::forward

C++ Rvalue References Explained

Author

s.x.

Posted on

2021-11-19

Updated on

2021-11-21

Licensed under

Comments