左值到右值隐式转换 [英] lvalue to rvalue implicit conversion

查看:263
本文介绍了左值到右值隐式转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到在整个C ++标准的许多地方使用的术语左值到右值转换。这种转换通常是隐含的,就我所知。



标准的短语的一个意想不到的特征是,他们决定对待左值到右值作为转换。如果他们说,glvalue总是可以接受,而不是prvalue。那句话实际上有不同的含义吗?例如,我们读取左值和x值是glvalues的例子。我们不读取左值和x值可以转换为glvalues。



在我第一次遇到这个术语之前,我习惯了或多或少精神地模拟左值和右值,如下所示:左值是总是能够作为右值,但另外可以出现在 = 的左侧,并且 ;



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



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


每当glvalue出现在需要prvalue的上下文中,glvalue将转换为prvalue;见4.1,4.2和4.3。 [注意:试图将右值引用绑定到左值不是这样的上下文;请参阅8.5.3。-end note]


我明白他们在备注中描述的内容如下:

  int x = 1; 
int&&& y = x; //在这个声明上下文中,x不会绑定到y。
//但是文字1会有绑定,所以这是一个上下文,其中隐式的
// lvalue到rvalue的转换没有发生。
//右边的表达式是一个左值。如果它是一个贬值,它会有约束。
//因此,prvalue转换的左值没有发生(这是好的)。

所以,我的问题是:



1)有人能澄清这种转换可能隐含发生的上下文吗?具体来说,除了绑定到右值引用的上下文之外,有没有其他哪里的左值到右值转换不会隐式发生?



2)此外,子句中的括号 [Note:...] 已经从之前的句子中找出来了。标准的哪一部分是?



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



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



我的目的不是要问是否允许这样的转换。我试图学习向自己解释这个代码的行为使用标准作为起点。



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

解决方案

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



我使用n3485作为 +11和n1256C99。






对象和值


$ b b

最简洁的说明在C99 / 3.14中:


对象
$ b

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


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


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


包含值(可以包含)。






值类别



尽管它的名称,值类别对表达式进行分类,而不是值。



完整的分类/分类可以在[basic.lval]中找到。 这里是一个StackOverflow讨论



这里是关于对象的部分:



  • ([...]一个东西。 [...]

  • (eXpiring值)也指一个对象[...]
  • (generalizedlvalue)是一个左值或一个xvalue。

  • 一个右值 ])是xvalue,临时对象或其子对象或与对象不相关联的值。

  • prvalue(purervalue) xvalue。 [...]


请注意短语与对象不相关联的值 。另请注意,由于xvalue-expressions指向对象,因此必须始终作为prvalue-expression出现。






左值到右值转换



如脚注53所示,它现在应该被称为glvalue-to-prvalue转换。首先,这是引号:


1  可以将非函数非数组类型 T 的glvalue转换为prvalue。如果 T 是不完整类型,则需要进行此转换的程序是错误的。如果glvalue引用的对象不是 T 类型的对象,并且不是从 T ,或者如果对象未初始化,则需要此转换的程序
具有未定义的行为。如果 T 是非类类型,则prvalue的类型是 T 的cv非限定版本。否则,prvalue的类型为 T


要求和转换的结果类型。


2 < ; 当在未求值的操作数或其子表达式中发生左值到右值转换时,不访问包含在引用对象中的值。否则,如果glvalue具有类类型,则转换复制将从glvalue初始化类型 T 的临时变量,转换的结果是临时变量的prvalue。否则,如果glvalue具有(可能是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 = 的默认定义会将lvalue-to-rvalue转换应用到 xm 。 / p>

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


由glvalue指示的对象中包含的值是prvalue结果。


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



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






值和表达式



大多数表达式不产生对象 。但是, id-expression 可以是标识符,表示实体。对象是一个实体,所以有表达式产生对象:

  int x; 
x = 5;

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



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


每当glvalue表达式作为运算符的操作数出现期望该操作数为prvalue,则应用左值到右值,数组到指针或函数到指针的标准转换将表达式转换为prvalue。


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



我现在想引用一个操作符,期望该操作数的prvalue,但是找不到任何除了casts。例如,[expr.dynamic.cast] / 2如果 T 是指针类型, v ]



许多算术运算符所要求的通常的算术转换会调用一个左值 - 通过使用的标准转换间接转换到值。所有标准转换,但是从左值转换为右值的三个转换都需要prvalues。



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


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


t在右侧显式地需要prvalue表达式,它需要一个。如果这个严格需要左值到右值的转换,我不清楚。有一个参数,访问未初始化变量的值应该总是调用未定义的行为(另见 CWG 616 ),无论是通过将其值分配给对象还是将其值添加到另一个值。但是这个未定义的行为只需要一个左值到右值的转换(Avalik),这样就可以访问存储在对象中的值。



