1. 程式人生 > >c++11-17 模板核心知識(十)—— 區分萬能引用(universal references)和右值引用

c++11-17 模板核心知識(十)—— 區分萬能引用(universal references)和右值引用

- [引子](#引子) - [如何區分](#如何區分) - [模板引數](#模板引數) - [const disqualify universal reference](#const-disqualify-universal-reference) - [auto宣告](#auto宣告) ## 引子 `T&&`在程式碼裡並不總是右值引用: ```c++ void f(Widget&& param); // rvalue reference Widget&& var1 = Widget(); // rvalue reference auto&& var2 = var1; // not rvalue reference template void f(std::vector&& param); // rvalue reference template void f(T&& param); // not rvalue reference ``` `T&&`代表兩種含義: * 右值引用 * 萬能引用(universal references, or forwarding references) ## 如何區分 萬能引用一般出現在兩個場景中: * 模板引數 ```c++ template void f(T&& param); // param is a universal reference ``` * auto宣告 ```c++ auto&& var2 = var1; // var2 is a universal reference ``` 我們分別討論下這兩種場景。 ### 模板引數 **我們注意到,涉及到萬能引用的地方,都會有引數推導的過程,例如上面的T和var2. 而右值引用則沒有這個過程:** ```c++ void f(Widget&& param); // no type deduction; param is an rvalue reference Widget&& var1 = Widget(); // no type deduction; var1 is an rvalue reference ``` 但是即使語句設計到引數推導,也不一定就是萬能引用。例如: ```c++ template void f(std::vector&& param); // param is an rvalue reference std::vector v; f(v); // error! can't bind lvalue to rvalue reference ``` 這點還是比較好理解的。萬能引用需要依靠表示式來初始化自己是右值引用還是左值引用,但是上面這個例子沒有表現出這一點,它僅僅是推斷了T的型別,但是param的型別一直都是`std::vector&&`。 我們再舉一個vector中的例子: ```c++ template> class vector { public: void push_back(T&& x); // rvalue reference template void emplace_back(Args&&... args); // universal reference }; ``` * `push_back(T&& x)`中的T&&為右值引用,因為這個雖然是T&&,但是不涉及到引數推導。當push_back被instantiated時,實際的呼叫類似於: ```c++ std::vector v; ... class vector> { public: void push_back(Widget&& x); // rvalue reference … }; ``` 可以很明顯的看出此時沒有引數推導的過程。 * `template emplace_back(Args&&... args)`中的`Args&&`為萬能引用。Args與T是相互獨立的,所以Args有一個獨立的引數推斷過程。 #### const disqualify universal reference 有意思的是,當引數加上const後,就一定是右值引用: ```c++ template int f(T&& heisenreference); template int g(const T&&); int i; int n1 = f(i); // calls f(int&) int n2 = f(0); // calls f(int&&) int n3 = g(i); // error: would call g(const int&&), which would bind an rvalue reference to an lvalue ``` 至於為什麼會有這個規定,按照[Why adding `const` makes the universal reference as rvalue](https://stackoverflow.com/questions/38814939/why-adding-const-makes-the-universal-reference-as-rvalue)的說法,大體有兩點原因: * `const T&&`允許你過載一個函式模板,它只接受右值引用。如果`const T&&`也被當做universal reference,那麼將沒有辦法讓函式只接受右值引用。 * 顯示禁用某個函式接受右值引用:`template void cref(const T&&) = delete;` ### auto宣告 對於auto的場景來說,所有的`auto&&`都是萬能引用,因為它總是有引數推導的過程。例如定義一個記錄函式執行時間的lambda(C++14中允許使用auto來宣告lambda的函式): ```c++ auto timeFuncInvocation = [](auto &&func, auto &&... params) { start timer; std::forward(func)( // invoke func std::forward(params)... // on params ); stop timer and record elapsed time; }; ``` (完) 朋友們可以關注下我的公眾號,獲得最及時的更新: ![](https://img2020.cnblogs.com/blog/1298604/202011/1298604-20201103132150036-885052