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

Ele*_*Ent 11 c++ rvalue rvalue-reference

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

这对我来说非常令人惊讶.带签名的函数如何f(T&&)与类型的调用不匹配T&&?让我更加惊讶的是,一个电话f(static_cast<T&&>(t))实际上会调用rvalue超载f(T&&).

使这成为可能的C++规则是什么?是T&&不是类型吗?

Yak*_*ont 14

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

T&& t有一个名字,它是t.

因为rvalues就是那些东西的原因是那个使用点之后提到它们几乎是不可能的.

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

它是rvalue参考类型的事实只在它的构造过程中很重要,如果你这样做的话decltype(variable_name).否则它只是引用类型的另一个左值.

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

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

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

其次,只有rvalue引用并且const&可以绑定到rvalues.

第三,当直接绑定到构造函数之外的引用时,会发生对临时值的引用生存期扩展.(仅作为rvalue引用并且const&可以直接绑定到临时,这仅适用于它们)

第四,T&&并不总是一个右值参考.如果T类型是X&X const&,然后引用塌陷匝T&&X&X const&.

最后,T&&在一个型扣上下文将推断TX,X&,X const&X const&&取决于参数的类型,并且因此可以作为"转发参考".


Bri*_*ian 7

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

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

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

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

在这个例子中:

void f(Foo&& foo) { /* use foo */ }
void g() {
    Foo foo;
    f(std::move(foo));
}
Run Code Online (Sandbox Code Playgroud)

虽然std::move(foo)in gfoo被调用者中的param 引用同一个对象,但该对象的值即将std::movein中消失g,而在该对象中,该对象的值f预计会被访问foo,可能在结束之前多次访问f.

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

struct Foo {
    void f() &;
    void f() &&;
    void g() && {
        f(); // calls lvalue-qualified f
    }
};
void h() {
    Foo().g();
}
Run Code Online (Sandbox Code Playgroud)

在这里,Foo()价值即将消失h(); 在完整表达后它将不可用.然而,在体内Foo::g(),它是永久的,直到结束g(); *this可靠地访问同一对象的值.所以很自然地,当g()调用时f(),它应该调用期望左值的重载,并且f()不应该窃取*this*from 的值,g()因为g()可能仍然想要访问它.


Nat*_*ica 5

Ing() t是一个命名变量。所有命名变量都是左值。如果T是模板类型,那么您可以将变量转发到f()using std::forward。这将f()使用与传递给的相同类型进行调用g()

template<typename T>
g(T&& t) { f(std::forward<T>(t));}
Run Code Online (Sandbox Code Playgroud)

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

g(T&& t) { f(std:move(t)); }
Run Code Online (Sandbox Code Playgroud)