在C++11中,&&不再只有邏輯與的含義,還可能是右值參考:
void f(int&& i);
但也不盡然,&&還可能是轉發參考:
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; -
T必須是該函式的模板引數,
換言之,以下函式的引數都不是轉發參考:
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&&:
std::vector<int> vec;
for (auto&& i : vec)
{
// ...
}
有一個例外,當auto&&右邊是初始化串列,如auto&& l = {1, 2, 3};時,該變數為std::initializer_list<int>&&型別,
轉發參考,是用來轉發的,只有當你的意圖是轉發引數時,才寫轉發參考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上撞撞墻才能學會一點東西,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/38524.html
標籤:C++
上一篇:P5727 冰雹猜想
