为什么rvalues引用的变量不是rvalue?

时间:2015-09-17 00:20:59

标签: c++ rvalue-reference rvalue

假设我有一个函数f(T&&)的两个重载。 f(T&)g。 然后在g(T&& t) { f(t);}的正文中: f(T&)将调用重载f(T&&),因为t被认为是左值。

这对我来说非常令人惊讶。签名T&&的功能如何与类型为f(static_cast<T&&>(t))的呼叫不匹配? 令我更加惊讶的是,呼叫f(T&&)实际上会调用右值超载T&&

使这成为可能的C ++规则是什么? {{1}}不仅仅是一种类型吗?

3 个答案:

答案 0 :(得分:12)

自动视为rvalues的东西是没有名字的东西,以及(很快)没有名字的东西(在返回值的情况下)。

T&& t有一个名称,它是t

rvalues就是那些东西的原因是在之后引用它们这个使用点几乎是不可能的。

T&&是rvalue类型引用。右值引用只能绑定到右值(不涉及static_cast),但它是rvalue引用类型的左值。

rvalue引用类型的事实仅在构造期间很重要,如果你做decltype(variable_name)。否则它只是引用类型的另一个左值。

std::move(t)执行return static_cast<T&&>(t);并返回右值引用。

管理这一规则的规则是在C ++标准中标准化的。复制/粘贴它们并不是很有用,因为它们不容易理解。

第一个一般规则是,当您从函数返回命名值时,或者当某个值没有名称时,或者函数显式返回时,您将获得一个隐式移动(也就是绑定到右值引用参数的参数)右值参考。

其次,只有右值引用和const&才能绑定到右值。

第三,当直接绑定到构造函数外部的引用时,会发生临时值的引用生存期扩展。 (因为只有右值引用和const&可以直接绑定到临时值,这只适用于它们)

Forth,T&&并不总是左值参考。如果T的类型为X&X const&,则引用折叠会将T&&变为X&X const&

最后,类型扣除上下文中的T&&会将T推断为XX&X const&X const&&,具体取决于类型参数,因此可以作为转发参考&#34;。

答案 1 :(得分:6)

当您按名称引用变量时,您总是得到一个左值。此规则没有例外,但请注意它不适用于预处理器宏,枚举器或非类型模板参数,这些参数都不是通常意义上的变量。

我认为虽然这种行为起初似乎没有意义,但当你仔细考虑它时,它确实有意义并且是正确的行为。首先,我们应该观察到值类别显然是表达式的属性,而不是对象本身的属性。这是显而易见的,因为std::move永远不会创建新对象,而只是创建一个引用给定对象的右值表达式。然后我们应该明白:

  • 如果表达式是左值,则通常意味着表达式引用的对象的值可以或稍后将在同一范围内通过相同的表达式访问。这是通过命名变量访问对象时的默认假设。
  • 如果表达式是rvalue,通常意味着表达式引用的对象的值不能或不会在稍后的同一范围内通过相同的表达式访问。 (这包括prvalue临时值;一个表达式中的T{}与后面表达式中的T{}不同;两者都创建匿名对象,但两者都是不同的,因此后者不会访问与前者相同的对象。 )

引用对象的表达式的值类别因此是 relative; 它取决于特定的表达式和范围。 std::move表示您不打算在同一范围内再次访问对象的值,允许被调用函数将其值移出该对象。但是,当被调用函数访问右值引用的名称时,该值在函数调用期间是永久; 该函数可以在任何时候将值移出该对象,或者根本不移动,但是无论如何,它可能在体内访问它,参数初始化之后。

在这个例子中:

void f(Foo&& foo) { /* use foo */ }
void g() {
    Foo foo;
    f(std::move(foo));
}

虽然std::move(foo)中的g和被调用者中的参数foo引用同一个对象,但该对象的值即将消失 std::move中的g,而f中的foo,该对象的值预计会通过f访问,可能会在struct Foo { void f() &; void f() &&; void g() && { f(); // calls lvalue-qualified f } }; void h() { Foo().g(); } 结束前多次访问。

调用ref-qualified成员函数时存在类似的情况。

Foo()

此处,h()的值即将从Foo::g()消失;它将在完整表达式之后无法使用。但是,在g()的正文中,它在*this结束之前是永久性的; g()可靠地访问同一对象的值。因此,当f()调用f()时,它应该调用期望左值的重载,*this*不应该从g()窃取g()的值,这是很自然的。因为{{1}}可能仍想访问它。

答案 2 :(得分:4)

g() t中是一个命名变量。所有命名变量都是左值。如果T是模板类型,则可以使用std::forward将变量转发至f()。这会将f()调用与传递给g()

的类型相同
template<typename T>
g(T&& t) { f(std::forward<T>(t));}

如果T不是模板类型而只是一种类型,那么您可以使用std::move

g(T&& t) { f(std:move(t)); }