在C++11中,&&
再也不只有逻辑与的含义,还多是右值引用:ios
void f(int&& i);
但也不尽然,&&
还多是转发引用:c++
template<typename T> void g(T&& obj);
“转发引用”(forwarding reference)旧称“通用引用”(universal reference),它的“通用”之处在于你能够拿一个左值绑定给转发引用,但不能给右值引用:程序员
void f(int&& i) { } template<typename T> void g(T&& obj) { } int main() { int n = 2; f(1); // f(n); // error g(1); g(n); }
一个函数的参数要想成为转发引用,必须知足:函数
参数类型为T&&
,没有const
或volatile
;code
T
必须是该函数的模板参数。rem
换言之,如下函数的参数都不是转发引用:get
template<typename T> void f(const T&&); template<typename T> void g(typename std::remove_reference<T>&&); template<typename T> class A { template<typename U> void h(T&&, const U&); };
另外一种状况是auto&&
变量也能够成为转发引用:编译器
auto&& vec = foo();
因此写范围for
循环的最好方法是用auto&&
:it
std::vector<int> vec; for (auto&& i : vec) { // ... }
有一个例外,当auto&&
右边是初始化列表,如auto&& l = {1, 2, 3};
时,该变量为std::initializer_list<int>&&
类型。io
转发引用,是用来转发的。只有当你的意图是转发参数时,才写转发引用T&&
,不然最好把const T&
和T&&
写成重载(若是须要的话还能够写T&
,还有不经常使用的const T&&
;其中T
是具体类型而非模板参数)。
转发一个转发引用须要用std::forward
,定义在<utility>
中:
#include <utility> template<typename... Args> void f(Args&&... args) { } template<typename T> void g(T&& obj) { f(std::forward<T>(obj)); } template<typename... Args> void h(Args&&... args) { f(std::forward<Args>(args)...); }
调用g
有几种可能的参数:
int i = 1; g(i);
,T
为int&
,调用g(int&)
;
const int j = 2; g(j);
,T
为const int&
,调用g(const int&)
;
int k = 3; g(std::move(k));
或g(4);
,T
为int
(不是int&&
哦!),调用g(int&&)
。
你也许会疑惑,为何std::move
不须要<T>
而std::forward
须要呢?这得从std::forward
的签名提及:
template<typename T> constexpr T&& forward(std::remove_reference_t<T>&) noexcept; template<typename T> constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
调用std::forward
时,编译器没法根据std::remove_reference_t<T>
反推出T
,从而实例化函数模板,所以<T>
须要手动指明。
可是这并无从根本上回答问题,或者能够进一步引出新的问题——为何std::forward
的参数不定义成T&&
呢?
缘由很简单,T&&
会把T&
、const T&
、T&&
和const T&&
(以及对应的volatile
)都吃掉,有了T&&
之后,再写T&
也没用。
且慢,T&&
参数在传入函数是会匹配到T&&
吗?
#include <iostream> #include <utility> void foo(int&) { std::cout << "int&" << std::endl; } void foo(const int&) { std::cout << "const int&" << std::endl; } void foo(int&&) { std::cout << "int&&" << std::endl; } void bar(int&& i) { foo(i); } int main() { int i; bar(std::move(i)); }
不会!程序输出int&
。在函数bar
中,i
是一个左值,其类型为int
的右值引用。更直接一点,它有名字,因此它是左值。
所以,若是std::forward
没有手动指定的模板参数,它将不能区分T&
和T&&
——那将是“糟糕转发”,而不是“完美转发”了。
最后分析一下std::forward
的实现,如下代码来自libstdc++:
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); }
当转发引用T&& obj
绑定左值int&
时,匹配第一个重载,_Tp
即T
为int&
,返回类型_Tp&&
为int&
(引用折叠:& &
、& &&
、&& &
都折叠为&
,只有&& &&
折叠为&&
);
const int&
同理;
当转发引用绑定右值int&&
时,匹配第二个重载,_Tp
为int
,返回类型为int&&
;
const int&&
同理。
综上,std::forward
能完美转发。
程序员老是要在Stack Overflow上撞撞墙才能学会一点东西。