右值到左值的转换和"named-refs-are-lvalues"规则

Rvalue to lvalue conversion and "named-refs-are-lvalues" rule

本文关键字:named-refs-are-lvalues 规则 转换      更新时间:2023-10-16

有很多与右值相关的问题,但我没有找到这些确切问题的答案。

我无法理解"命名引用是左值引用"的经验法则。

这看起来真的很奇怪 - 我们将引用声明为值,但由于我们必须以某种方式使用此引用(否则,有什么意义?),我们命名它,因为它被命名,毕竟它是一个左值

请考虑以下代码:

int&& foo(int&& arg)
{
arg = 10;
return arg; // ERROR, `arg` is an lvalue since it's named!
}
foo(1);

问题是:

  1. arg 究竟什么时候成为左值?
  2. 如果函数无效并且 b) 和 c) 行不存在,arg 的类型是什么?
  3. 很多文章(只是引用第一个发现的结果)说可能存在隐含的右值转换,但相反的方向是不可能的 - 为什么?此示例显示arg-s 从int&&转换为int&,然后尝试将int&隐式转换为int&&这会导致编译错误 - 恰恰相反的行为!这就是为什么我们需要std::move它基本上是 rvalue 类型的显式static_cast。

>arg变量具有类型int&&而没有值类别。

arg表达式(它是第 3 行和第 4 行的表达式)具有类型int和值类别"左值">

左值到右值的转换更改表达式的值类别,而不更改其类型。如果你在函数内部编写arg+1,则类型int的 lvalue 表达式arg将进行此转换以生成类型int的 prvalue 表达式,因为这是内置+所需要的。

int&和int&&之间没有"左值到右值"或反向转换,因为表达式从来没有引用类型。程序中的错误是无法右值引用(int&&&)绑定到左值表达式(类型为int)。

我们应该将"如果它有一个名字,那就是一个左值">规则细化为更确切的形式:

如果它有一个名称,那么当用作表达式时,它是一个左值。

但是我们不是总是使用变量作为表达式吗?

通常是的,但不一定。请参阅以下函数:

void foo(A&& a) {
// decltype of the variable a - is rvalue
static_assert(std::is_rvalue_reference_v<decltype(a)>);
// decltype of the expression a - is lvalue
static_assert(std::is_lvalue_reference_v<decltype((a))>);
}

请注意,a不只是成为左值。如果它被用作表达式 - 这是变量的常见用法 - 则表达式是左值引用。

在上面的代码中,adecltypeA&&因此仍然是一个右值引用。

另一方面,(a)a的使用转换为该上下文中的表达式,因此(a)decltypeA&- 一个左值引用。

你可以把它想象成:使用变量a的表达式可以看作是对A&&的左值引用(即A&& &),随着引用折叠变为A&


变量a仍然是右值,只有表达式a是左值,这一事实并不常用。通常我们只是将a作为一种表达来处理。但是,当我们想要转发auto&&参数时,正在使用它,例如:

[](auto&& v){ foo(std::forward<decltype(v)>(v)); } (A{});

来自C++参考:表达式,可以分为以下三个选项之一:

  • 如果它具有标识并且无法从中移动,则称为左值表达式
  • 如果它具有标识并且可以从中移动,则称为xValue 表达式
  • 如果它没有标识并且可以从中移动,则称为prvalue("纯 rvalue")表达式

可以从中移动的表达式称为"右值表达式"。prvalue 和 xvalues 都是右值表达式。自 C++17 起,由于强制复制省略,prvalues 不再移动。

具有恒等式的表达式称为"glvalue 表达式"(glvalue 代表"广义左值")。左值和 x值都是 glvalue 表达式。

具有标识被认为可以确定表达式是否引用与另一个表达式相同的实体,例如通过比较对象的地址或它们标识的功能。


在下面的代码中,我们比较了左值prvaluexvalue表达式:

void foo(A&& a) {
// below we send the expression a to bar, as lvalue
bar(a);
// std::move(a) is an xvalue expression
bar(std::move(a));
// A{} is a prvalue expression
bar(A{});
}

有一个表达式列表可以创建一个 xvalue(请参阅下面的链接,了解"哪些表达式创建xvalues?"),但最常见的两个表达式是std::movestd::forward

创建 prvalue 很容易,您只需创建一个没有名称的对象,或者在需要 prvalue 的表达式中使用对象(从左值到prvalue的转换)。


玩的代码:https://godbolt.org/z/so746KcqG

<小时 />

另请参阅:

  • 哪些表达式创建 x值?
  • 什么是右值、左值、
  • x值、gl值和pr值?
  • 关于如何识别右值或左值引用以及它是否有名称规则
  • 右值引用被视为左值?
  • 为什么命名右值引用是右值表达式?

问题 3 询问为什么不允许从左值(无论是否引用)隐式转换为右值引用。这可能基于右值引用只能绑定到右值的约束。例如,此约束对于重载函数以实现复制和移动构造函数至关重要。

关于表达式是否可以具有引用类型,Scott Meyers 的这篇博客文章非常有用。最新标准草案中的引述:

如果表达式最初具有"对 T 的引用"([dcl.ref], [dcl.init.ref]),在进一步操作之前将类型调整为 T。 分析。表达式指定对象或函数,表示为 引用和表达式是左值或 x值,具体取决于 在表达式上。

因此,为了非常准确,表达式可以具有引用类型,但出于所有实际目的,将删除此引用性。

相关文章: