C++11: 为什么将rvalue引用参数隐式转换为lvalue?

4
以下是我问题的简单代码,
void overloaded (int&& x) {
  cout << "[rvalue]";
}

template <class T>
void fn (T&& x) {
  overloaded(x);
}

int main() {
    fn(0);
    return 0;
}

我遇到编译错误了。

cannot bind ‘int’ lvalue to ‘int&&

  overloaded(x);

我在这里感到困惑,x作为右值引用传递给fn()。但是为什么fn()中的overload()调用会抱怨x是左值?

2个回答

8

你并不是“将x作为rvalue引用传递”,因为这个说法甚至没有意义。

以下是关于C++类型和表达式分类的一些事实:

  • 有对象类型和引用类型。(还有其他几种类型。)

  • 变量可以是对象或引用。

  • 表达式具有类型,这些类型几乎总是对象类型,而不是引用类型。(这就是为什么你说“将某物作为引用传递”的语句没有意义的原因;你传递的是参数,而参数始终是表达式。)表达式可以是lvalue、xvalue或prvalue。

  • Lvalue引用绑定到lvalue。Rvalue引用绑定到rvalue。(rvalue既可以是prvalue也可以是xvalue。)

  • 命名变量或参数的id-expression是lvalue。(它是一个“带名称的东西”,或者说“一个位置”.)

因此,在表达式overloaded(x)中,子表达式x是一个lvalue,并且它不会绑定到期望rvalue(由于其rvalue引用参数)的函数。

“lvalue引用”和“rvalue引用”中的“l”和“r”是指引用可以绑定到的值的类别,而不是这个引用类型的变量名表达式的类别。

您可以通过将其强制转换为xvalue将lvalue转换为rvalue;这方便地封装在类型推断转换助手std::move中。


4

首先,fn 函数的参数 x 不是一个右值引用,而是一个"通用引用"(是的,这有点令人困惑)。

其次,一旦您给对象命名,该名称就不再是右值,除非通过使用 std::move(始终将其转换为右值引用)或者 std::forward(在通用引用的情况下将其转换回其原始类型)进行明确“修复”。如果要避免出现问题,请使用 std::forward 作为原始类型进行转发:

template <class T>
void fn (T&& x) {
  overloaded(std::forward<T>(x));
}

1
“一旦你给一个变量命名,它就不再是 rvalue(右值)”这句话没有意义。rvalue 是一个表达式类别,而不是变量或对象。一个对象没有表达式类别;是指向该变量的表达式具有表达式类别,同一个对象可以有不同类别的表达式指向它。” - M.M
1
@M.M:我希望有更准确的措辞,而不需要完全理解C++规范才能理解它。关键是,一旦将r-value绑定到名称(即使是声明为&&的命名),它就不能在没有额外“努力”(std::move/std::forward)的情况下用作r-value。你是否更喜欢“一旦给对象命名,它就不再是r-value了”? - ShadowRanger
对象在任何时候都不是lvalue或rvalue。表示对象的表达式可以是lvalue或rvalue。将引用绑定到对象不会更改对象的任何属性(除了可能延长其生命周期)。名称“x”是一个lvalue;它不会变成一个,给定作为参数的对象也不会“停止成为rvalue”,因为它从来不是rvalue,它是(仍然是)一个对象。 - M.M
关于最近的编辑,x 总是一个左值,没有“除非”的情况。像 std::forward<T>(x) 这样的代码是与 x 不同的表达式。整个段落似乎在谈论实际对象是否具有左值或右值属性。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接