1template<typename T>
2void f(T&& param); // param is a universal reference
3
4// lvalue passed to f; param's type is
5// Widget& (i.e., an lvalue reference)
6Widget w;
7f(w);
8
9// rvalue passed to f; param's type is
10// Widget&& (i.e., an rvalue reference)
11f(std::move(w));
这里涉及到 C++11 引入的右值引用和"完美转发"的概念,以及模板类型推导与引用折叠规则。
首先,当我们看到 T&& 的形式,并且 T 是一个模板参数,这通常被称为"通用引用"或"转发引用"。这种引用的类型推导会受到传递给它的实参的影响。
- 传递 lvalue:
当你有如下代码:
1Widget w;
2f(w); // lvalue passed to f
在这种情况下,因为 w 是一个 lvalue,模板类型推导会使 T 成为 Widget&,这样 param 的类型(T&&)就变成了 Widget& &&。根据 C++ 的引用折叠规则,Widget& && 会折叠成 Widget&,所以 param 的类型是 lvalue 引用。
- 传递 rvalue:
当你有如下代码:
1f(std::move(w)); // rvalue passed to f
std::move(w) 返回一个右值引用,所以这里传递给 f 的是一个 rvalue。在这种情况下,模板类型推导会使 T 成为 Widget,因此 param 的类型(T&&)就是 Widget&&,即 rvalue 引用。
总结:
- 对于 lvalue,
T被推导为 lvalue 引用类型,因此T&&成为 lvalue 引用。 - 对于 rvalue,
T被推导为普通类型(非引用),因此T&&保持为 rvalue 引用。
这种模式允许函数模板 f 能够接受任何类型的参数,无论是 lvalue 还是 rvalue,并且通过 std::forward(与 std::move 类似,但它根据参数的类型来决定是否应该移动)实现完美转发。这是 C++11 及其后续版本中用于实现像 std::make_shared、std::make_unique 和 std::thread 之类的库功能的关键技术。
引用折叠是 C++11 引入的一个规则,主要用于处理模板和右值引用时可能出现的复合引用类型。规则如下:
T& &折叠为T&T& &&折叠为T&T&& &折叠为T&T&& &&折叠为T&&
基本上,只要你有任何形式的 lvalue 引用 (&),结果总是一个 lvalue 引用。只有当两个引用都是 rvalue 引用 (&&) 时,结果才是 rvalue 引用。
为什么需要引用折叠规则呢?
主要是因为模板和类型推导,尤其是在使用通用引用(也称为转发引用)时。例如:
1template<typename T>
2void foo(T&& arg) {
3 // ...
4}
在这种情况下,T&& 是一个通用引用,它可以绑定到 lvalue 或 rvalue。当绑定到 lvalue 时,T 被推导为 Type&,因此 T&& 实际上变为 Type& &&,根据引用折叠规则,它折叠为 Type&。
这种行为允许我们在模板中实现所谓的"完美转发",这意味着我们可以精确地将传递给函数的参数转发(或传递)给另一个函数,同时保持其原始的值类别(lvalue 或 rvalue)。