[英]C++ lvalues and rvalues in template functions
我從http://www.cplusplus.com/reference/utility/forward中取樣:
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
打印
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
但是,如果我想制作重載模板,那樣:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T>
void overloaded (T&& x) {std::cout << "[rvalue]";}
它打印
calling fn with lvalue: [rvalue][rvalue]
calling fn with rvalue: [rvalue][rvalue]
有沒有辦法使用模板功能來推斷我轉移到哪個對象的功能?
是的,可以,只需在第二個重載中添加一個const
:
template<typename T>
void overloaded (const T& x);
template<typename T>
void overloaded (const T&& x);
// ^^^^^
你需要const
的原因是使x
不是轉發引用。 轉發引用非常貪婪,如果你沒有傳入完全相同的類型(包括任何cv限定符),那么將選擇轉發引用重載。
在您的情況下,因為您沒有將const
對象傳遞給overload
,所以第二個重載將始終是更好的匹配。
但是如果你在那里添加一個const
,那么它不再是轉發引用,只能接受rvalues而不能接受左值,並且不會更好地匹配左值作為結果但是對於任何rvalues都比const&
更好地匹配超載。
如果您需要從x
移動,那么您將不得不做其他事情。 無論你有rvalue還是左值,都要刪除轉發引用中的const&
overload和branch:
template <typename T> void overloaded(T &&x) {
if /*constexpr*/ (std::is_lvalue_reference_v<T>)
std::cout << "[lvalue]";
else
std::cout << "[rvalue]";
}
注意:如果您執行的某些特定內容對分支或其他分支無效,則需要使用if constexpr
。
只要函數模板參數具有類型“對模板參數的rvalue引用”,就像template<typename T> void overloaded(T&& x);
的x
template<typename T> void overloaded(T&& x);
,它成為“轉發參考”又名“通用參考”。 它們遵循特殊規則,即它們可以匹配左值或右值參數。
當參數是rvalue時, T
的類型為非引用,參數類型為右值引用。
當參數是左值時, T
是左值引用類型。 這是有意義的,因為第二個“引用 - 折疊規則”:如果您將一個&
或&&
添加到類型別名( typedef
, using
定義的類型或類型模板參數)已經是引用,它將形成一個引用類型如果別名都是右值引用並且您在其他三種情況下添加了&&
或左值引用,則這是一個右值引用。
因此,當x
是int
類型的左值時,調用overloaded(x)
時, overloaded
匹配的兩個重載:
template<typename T>
void overloaded (const T& x);
// Specialization void overloaded<int>(const int&);
template<typename T>
void overloaded (T&& x);
// Specialization void overloaded<int&>(int&);
但第二個專業化是一個更好的匹配參數類型,所以它贏了。
為了防止這種情況,我將使用SFINAE技術將第二次overloaded
限制為僅非引用類型。
#include <type_traits>
// As before:
template<typename T>
void overloaded (const T& x);
template<typename T>
auto overloaded (T&& x) -> std::enable_if_t<!std::is_reference<T>::value>;
現在給出任何左值參數,第二個模板將推導T
為左值引用類型,無法將其替換為返回類型,然后模板將被忽略以進行重載解析,以便可以使用第一個重載。
第二個overloaded
模板不適用於rvalues,而是轉發引用。 接收調用往往非常積極,因為它會完全正確地推斷出const-enss和value-ness,幾乎在所有情況下都是首選(你的int調用是左值,但它不是const,所以這就是為什么它被調用)。
要使其工作,您需要約束該模板。 當該模板與左值匹配時, T
將具有引用類型,以便引用折疊規則表明T&&
將折疊為T&
。 所以我們可以這樣做:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T, std::enable_if_t<!std::is_reference<T>::value, int> = 0>
void overloaded (T&& x) {std::cout << "[rvalue]";}
如果你這樣做,那么你應該得到預期的輸出。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.