首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么右值引用变量不是右值?

为什么右值引用变量不是右值?
EN

Stack Overflow用户
提问于 2015-09-17 08:20:59
回答 3查看 2.1K关注 0票数 19

假设我有一个函数f的两个重载

代码语言:javascript
运行
复制
void f(T&&); // #1
void f(T&);  // #2

然后在另一个函数g的主体中

代码语言:javascript
运行
复制
void g(T&& t) 
{ 
  f(t);  // calls #2
}

由于t被视为左值,因此将调用重载f(T&)

这对我来说非常令人惊讶。为什么签名为f(T&&)的函数不能匹配类型为T&&的调用?更让我惊讶的是,调用f(static_cast<T&&>(t))实际上会调用rvalue重载f(T&&)

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

EN

回答 3

Stack Overflow用户

发布于 2015-09-17 08:32:22

自动被视为右值的对象是没有名称的对象,以及(很快)不会有名称的对象(在返回值的情况下)。

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

rvalues之所以是这些东西,是因为在该使用点之后引用它们几乎是不可能的。

T&&是类型右值引用。右值引用只能绑定到右值(不涉及static_cast ),但在其他情况下它是右值引用类型的左值。

它是右值引用类型的事实只关系到它的构造过程,如果你使用decltype(variable_name)的话。否则它只是另一个引用类型的左值。

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

管理这一点的规则是用C++标准中的标准行话编写的。复制/粘贴它们并不是那么有用,因为它们并不那么容易理解。

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

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

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

第四,T&&并不总是右值引用。如果T的类型为X&X const&,则引用折叠会将T&&转换为X&X const&

最后,类型推导上下文中的T&&将根据参数的类型将T推断为XX&X const&X const&&,因此可以充当“转发引用”。

票数 21
EN

Stack Overflow用户

发布于 2015-09-17 09:01:50

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

我认为,虽然这种行为一开始似乎没有意义,但当你仔细考虑它时,它确实是有意义的,是正确的行为。首先,我们应该注意到,值类别显然是表达式的属性,而不是对象本身的属性。这是显而易见的,因为std::move从不创建新对象,而只是创建一个引用给定对象的右值表达式。那么我们应该理解这一点:

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

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

在此示例中:

代码语言:javascript
运行
复制
void f(Foo&& foo) { /* use foo */ }
void g() {
    Foo foo;
    f(std::move(foo));
}

尽管g中的std::move(foo)和被调用者中的参数foo引用了同一个对象,但该对象的值在g中的std::move处即将消失,而在f中,该对象的值预计将通过foo访问,可能会在f结束之前多次访问。

调用ref限定的成员函数时也存在类似的情况。

代码语言:javascript
运行
复制
struct Foo {
    void f() &;
    void f() &&;
    void g() && {
        f(); // calls lvalue-qualified f
    }
};
void h() {
    Foo().g();
}

在这里,Foo()的值将从h()中消失;在完整表达式之后将不可用。但是,在Foo::g()的主体中,它是永久的,直到g()的末尾;*this可靠地访问同一对象的值。因此,当g()调用f()时,它自然应该调用期望左值的重载,并且f()不应该从<代码>D35窃取*this*的值,因为<代码>D36可能仍然想要访问它。

票数 11
EN

Stack Overflow用户

发布于 2015-09-17 08:31:19

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

代码语言:javascript
运行
复制
template<typename T>
g(T&& t) { f(std::forward<T>(t));}

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

代码语言:javascript
运行
复制
g(T&& t) { f(std:move(t)); }
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32620750

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档