【问题标题】:lvalue to rvalue implicit conversion左值到右值的隐式转换
【发布时间】:2014-01-17 23:42:21
【问题描述】:

我看到在整个 C++ 标准的许多地方都使用了术语“左值到右值转换”。据我所知,这种转换通常是隐式完成的。

标准措辞的一个出乎意料的(对我而言)特征是他们决定将左值到右值视为一种转换。如果他们说glvalue总是可以接受而不是prvalue怎么办。这句话真的会有不同的含义吗?例如,我们读到 lvalues 和 xvalues 是 glvalues 的例子。我们没有读到左值和 xvalue 可以转换为 glvalues。意思有区别吗?

在我第一次遇到这个术语之前,我曾经在心理上或多或少地对左值和右值进行建模,如下所示:“左值总是能够充当右值,但另外还可以出现在左侧= 的一侧,&" 的右侧。

对我来说,这是一种直观的行为,如果我有一个变量名,那么我可以把这个名字放在任何我应该放置文字的地方。这个模型似乎与标准中使用的左值到右值隐式转换术语一致,只要保证这种隐式转换会发生。

但是,因为他们使用这个术语,我开始怀疑隐式左值到右值的转换是否在某些情况下会失败。也就是说,也许我的心智模型在这里是错误的。这是标准的相关部分:(感谢评论者)。

当一个泛右值出现在一个期望纯右值的上下文中时,这个泛左值就被转换成一个纯右值;见 4.1、4.2 和 4.3。 [注意:将右值引用绑定到左值的尝试不是这样的上下文;见 8.5.3 .—尾注]

我了解他们在注释中的描述如下:

int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit 
// lvalue to rvalue conversion did not happen.  
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good). 

所以,我的问题是(是):

1) 有人可以澄清这种转换可以隐式发生的上下文吗?具体来说,除了绑定到右值引用的上下文之外,是否还有其他无法隐式发生左值到右值转换的地方?

2) 此外,从句中的括号[Note:...] 使我们似乎可以从之前的句子中找出它。那是标准的哪一部分?

3) 这是否意味着右值引用绑定不是我们期望纯右值表达式(在右侧)的上下文?

4) 与其他转换一样,glvalue 到prvalue 的转换是否涉及运行时的工作,可以让我观察到它?

我在这里的目的不是问是否需要允许这样的转换。我正在尝试学习以标准为起点向自己解释这段代码的行为。

一个好的答案是通过我上面引用的引用并解释(基于解析文本)其中的注释是否也隐含在其文本中。然后它可能会添加任何其他引号,让我知道这种转换可能无法隐式发生的其他上下文,或者解释没有更多这样的上下文。也许一般讨论为什么将 glvalue 到 prvalue 视为转换。

【问题讨论】:

  • 请注意,int && y = x; 本身不是一个表达式,而是一个声明。因此,左值到右值的转换不会自动应用于“= 右侧的操作数”。
  • 这不是左值到右值的转换失败;那是右值引用绑定失败。
  • 另请注意,左值仍然可以“充当”(可以转换为)右值,即使对于右值引用绑定也是如此。您只需手动应用左值到右值的转换;正如我所说,它不会自动应用于引用绑定:int x = 42; int && y = +x;(一元 + 调用左值到右值转换)——但这不会将 y 绑定到 x,而是绑定到临时的,就像 int && y = 42; 一样。
  • @DyP:好吧,右边还有一个表达式......但我认为标准实际上是说,“当需要一个右值时;绑定到引用是不是 这种情况”。
  • @orm 需要一个 xvalue。当您使用 prvalue 初始化 rvalue 引用时,它实际上绑定到使用该 prvalue 初始化的临时 xvalue。换句话说,int && x = 1; ++x; 实际上并没有改变1 的值。但是int y = 42; int &&x = std::move(y); ++x; 会改变y 的值。

标签: c++ c++11 implicit-conversion lvalue-to-rvalue


【解决方案1】:

我认为左值到右值的转换不仅仅是在需要右值的地方使用左值。它可以创建一个类的副本,并且总是产生一个,而不是一个对象。

我将 n3485 用于“C++11”,将 n1256 用于“C99”。


对象和值

最简洁的描述在C99/3.14:

对象

