# 围绕std::move()谈谈通用引用,类型推导和引用折叠

## 从std::move()说起

对于`std::move()`，既可以接受一个`左值`，也可以接受一个`右值`。它的函数定义是这个样子的
```cpp
template <typename T>
typename remove_reference<T>::type&& move(T&& t) noexcept {
    return static_cast<typename remove_reference<T>::type&&>(t);
}
```
但形参中的`T&&`并不是是一个`右值引用`, 如果他是一个`右值引用`, 那么下面代码是无法编译通过的
```cpp
Foo f;
std::move(f); // error
```
其实这里的`T&&`是一个[通用引用(universal reference)](https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers)。


## 通用引用

> T&& Doesn’t Always Mean “Rvalue Reference”  by Scott Meyers

通用引用有一条性质，当匹配一个`左值表达式`时, 会表现的像一个`左值引用`; 当匹配一个`右值表达式`的时候, 会表现的像一个`右值引用`. (后面会谈到是如何实现的)

### 构成通用引用的条件
正如 Scott Meyers
> If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

所说的, 构成`通用引用`必须具有两个条件

1. 形如`T&&`
2. T的类型需要推导(deduced type)

#### 举例

* 必须形如`T&&`
```cpp
int lvalue = 3;

const auto&& r = lvalue; //错误：r不是通用引用，而是右值引用，故不能通过左值初始化！

template<typename T>
void func(const T&& p);

func(lvalue);            //错误：p不是通用引用，而是右值引用，故不能通过左值初始化！
```
必须是`T&&`, 哪怕是`const T &&`都不行。

* 必须存在类型推导
```cpp
template<typename T>
class Bar {
private:
    T member;
public:
    Bar(Bar&& other); // 不是通用引用，而是右值引用
};
```
`other`的类型是`Bar&&`, 而非`T&&`. 因此，是`右值引用`。

## 类型推导和引用折叠

那么`通用引用`是如何实现 *当匹配一个`左值表达式`时, 会表现的像一个`左值引用`; 当匹配一个`右值表达式`的时候, 会表现的像一个`右值引用`* 的呢?
1. 编译器允许它匹配左值和右值
2. 类型推导和引用折叠最终让它变成`左值引用`或`右值引用`

### 类型推导规则
通用引用`auto&&`和`T&&`的类型推导规则是:
对于
```cpp
template<typename T>
void f(T&& param);

f(expr);

// auto 和 模版 通过一个非常规范非常系统化的转换流程来转换彼此
```
1. 若匹配的expr是`左值(lvalue)`，`auto`或`T`被推导成`lvalue`的`引用类型`；例如匹配i时(int i=3;)，推导结果是`int&`；这非常不寻常，这是模板类型推导中**唯一一**种T被推导为引用的情况

2. 若匹配的expr是`右值rvalue`，`auto`或`T`被正常推导成`rvalue`的类型；例如匹配3时，推导结果是`int`；

### 类型推导带来的问题

在C++中，引用的引用是非法的，如果在编写代码是写出引用的引用，编译器会报错。
```cpp
int f = 3;

int & & r1 = f;   //error: cannot declare reference to ‘int&’, which is not a typedef or a template type argument
int & && r2 = f;  //error: cannot declare reference to ‘int&’, which is not a typedef or a template type argument
int && & r3 = f;  //error: cannot declare reference to ‘int&&’, which is not a typedef or a template type argument
int && && r4 = f; //error: cannot declare reference to ‘int&&’, which is not a typedef or a template type argument
```


但是在编译期，根据类型推导的规则，就会产生**引用的引用**
```cpp
template <typename T>
void func(T&& x) {} // 等价于 void func(int& && x);
int main() {
    int a = 0;
    func(a);
}
```

由于`T&&` 接受一个左值`a`, 因此`T`被类型推导为`int&`, 此时函数定义等价于 `void func(int& && x);`

由于*引用的引用*是非法的。那我们这些例子中都存在引用的引用怎么办呢？好在，这些引用的引用不是存在于源代码中(否则编译失败)，而是在编译过程中临时产生的。编译器会立即消除它们，手段就是**引用折叠(reference collapsing)**。

### 引用折叠

引用折叠规则: “如果任一引用为左值引用，则结果为左值引用。否则（即，如果引用都是右值引用），结果为右值引用。”

也就是说:
1. 右值引用的右值引用折叠成(collapses into)右值引用；
2. 其他情况折叠成(collapses into)左值引用；

这样上面的`void func(int& && x);` 被折叠成为 `void func(int& x);` 顺利通过编译。

**类型推导**和**引用折叠**在通用引用机制中起着重要作用：编译器允许T&&匹配左值和右值，然后经过类型推导和(或)引用折叠，T&&才最终变成左值引用或者右值引用。


## 回到std::move()

那么`std::move()`是怎样的工作的呢？

```cpp
template <typename T>
typename remove_reference<T>::type&& move(T&& t) noexcept {
    return static_cast<typename remove_reference<T>::type&&>(t);
}
```
`typename`的作用是在` remove_reference<T>::type`前使用了typename关键字来修饰，编译器才会将该名称当成是类型, 具体可参考[知无涯之C++ typename的起源与用法](https://feihu.me/blog/2014/the-origin-and-usage-of-typename/)。

`remove_reference `的作用是去掉类型的引用(左值引用和右值引用), 直接返回类型的基本内容。


```cpp
// 对于 move1(Foo())
// Foo()是右值, T的类型被推断为Foo, move函数被推断为
Foo&& move1(Foo&& item) noexcept {
    return static_cast<Foo&&>(item);
}

// 对于 f=Foo(); move1(f);
// f是左值, T的类型被推断为 Foo&
//Foo&& move1(Foo& && item) noexcept {
//    return static_cast<Foo&&>(item);
//}
// 又因为引用折叠, 被推断为
Foo&& move1(Foo& item) noexcept {
    return static_cast<Foo&&>(item);
}
```

因此`std::move()`只是一个语法糖, 并不会带来运行时开销。

![59cdb14e158f9b48b22b0f5daf3b5e4d.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1658281019107/7tT8shhLB.jpg align="left")


## Ref
1. https://www.yuanguohuo.com/2018/05/25/cpp11-universal-ref/
