左值到右值的隐式转换
我在整个C ++标准的许多地方看到术语“左值到右值的转换”。 就我所知,这种转换通常是隐含的。
标准中措辞的一个意想不到的(对我来说)特征是他们决定将左值转换为右值作为转换。 如果他们说过一个总是可以接受的而不是一个价值的话,那该怎么办? 那个短语实际上会有不同的含义吗? 例如,我们读到左值和x值是glvalues的例子。 我们没有看到左值和右值可以转换为glvalues。 意义有什么不同?
在我第一次遇到这个术语之前,我习惯性地在精神上模拟左值和右值,如下所示:“左值总是能够作为右值,但是另外可以出现在右边的=
和左边一个&
“。
对我而言,这是一种直观的行为,如果我有一个变量名,那么我可以把这个名字放在任何我想放置文字的地方。 只要保证这种隐式转换发生,该模型似乎与标准中使用的左值到右值隐式转换术语一致。
但是,因为他们使用这个术语,我开始怀疑在某些情况下隐式左值到右值的转换是否会失败。 也就是说,也许我的心智模式在这里是错误的。 这是标准的相关部分:(感谢评论者)。
每当一个glvalue出现在预期prvalue的上下文中时,glvalue就被转换为一个prvalue; 见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-to-prvalue转换是否涉及在运行时工作,这将允许我观察它?
我的目标不是要问是否有必要允许这种转换。 我试图学会用自己的标准作为出发点来解释这段代码的行为。
一个好的答案将通过我上面提到的引用并解释(基于解析文本)其中的注释是否也隐含在其文本中。 然后,它可能会添加任何其他引用,让我知道其中的转换可能无法隐含发生的其他上下文,或者解释没有更多此类上下文。 也许关于为什么值得尊敬的一般讨论被认为是一种转变。
我认为左值到右值的转换不仅仅是在需要右值的情况下使用左值。 它可以创建一个类的副本,并且总是产生一个值,而不是一个对象。
我使用n3485作为“C ++ 11”,n1256作为“C99”。
对象和值
最简洁的描述在C99 / 3.14中:
目的
执行环境中的数据存储区域,其内容可以表示值
在C ++ 11 / [intro.object] / 1中也有一些
一些对象是多态的; 该实现生成与每个这样的对象相关联的信息,这使得可以在程序执行期间确定该对象的类型。 对于其他对象,其中找到的值的解释取决于用于访问它们的表达式的类型。
所以一个对象包含一个值(可以包含)。
价值类别
尽管它的名字,价值类别分类的表达,而不是价值观。 左值表达式甚至不能被视为值。
完整的分类/分类可以在[basic.lval]中找到; 这里有一个StackOverflow讨论。
以下是关于对象的部分:
请注意短语“与对象无关的值”。 另请注意,由于xvalue表达式引用对象,因此必须始终以prvalue表达式的形式出现true值。
左值到右值的转换
如脚注53所示,它现在应该被称为“从价值到价值的转换”。 首先,这是引用:
1非函数非数组类型T
的glvalue可以转换为prvalue。 如果T
是不完整的类型,则需要进行此转换的程序不合格。 如果glvalue引用的对象不是T
类型的对象,也不是T
派生类型的对象,或者对象未初始化,则需要此转换的程序具有未定义的行为。 如果T
是非类型的,则prvalue的类型是T
的cv非标准版本。 否则,prvalue的类型是T
该第一段指定了转换的要求和结果类型。 它尚未涉及转换的影响(除未定义行为外)。
2当未评估的操作数或其子表达式发生左值到右值转换时,被访问对象中包含的值不被访问。 否则,如果glvalue具有类类型,则转换copy-从glvalue初始化类型T
的临时值,并且转换的结果是临时值。 否则,如果glvalue具有(可能是cv-qualified)类型的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=
的默认定义将左值到右值转换应用于xm
。
因此,对我来说最重要的部分似乎是
否则,由glvalue指示的对象中包含的值是prvalue结果。
所以通常,左值到右值的转换只会读取一个对象的值。 它不仅仅是值(表达式)类别之间的无操作转换; 它甚至可以通过调用一个拷贝构造函数来创建一个临时的。 左值到右值的转换总是返回一个prvalue值,而不是(临时)对象。
请注意,左值到右值的转换不是将左值转换为prvalue的唯一转换:还有数组指针转换和函数指针转换。
值和表达式
大多数表达式不会产生对象[[需要的引证]]。 但是,一个id表达式可以是一个标识符,它表示一个实体。 一个对象是一个实体,所以有表达式产生对象:
int x;
x = 5;
赋值表达式x = 5
的左侧也需要是一个表达式。 这里x
是一个id表达式,因为x
是一个标识符。 这个id表达式的结果是由x
表示的对象。
表达式应用隐式转换:[expr] / 9
只要glvalue表达式作为操作数的操作数出现,该操作数需要该操作数的一个prvalue,则应用左值到右值,数组到指针或函数到指针标准转换将表达式转换为prvalue。
和/ 10关于通常的算术转换以及/ 3关于用户定义的转换。
我现在想引用一个“期望该操作数有价值”的操作符,但找不到任何操作符。 例如,[expr.dynamic.cast] / 2“如果T
是指针类型, v
[操作数]应该是指向完成类类型的指针的前值”。
许多算术运算符通常需要的算术转换通过使用的标准转换间接地调用左值到右值转换。 所有标准转换,但是从左值转换为右值的三个预期值都是预估值。
然而,简单的赋值不会调用通常的算术转换。 它在[expr.ass] / 2中定义为:
在简单赋值( =
)中,表达式的值将替换左操作数引用的对象的值。
因此,尽管它没有明确要求在右侧显示一个prvalue表达式,但它确实需要一个值。 我不清楚这是否严格要求左值到右值的转换。 有一种观点认为,访问未初始化变量的值应始终调用未定义的行为(请参阅CWG 616),无论它是通过将其值分配给对象还是将其值添加到其他值。 但是这种未定义的行为仅仅是左值到右值转换(AFAIK)所必需的,后者应该是唯一访问存储在对象中的值的方法。
如果这个更概念化的视图是有效的,我们需要左值到右值转换来访问对象内部的值,那么它将更容易理解它在哪里(和需要被)应用。
初始化
与简单的赋值一样,讨论是否需要左值到右值转换来初始化另一个对象:
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,我们可以把它作为左值到右值转换的间接调用。
参考绑定
如果我们将左值到右值的转换看作是访问对象值的方式(加上为类类型操作数创建临时值),我们知道它通常不适用于绑定到引用:引用是左值,它总是指一个对象。 因此,如果我们将值绑定到引用,则需要创建包含这些值的临时对象。 如果引用的初始化表达式是一个prvalue(它是一个值或一个临时对象),情况确实如此:
int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42; // same
禁止将一个prvalue绑定到一个左值引用,但带有产生左值引用的转换函数的类类型的前值可能会绑定到转换类型的左值引用。
[dcl.init.ref]中引用绑定的完整描述相当长,而且相当偏离主题。 我认为它涉及到这个问题的本质是引用是指对象,因此没有glvalue-to-prevalue(object-to-value)转换。
在glvalue上:glvalue(“generalized”左值)是一个表达式,可以是左值或者是xvalue。 一个glvalue可以隐式转换为prvalue,左值到右值,数组到指针或函数到指针的隐式转换。
左值变换适用于左值参数(例如对象引用)用于需要右值(例如数字)的上下文中。
左值到右值的转换
任何非函数,非数组类型T的glvalue都可以隐式转换为相同类型的 prvalue。 如果T是非类类型,则此转换还会删除cv限定符。 除非在未经评估的上下文中(在sizeof,typeid,noexcept或decltype的操作数中)遇到,否则此转换将使用原始glvalue作为构造函数参数有效地复制构造类型T的临时对象,并且该临时对象作为prvalue返回。 如果glvalue的类型为std :: nullptr_t,则结果的prvalue是空指针常量nullptr。