执行环境中的数据存储区域,其内容可以表示 价值观

C++11/[intro.object]/1中也有一点

有些对象是多态的;实现生成与相关的信息 每个这样的对象都可以在程序执行期间确定该对象的类型。对于其他对象,其中找到的值的解释取决于用于访问它们的表达式的类型。

所以一个对象包含一个值(可以包含)。


价值类别

尽管它的名字,值类别分类表达式,而不是值。左值表达式甚至 不能 被视为值。

完整的分类/分类可以在 [basic.lval] 中找到; here's a StackOverflow discussion.

以下是关于对象的部分:

  • lvalue ([...]) 表示函数或对象。 [...]
  • xvalue(“eXpiring”值)也指对象 [...]
  • glvalue(“广义”左值)是左值或 xvalue。
  • rvalue ([...]) 是一个 xvalue、一个临时对象或其子对象,或者一个与对象无关的值。
  • prvalue(“纯”右值)是不是 xvalue 的右值。 [...]

请注意短语“与对象无关的值”。另请注意,由于 xvalue 表达式引用对象,所以真正的 必须始终作为 prvalue 表达式出现。


左值到右值的转换

如脚注 53 所示,现在应将其称为“glvalue-to-prvalue 转换”。首先,这是引用:

1    非函数、非数组类型T 的泛左值可以转换为纯右值。如果T 是不完整的类型,则需要进行此转换的程序格式错误。如果泛左值所引用的对象不是T 类型的对象,也不是从T 派生的类型的对象,或者如果该对象未初始化,则程序 必须进行此转换的行为未定义。如果T 是非类类型,则prvalue 的类型是T 的cv-unqualified 版本。否则,prvalue的类型为T

第一段规定了转换的要求和结果类型。它还不关心转换的效果(未定义的行为除外)。

2    当在未计算的操作数或其子表达式中发生左值到右值的转换时,不会访问包含在被引用对象中的值。否则,如果glvalue 具有类类型,则转换从glvalue 复制初始化T 类型的临时值,并且转换的结果是临时值的prvalue。否则,如果泛左值具有(可能是 cv 限定的)类型 std::nullptr_t,则 prvalue 结果是一个空指针常量。否则,glvalue指示的对象中包含的值就是prvalue结果。

我认为您会看到左值到右值的转换最常应用于非类类型。例如,

struct my_class { int m; };

my_class x{42};
my_class y{0};

x = y;

表达式x = y将左值到右值的转换应用到y(顺便说一下,这会创建一个临时的my_class)。原因是x = y 被解释为x.operator=(y),默认情况下按引用采用y,而不是按值(对于引用绑定,请参见下文;它不能绑定右值,因为这将是一个不同于y 的临时对象)。但是,my_class::operator= 的默认定义确实将左值到右值的转换应用于x.m

因此,对我来说最重要的部分似乎是

否则,glvalue指示的对象中包含的值就是prvalue结果。

通常,左值到右值的转换只会从对象中读取值。这不仅仅是值(表达式)类别之间的无操作转换;它甚至可以通过调用复制构造函数来创建一个临时的。并且左值到右值的转换总是返回纯右值,而不是(临时)对象

请注意,左值到右值的转换并不是将左值转换为纯右值的唯一转换:还有数组到指针的转换和函数到指针的转换。


值和表达式

大多数表达式不会产生对象[[需要引用]]。然而,一个id-expression 可以是一个标识符,它表示一个实体。对象是一个实体,所以有一些表达式可以产生对象:

int x;
x = 5;

assignment-expression x = 5 的左侧也需要是一个表达式。 x 这里是一个id-expression,因为x 是一个标识符。这个id-expression的结果是x所表示的对象。

表达式应用隐式转换:[expr]/9

当一个泛左值表达式作为一个操作数的操作数出现时,该操作数需要一个纯右值,左值到右值、数组到指针或函数到指针的标准转换将应用于将表达式转换为prvalue。

还有关于常用算术转换的 /10 以及关于用户定义转换的 /3。

我现在很想引用一个“期望该操作数的纯右值”的运算符,但除了强制转换之外找不到任何东西。例如 [expr.dynamic.cast]/2 "如果T 是指针类型,则v [操作数] 应该是指向完整类类型的指针的纯右值"。

