無論是模板還是auto的型別推導,絕大部分情況下都會忽略引用。舉個栗子:
template<typename T>
void f(T param) {
如果現在有int類型的變量x和它的引用rx,當rx傳給函數f時,我們的直覺是T應該被推導為引用,由此直覺還認為定義一個T類型的變量temp並用param賦值后,temp和param應該都是x的引用。然而直覺是錯誤的,這里T會被推導為int,而非int&。可以用代碼測試一下,如果temp也是引用,那么對temp另外賦值應該會更改x的值:
template<typename T>
void f(T param) {
T temp = param;
temp = 5; // 若temp是引用,那么這條語句會把x的值改為5
}
//main函數中:
int x = 2;
int& rx = x;
f(rx); //實參為int&
cout << x; // 看看x的值是否被改變
最終輸出結果為2,說明T只是int類型,temp是一個拷貝而非引用。
auto的型別推導同樣。
只有一個情況例外:當形參是萬能指針而實參是左值時,T為左值引用。也就是說將上面代碼中f的形參類型改為T&&
,此時再運行代碼會發現輸出為5,說明T為int&。
我們有時會忘記型別推導忽略引用這個特點。比如,大多數容器的operator []會返回T&,因此當我們希望寫一個函數,該函數可以返回位於某容器特定下標位置的元素時,我們可能會寫出這樣的代碼:
/*向該函數傳入一個容器和一個下標;函數返回位於這個容器該下標位置的元素的引用*/
template<typename Container, typename Index>
auto find_ele(Container& c, Index i) {
do_sth();
return c[i];
}
然后會像下面這樣來使用這個函數:
find_ele(arr, 3) = 5;
然后會發現報錯了。編譯器會說find_ele(arr, 3)並非左值,不能被賦值。這就是因為雖然return c[i]
返回的是引用,但auto
型別推導時卻會忽略引用。
如果希望將引用作為返回,可以像下面這樣寫函數頭部(僅考慮實參為左值的情況):
//(1)
auto& find_ele(Container& c, Index i)
//(2)
auto find_ele(Container& c, Index i) -> decltype(c[i])
//(3)
decltype(auto) find_ele(Container& c, Index i)
(1)顯式規定返回值必須是引用;(2)和(3)都使用了decltype,這是因為decltype在一般情況下都會忠實地返回參數自身的類型而不做更改。