完美轉發指的是函數模板可以將自己的參數“完美”地轉發給內部調用的其它函數。所謂完美,即不僅能準確地轉發參數的值,還能保證被轉發參數的左、右值屬性不變。
文章目錄
- 場景
- 舊的方法
- 新的方法
- 內部實現
- 參考文獻
場景
思考下面的代碼:
template<typename T>
void function(T t) {otherfunc(t);
}
function()
函數模板中調用了otherfunc()
函數。我們想要的完美轉發是:
- 如果
function()
函數接收到的參數t
為左值,那么該函數傳遞給otherfunc()
的參數t
也是左值。 - 如果
function()
函數接收到的參數t
為右值,那么傳遞給otherfunc()
函數的參數t
也是右值
顯然 function()
函數模板沒有實現完美轉發,這是因為無論是左值還是右值傳遞進來,都會當作是左值,因為是非引用類型。
比如 function(10);
傳遞給 otherfunc()
也是左值而不是我們期望的右值,這在我們期望對左值和右值進行不同處理時會產生問題。
舊的方法
在C++98/03 標準下的 C++ 也可以實現完美轉發,只是實現方式比較麻煩。
C++98/03 標準中只有左值引用,可以細分為非 const引用和const引用:
- 非const引用作為函數模板參數,只能接收左值無法接收右值
- const左值引用既可以接收左值,也可以接收右值,但如果內部需要將參數傳遞給其他函數,需要被調用函數的參數也是 const,否則無法直接傳遞。
可見能實現轉發,但不夠"完美"。
#include <iostream>
using namespace std;
//重載被調用函數,查看完美轉發的效果
void otherfunc(int & t) {cout << "call lvalue\n";
}
void otherfunc(const int & t) {cout << "call rvalue\n";
}//重載函數模板,分別接收左值和右值
//接收右值參數
template <typename T>
void function(const T& t) {otherfunc(t);
}
//接收左值參數
template <typename T>
void function(T& t) {otherfunc(t);
}
int main()
{function(10);//10 是右值int x = 2;function(x);//x 是左值return 0;
}
輸出為
左值實參既能匹配 function(T& t)
也能匹配 function(const& t)
,編譯器會選擇更合適的 function(T& t)
。
新的方法
對于舊的方法,當模板函數有大量參數的情況,可能就需要編寫大量的重載函數模板。
在C++11標準中引入了右值引用,通常情況下右值引用只能接收右值,而對于函數模板中使用右值引用語法定義的參數來說,它既可以接收右值,也可以接收左值(稱為萬能引用)。
因此在C++11標準中實現完美轉發,只需要編寫如下一個模板函數即可:
template <typename T>
void function(T&& t) {otherdef(t);
}
但是還存在一個問題,如果我們傳入的參數是一個左值引用或右值引用的實參,如下所示:
int x = 5;
int& y = x;
function(y); // T為int&
int&& z = 1;
function(z); // T 為int&&
其中, function(y)
實例化的函數為 function(int& && t)
,由function(z)
實例化的函數為 function(int&& &&t)
,這在C++98/03是不支持的,而C++11引入了引用折疊規則:
- 當實參為左值或者左值引用(
A&
)時,函數模板中T&&
將轉變為A&
,即A& &&
=A&
。 - 當實參為右值或者右值引用(
A&&
)時,函數模板中T&&
將轉變為A&&
,即A&& &&
=A&&
。
還存在的問題是,在function()
函數內部,不管是 T& t
還是 T&& t
其實 t
都是一個左值,因此都會傳遞到 otherfunc(int& t)
。
所以我們需要一種解決方案來處理這個問題,C++11標準里的模板函數 forward()
就是用來解決這個問題,讓我們能傳遞左值/右值屬性,例子如下:
#include <iostream>
using namespace std;
//重載被調用函數,查看完美轉發的效果
void otherfunc(int & t) {cout << "lvalue\n";
}
void otherfunc(const int & t) {cout << "rvalue\n";
}
//實現完美轉發的函數模板
template <typename T>
void function(T&& t) {otherfunc(forward<T>(t));
}
int main()
{function(1);int x = 2;function(x);return 0;
}
輸出如下,正確傳遞了左值/右值屬性
內部實現
下面簡單看下內部實現(MSVC)
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {return static_cast<_Ty&&>(_Arg);
}_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");return static_cast<_Ty&&>(_Arg);
}
簡單地來說就是通過靜態的強制類型轉換+引用折疊,返回對應的結果。
比如下面的四鐘情況:
int x = 2;otherfunc(forward<int>(x)); // 匹配第一個,返回 int&&otherfunc(forward<int>(2)); // 匹配第二個,返回 int&&int& y = x;otherfunc(forward<int&>(y)); // 匹配第一個,返回 int&int&& z = 2;otherfunc(forward<int&&>(z)); // 匹配第一個,返回 int&&,可見右值引用是個左值
它的輸出結果如下
參考文獻
C++11、C++14、C++17、C++20新特性總結(5萬字詳解)