如果这个更概念的视图是有效的,我们需要左值到右值转换来访问对象中的值,那么它将更容易理解它应用在哪里(并且需要应用)。






初始化



与简单赋值一样,有一个讨论是否需要进行左值到右值转换以初始化另一个对象:

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

对于基本类型,[dcl.init] point说:


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


但是,它还提到了初始化的值表达式。类似于simple-assignment-expression,我们可以将其作为对左值到右值转换的间接调用。






< h2>引用绑定

如果我们将lvalue-to-rvalue转换看作访问对象的值(加上为类类型操作数创建一个临时) ,我们理解,不是一般应用于绑定到引用:引用是一个左值,它总是引用一个对象。因此,如果我们将值绑定到引用,我们需要创建包含这些值的临时对象。如果引用的initializer-expression是一个prvalue(这是一个值或一个临时对象),这就是这种情况:

  int const& lr = 42; //创建一个临时对象,绑定到`r'
int&& rv = 42; // same

禁止将prvalue绑定到lvalue引用,但禁止使用转换函数



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


I see the term "lvalue-to-rvalue conversion" used in many places throughout the C++ standard. This kind of conversion is often done implicitly, as far as I can tell.

One unexpected (to me) feature of the phrasing from the standard is that they decide to treat lvalue-to-rvalue as a conversion. What if they had said that a glvalue is always acceptable instead of a prvalue. Would that phrase actually have a different meaning? For example, we read that lvalues and xvalues are examples of glvalues. We don't read that lvalues and xvalues are convertible to glvalues. Is there a difference in meaning?

Before my first encounter with this terminology, I used to model lvalues and rvalues mentally more or less as follows: "lvalues are always able to act as rvalues, but in addition can appear on the left side of an =, and to the right of an &".

This, to me, is the intuitive behavior that if I have a variable name, then I can put that name everywhere where I would have put a literal). This model seems consistent with lvalue-to-rvalue implicit conversions terminology used in the standard, as long as this implicit conversion is guaranteed to happen.

But, because they use this terminology, I started wondering whether the implicit lvalue-to-rvalue conversion may fail to happen in some cases. That is, maybe my mental model is wrong here. Here is a relevant part of the standard: (thanks to the commenters).

Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue; see 4.1, 4.2, and 4.3. [Note: An attempt to bind an rvalue reference to an lvalue is not such a context; see 8.5.3 .—end note]

I understand what they describe in the note is the following:

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). 

So, my question is (are):

1) Could someone clarify the contexts where this conversion can happen implicitly? Specifically, other than the context of binding to an rvalue reference, are there any other where lvalue-to-rvalue conversions fail to happen implicitly?

2) Also, the parenthetical [Note:...] in the clause makes it seem that we could have figured it out from the sentence before. Which part of the standard would that be?

3) Does that mean that rvalue-reference binding is not a context where we expect a prvalue expression (on the right)?

4) Like other conversions, does the glvalue-to-prvalue conversion involve work at runtime that would allow me to observe it?

My aim here is not to ask if it is desirable to allow such a conversion. I'm trying to learn to explain to myself the behavior of this code using the standard as starting point.

A good answer would go through the quote I placed above and explain (based on parsing the text) whether the note in it is also implicit from its text. It would then maybe add any other quotes that let me know the other contexts in which this conversion may fail to happen implicitly, or explain there are no more such contexts. Perhaps a general discussion of why glvalue to prvalue is considered a conversion.

解决方案

I think the lvalue-to-rvalue conversion is more than just use an lvalue where an rvalue is required. It can create a copy of a class, and always yields a value, not an object.

I'm using n3485 for "C++11" and n1256 for "C99".


Objects and values

The most concise description is in C99/3.14:

object

region of data storage in the execution environment, the contents of which can represent values

There's also a bit in C++11/[intro.object]/1

Some objects are polymorphic; the implementation generates information associated with each such object that makes it possible to determine that object’s type during program execution. For other objects, the interpretation of the values found therein is determined by the type of the expressions used to access them.

So an object contains a value (can contain).


Value categories

Despite its name, value categories classify expressions, not values. lvalue-expressions even cannot be considered values.

The full taxonomy / categorization can be found in [basic.lval]; here's a StackOverflow discussion.

Here are the parts about objects:

  • An lvalue ([...]) designates a function or an object. [...]
  • An xvalue (an "eXpiring" value) also refers to an object [...]
  • A glvalue ("generalized" lvalue) is an lvalue or an xvalue.
  • An rvalue ([...]) is an xvalue, a temporary object or subobject thereof, or a value that is not associated with an object.
  • A prvalue ("pure" rvalue) is an rvalue that is not an xvalue. [...]

