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)。