[英]Is use of std::forward with auto&& the right thing to do
試圖理解使用帶有auto&&
變量的std::forward
是否是傳遞這些變量以允許移動的正確方法。
假設有一個功能:
void moveWidget(Widget&& w);
和調用者 - 兩個變量來引用rvalue和lvalue:
Widget w;
auto&& uniRefLV = w; // lvalue initialiser,
// uniRefLV's type is Widget&
auto&& uniRefRV = std::move(w); // rvalue initialiser,
// uniRefRV's type is Widget&&
我們知道auto&&
類型的變量是一個通用引用,因為存在類型推導。 這意味着uniRefRV
和uniRefLV
都是通用引用 。
在我的例子中,很明顯uniRefRV
是rvalue而uniRefLV
是左值,但從概念上講它們都是通用引用 ,如果定義不同,它們可以代表rvalue或lvalue 。
現在,我想調用moveWidget()
並完善這些通用引用類型。 該指南(由Scott Meyers撰寫)說:
通過
std::move
傳遞和返回rvalue引用 ,通過std::forward
傳遞通用引用 。
除非我完全誤解了指南,否則使用std::forward
似乎是合乎邏輯的。 但是讓我們考慮所有可能的選擇:
// (1) std::move:
moveWidget(std::move(uniRefLV)); // Compiles and looks fine
// but violates the guideline?
// (unconditionally casts lvalue to rvalue)
moveWidget(std::move(uniRefRV)); // Same as above - but not an issue here
// as we cast rvalue to rvalue
// (2) std::forward with Widget:
moveWidget(std::forward<Widget>(uniRefLV)); // Compiles, follows the guideline
// but doesn't look right - what if
// we didn't know Widget's type?
moveWidget(std::forward<Widget>(uniRefRV)); // Same as above
// (3) std::forward with decltype:
moveWidget(std::forward<decltype(uniRefLV)>(uniRefLV)); // Fails to compile! (VC10)
// follows the guideline
// has nice and short syntax :)
moveWidget(std::forward<decltype(uniRefRV)>(uniRefRV)); // Compiles fine
你認為我們應該同等對待引用uniRefLV
和uniRefRV
嗎?我們應該使用哪三個選項來完美轉發?
你誤解了指南。 或者至少從字面上看它。
我認為這里有三個重要的事情需要實現。
首先,這里所有類型都是已知的。 對於通用引用的建議主要適用於具有模板的通用代碼,如果某些內容是或者采用左值引用或右值引用,則根本不知道。
其次,該函數采用右值引用:您必須傳遞右值。 期。 這里別無選擇。
而符合邏輯的結論是,你並不想傳遞一個普遍的參考:無論你傳遞必須是一個右值,它永遠是一個左值。 通用引用可以是左值(如果它們被實例化為左值引用)。 傳遞一個通用引用意味着“我不知道這是什么樣的引用,我可以將它作為rvalue或左值傳遞,所以我正在傳遞它,就像我得到它一樣”。 這個問題的情況更像是“我確切地知道我必須通過什么,所以這就是我將要通過的”。
讓我們假設案例並不像看起來那么簡單。 而不是giveMeInt
我們這樣的東西:
template <typename T>
typename complex_computation<T>::type giveMeSomething(T t);
而不是moveMe
,你有一些實際上需要通用引用的東西,因此不需要無條件的std::move
。
template <typename T>
void wantForwarded(T&&);
現在你真的需要完美的轉發。
auto&& uniRef = giveMeSomething(iDontKnowTheTypeOfThis);
wantForwarded(std::forward<decltype(uniRef)>(uniRef);
我覺得你有點混淆了。 uniRefLV
和uniRefRV
都不是“通用參考”。 它們只是變量,兩者都有一定的類型。 這兩種類型恰好都是引用類型,但這並不重要。
要調用moveWidget
,你必須在兩種情況下都使用std::move
,並且在這兩種情況下,語義都是在移動之后,你的原始w
已被移出並因此不再處於確定狀態。 (你可以做的最好的事情就是銷毀它或重新分配它。)
現在,相比之下,真正的通用引用是模板參數 ,它必須始終是規范形式:
template <typename T> void foo(T &&);
// ^^^^^^
foo(bar());
foo(x);
foo(std::move(y));
那是,
T
作為函數參數T &&
出現 當滿足這些條件時, T &&
是通用引用,並通過std::forward<T>
傳遞它,它保留了正確的引用類型。
(1).a不違反指南。 你明確地說“我不再需要那個左值,所以接受它” ,這基本上就是std :: move的用途。 如果你在完成std :: move之后繼續使用uniRefLV - 這是違反准則的。 價值消失了(可能),你把它傳了過來。 這就是斯科特所說的。
(1).b這是合法的。 雖然那里不需要std :: move,但它確實簡化了源代碼的閱讀。
(2),(3)我們不必為std :: forward提供模板參數。 它應由編譯器推導出來! 至少這就是我的理解。 而且我沒有看到手動完成這一點。 轉發只是從類型定義中刪除引用(&&和&)。 這就是為什么它被稱為轉發並用於數據轉發。 當你有函數時:template void foo(T && t);
它可以被實例化為以下任何一個:
void foo(SomeType & t);
void foo(SomeType && t);
void foo(const SomeType & t);
void foo(const SomeType && t);
void foo(volatile SomeType & t);
void foo(volatile SomeType && t);
void foo(const volatile SomeType & t);
void foo(const volatile SomeType && t);
例如:
Widget a;
foo(a);
將實例化:
void foo(Widget & a);
不:
void foo(Widget a);
所以這些引用都被std :: forward刪除了。 只是*modificator* SomeType t
被呈現給傳遞給它的foo()
內部的任何函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.