c++11-17 模板核心知識(十)—— 區分萬能引用(universal references)和右值引用
阿新 • • 發佈:2020-12-01
- [引子](#引子)
- [如何區分](#如何區分)
- [模板引數](#模板引數)
- [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