许多算术运算符所需的常用算术转换确实通过使用的标准转换间接调用左值到右值的转换。除了从左值转换为右值的三个标准转换之外,所有标准转换都需要纯右值。

然而,简单的赋值不会调用通常的算术转换。在 [expr.ass]/2 中定义为:

在简单赋值 (=) 中,表达式的值替换左操作数引用的对象的值。

因此,尽管它没有明确要求右侧的纯右值表达式,但它确实需要一个。我不清楚这 strictly 是否需要左值到右值的转换。有一个论点是访问未初始化变量的值应始终调用未定义的行为(另请参阅CWG 616),无论是通过将其值分配给对象还是将其值添加到另一个值。但是这种未定义的行为只需要左值到右值的转换 (AFAIK),这应该是访问存储在对象中的值的唯一方法。

如果这个更概念化的观点是有效的,我们需要左值到右值的转换来访问对象内部的值,那么理解它在哪里(并且需要)应用就会容易得多。


初始化

与简单赋值一样,有一个discussion 是否需要左值到右值的转换来初始化另一个对象:

int x = 42; // initializer is a non-string literal -> prvalue
int y = x;  // initializer is an object / lvalue

对于基本类型,[dcl.init]/17 最后一个要点说:

否则,被初始化对象的初始值是初始化表达式的(可能转换的)值。如有必要,将使用标准转换将初始化表达式转换为目标类型的 cv 非限定版本;不考虑用户定义的转换。如果无法完成转换,则初始化格式错误。

不过,它也提到了初始化表达式的值。与 simple-assignment-expression 类似,我们可以将其视为对左值到右值转换的间接调用。


引用绑定

如果我们将左值到右值的转换视为访问对象值的一种方式(加上为类类型操作数创建临时对象),我们理解它通常应用于绑定到引用:引用是一个左值,它总是引用一个对象。因此,如果我们将值绑定到引用,我们需要创建临时对象来保存这些值。如果引用的初始化表达式是纯右值(它是一个值或临时对象),情况确实如此:

int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42;      // same

禁止将纯右值绑定到左值引用,但具有产生左值引用的转换函数的类类型的纯右值可以绑定到转换后类型的左值引用。

[dcl.init.ref] 中对引用绑定的完整描述相当长而且离题。我认为与这个问题有关的本质是引用指的是对象,因此没有 glvalue-to-prvalue(object-to-value)转换。

【讨论】:

  • 由于指针算术不受通常的算术转换的约束,因此也没有将左值到右值的转换明确应用于作为操作数的指针(即使使用了它们的值)。
  • 它可能需要一个 TL;DR
  • +1 这是非常详细的。我没有看到这里涉及到间接,据我所知,间接不会导致lvalue to revalue 转换,但这并不明显。
  • @ShafikYaghmour 间接使用一元 * 产生一个左值,[expr.unary.op]/1
  • 这也是我的想法,但我已经多次看到这种理由表明某些事情是未定义的行为,例如here,但我认为它不适用。我想人们的观点是任何读取的值都是lvalue-to-rvalue conversion,这似乎太宽泛了。
【解决方案2】:

关于glvalues:glvalue(“广义”左值)是一个表达式,它可以是左值也可以是xvalue。 通过左值到右值、数组到指针或函数到指针的隐式转换,可以将左值隐式转换为纯右值。

当在需要右值(例如数字)的上下文中使用左值参数(例如对对象的引用)时,将应用左值转换。

左值到右值的转换
任何非函数、非数组类型 T 的泛左值都可以隐式转换为 相同类型的纯右值。如果 T 是非类类型,则此转换还会删除 cv 限定符。除非在未计算的上下文中遇到(在 sizeof、typeid、noexcept 或 decltype 的操作数中),此转换使用原始 glvalue 作为构造函数参数有效地复制构造类型 T 的临时对象,并且该临时对象作为纯右值返回.如果 glvalue 的类型为 std::nullptr_t,则生成的纯右值是空指针常量 nullptr。

【讨论】:

    猜你喜欢
    • 2017-11-24
    • 1970-01-01
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2020-09-19
    • 1970-01-01
    • 2017-08-18
    • 2017-02-17
    相关资源
    最近更新 更多