簡體   English   中英

使用std :: forward與auto &&是正確的事情

[英]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&&類型的變量是一個通用引用,因為存在類型推導。 這意味着uniRefRVuniRefLV都是通用引用

在我的例子中,很明顯uniRefRVrvalueuniRefLV左值,但從概念上講它們都是通用引用 ,如果定義不同,它們可以代表rvaluelvalue

現在,我想調用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

你認為我們應該同等對待引用uniRefLVuniRefRV嗎?我們應該使用哪三個選項來完美轉發?

你誤解了指南。 或者至少從字面上看它。

我認為這里有三個重要的事情需要實現。

首先,這里所有類型都是已知的。 對於通用引用的建議主要適用於具有模板的通用代碼,如果某些內容是或者采用左值引用或右值引用,則根本不知道。

其次,該函數采用右值引用:您必須傳遞右值。 期。 這里別無選擇。

而符合邏輯的結論是,你並不想傳遞一個普遍的參考:無論你傳遞必須是一個右值,它永遠是一個左值。 通用引用可以是左值(如果它們被實例化為左值引用)。 傳遞一個通用引用意味着“我不知道這是什么樣的引用,我可以將它作為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);

我覺得你有點混淆了。 uniRefLVuniRefRV都不是“通用參考”。 它們只是變量,兩者都有一定的類型。 這兩種類型恰好都是引用類型,但這並不重要。

要調用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM