让T
成为任意类型。考虑一个采用const
[左值]引用的函数:
void f(const T &obj);
假设此函数在内部调用另一个函数,该函数具有右值引用过载:
void g(T &&obj);
如果我们将rvalue传递给f
,是否会调用g
的右值引用过载,或者它是否会失败,因为它已被“转换”/绑定到const
左值引用?
类似地,如果f
调用一个函数,该函数按值获取T
的实例,
void h(T obj);
和T
有一个移动构造函数(即T(T &&);
),是否会调用移动构造函数,还是会调用复制构造函数?
总之,如果我们想确保在rvalue上调用f
时,rvalue引用会保持其rvalue“status”,我们是否必须为f
提供rvalue引用重载?
当您使用引用的名称作为表达式时,该表达式始终是左值。无论它是左值参考还是右值参考。如果使用左值或右值初始化引用也无关紧要。
int &&x = 1;
f(x); // Here `x` is lvalue.
因此,在void f(const T &obj) {...}
中,obj
总是一个左值,无论你作为一个论点传递什么。
另请注意,值类别是在编译时确定的。由于f
不是模板,因此其中每个表达式的值类别不能依赖于您传递的参数。
从而:
如果我们将rvalue传递给
f
,那么将调用g
的右值引用过载
没有。
如果
f
调用一个函数,它按值获取T
的实例,void h(T obj);
和T
有一个移动构造函数(即T(T &&);
),将调用移动构造函数
没有。
总之,如果我们想确保在rvalue上调用
f
时,rvalue引用会保持其rvalue“status”,我们是否必须为f
提供rvalue引用重载?
提供过载是一种选择。请注意,在这种情况下,您必须在rvalue重载中显式调用std::move
。
另一个选择是使用转发引用,正如Nicol Bolas建议的那样:
template <typename T> void f(T &&t)
{
g(std::forward<T>(t));
}
在这里,std::forward
基本上充当'有条件的move
'。如果rvalue传递给它,它会移动t
,否则什么都不做。
值类别应用于表达式,而不是对象。 obj
中的f
是一个左值表达式,因此将被视为这样。请注意,obj
中的g
也是一个左值表达式;如果表达式是对象的名称,那么它就是左值。
您正在谈论的能力正是转发引用存在的原因:因此可以通过函数调用保留参数表达式的值类别的复制/移动方面。 f
必须成为template<typename T> void f(T&& t);
形式的模板,你必须使用std::forward
传递给g
。