Note the phrase "a value that is not associated with an object". Also note that as xvalue-expressions refer to objects, true values must always occur as prvalue-expressions.


The lvalue-to-rvalue conversion

As footnote 53 indicates, it should now be called "glvalue-to-prvalue conversion". First, here's the quote:

1    A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.

This first paragraph specifies the requirements and the resulting type of the conversion. It isn't yet concerned with the effects of the conversion (other than Undefined Behaviour).

2    When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof the value contained in the referenced object is not accessed. Otherwise, if the glvalue has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary. Otherwise, if the glvalue has (possibly cv-qualified) type std::nullptr_t, the prvalue result is a null pointer constant. Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

I'd argue that you'll see the lvalue-to-rvalue conversion most often applied to non-class types. For example,

struct my_class { int m; };

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

x = y;

The expression x = y does not apply the lvalue-to-rvalue conversion to y (that would create a temporary my_class, by the way). The reason is that x = y is interpreted as x.operator=(y), which takes y per default by reference, not by value (for reference binding, see below; it cannot bind an rvalue, as that would be a temporary object different from y). However, the default definition of my_class::operator= does apply the lvalue-to-rvalue conversion to x.m.

Therefore, the most important part to me seems to be

Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

So typically, an lvalue-to-rvalue conversion will just read the value from an object. It isn't just a no-op conversion between value (expression) categories; it can even create a temporary by calling a copy constructor. And the lvalue-to-rvalue conversion always returns a prvalue value, not a (temporary) object.

Note that the lvalue-to-rvalue conversion is not the only conversion that converts an lvalue to a prvalue: There's also the array-to-pointer conversion and the function-to-pointer conversion.


values and expressions

Most expressions don't yield objects[[citation needed]]. However, an id-expression can be an identifier, which denotes an entity. An object is an entity, so there are expressions which yield objects:

int x;
x = 5;

The left hand side of the assignment-expression x = 5 also needs to be an expression. x here is an id-expression, because x is an identifier. The result of this id-expression is the object denoted by x.

Expressions apply implicit conversions: [expr]/9

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue, array-to-pointer, or function-to-pointer standard conversions are applied to convert the expression to a prvalue.

And /10 about usual arithmetic conversions as well as /3 about user-defined conversions.

I'd love now to quote an operator that "expects a prvalue for that operand", but cannot find any but casts. For example, [expr.dynamic.cast]/2 "If T is a pointer type, v [the operand] shall be a prvalue of a pointer to complete class type".

The usual arithmetic conversions required by many arithmetic operators do invoke an lvalue-to-rvalue conversion indirectly via the standard conversion used. All standard conversions but the three that convert from lvalues to rvalues expect prvalues.

The simple assignment however doesn't invoke the usual arithmetic conversions. It is defined in [expr.ass]/2 as:

In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.

So although it doesn't explicitly require a prvalue expression on the right hand side, it does require a value. It is not clear to me if this strictly requires the lvalue-to-rvalue conversion. There's an argument that accessing the value of an uninitialized variable should always invoke undefined behaviour (also see CWG 616), no matter if it's by assigning its value to an object or by adding its value to another value. But this undefined behaviour is only required for an lvalue-to-rvalue conversion (AFAIK), which then should be the only way to access the value stored in an object.

If this more conceptual view is valid, that we need the lvalue-to-rvalue conversion to access the value inside an object, then it'd be much easier to understand where it is (and needs to be) applied.


Initialization

As with simple assignment, there's a discussion whether or not the lvalue-to-rvalue conversion is required to initialize another object:

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

For fundamental types, [dcl.init]/17 last bullet point says:

Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed.

However, it also mentioned the value of the initializer expression. Similar to the simple-assignment-expression, we can take this as an indirect invocation of the lvalue-to-rvalue conversion.


Reference binding

If we see lvalue-to-rvalue conversion as a way to access the value of an object (plus the creation of a temporary for class type operands), we understand that it's not applied generally for binding to a reference: A reference is an lvalue, it always refers to an object. So if we bound values to references, we'd need to create temporary objects holding those values. And this is indeed the case if the initializer-expression of a reference is a prvalue (which is a value or a temporary object):

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

Binding a prvalue to an lvalue reference is prohibited, but prvalues of class types with conversion functions that yield lvalue references may be bound to lvalue references of the converted type.

The complete description of reference binding in [dcl.init.ref] is rather long and rather off-topic. I think the essence of it relating to this question is that references refer to objects, therefore no glvalue-to-prvalue (object-to-value) conversion.

这篇关于左值到右值隐式转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