我认为左值到右值的转换不仅仅是在需要右值的地方使用左值。它可以创建一个类的副本,并且总是产生一个值,而不是一个对象。
我将 n3485 用于“C++11”,将 n1256 用于“C99”。
对象和值
最简洁的描述在 C99/3.14 中:
目的
执行环境中的数据存储区域,其内容可以表示值
C++11/[intro.object]/1 中也有一点
有些对象是多态的;该实现生成与每个此类对象相关联的信息,从而可以在程序执行期间确定该对象的类型。对于其他对象,其中找到的值的解释取决于用于访问它们的表达式的类型。
所以一个对象包含一个值(可以包含)。
价值类别
尽管它的名字,值类别分类表达式,而不是值。左值表达式甚至不能被视为值。
完整的分类/分类可以在 [basic.lval] 中找到;这是一个 StackOverflow 讨论。
以下是关于对象的部分:
- 左值([...])指定函数或对象。[...]
- 一个xvalue(一个“eXpiring”值)也指一个对象 [...]
- 左值(“广义”左值)是左值或 xvalue。
- 右值([...]) 是一个xvalue、一个临时对象或它的子对象,或者一个与对象无关的值。
- prvalue(“纯”右值)是不是 xvalue 的右值。[...]
请注意短语“与对象无关的值”。另请注意,由于 xvalue 表达式引用对象,真值必须始终作为纯右值表达式出现。
左值到右值的转换
如脚注 53 所示,现在应将其称为“glvalue 到prvalue 转换”。首先,这是报价:
1 非函数、非数组类型的左值T
可以转换为纯右值。如果T
是不完整类型,则需要进行此转换的程序格式错误。如果泛左值所引用的对象不是类型对象,T
也不是派生自 的类型的对象T
,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为。如果T
是非类类型,则纯右值的类型是 的 cv 非限定版本T
。否则,纯右值的类型是T
。
第一段规定了转换的要求和结果类型。它还不关心转换的效果(除了未定义的行为)。
2 当在未计算的操作数或其子表达式中发生左值到右值的转换时,不访问包含在引用对象中的值。否则,如果glvalue 具有类类型,则转换复制初始化T
glvalue 的临时类型,并且转换的结果是临时的prvalue。否则,如果泛左值具有(可能是 cv 限定的)类型std::nullptr_t
,则纯右值结果是一个空指针常量。否则,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结果。
因此,通常情况下,左值到右值的转换只会从 object 中读取值。这不仅仅是值(表达式)类别之间的无操作转换;它甚至可以通过调用复制构造函数来创建一个临时的。并且左值到右值的转换总是返回一个纯右值,而不是一个(临时)对象。
请注意,左值到右值的转换并不是将左值转换为纯右值的唯一转换:还有数组到指针的转换和函数到指针的转换。
值和表达式
大多数表达式不会产生对象[[citation needed]]。然而,一个id-expression可以是一个标识符,它表示一个实体。对象是一个实体,所以有一些表达式可以产生对象:
int x;
x = 5;
赋值表达式 的左侧x = 5
也需要是一个表达式。x
这是一个id-expression,因为x
是一个标识符。此id 表达式的结果是由 表示的对象x
。
表达式应用隐式转换:[expr]/9
每当一个泛左值表达式作为一个操作数的操作数出现时,该操作数需要一个纯右值,左值到右值、数组到指针或函数到指针的标准转换将应用于将表达式转换为纯右值。
和 /10 关于通常的算术转换以及 /3 关于用户定义的转换。
我现在很想引用一个“期望该操作数的纯右值”的运算符,但除了强制转换之外找不到任何东西。例如,[expr.dynamic.cast]/2 “如果T
是指针类型,v
[操作数] 应该是指向完整类类型的指针的纯右值”。
许多算术运算符所需的通常算术转换确实通过使用的标准转换间接调用左值到右值的转换。除了从左值转换为右值的三个标准转换之外,所有标准转换都需要纯右值。
然而,简单的赋值不会调用通常的算术转换。它在 [expr.ass]/2 中定义为:
在简单赋值 ( =
) 中,表达式的值替换左操作数引用的对象的值。
因此,尽管它没有明确要求右侧的纯右值表达式,但它确实需要一个value。我不清楚这是否严格要求左值到右值的转换。有一个论点是访问未初始化变量的值应始终调用未定义的行为(另见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 类似,我们可以将其视为对左值到右值转换的间接调用。
参考绑定
如果我们将左值到右值的转换视为访问对象值的一种方式(加上为类类型操作数创建临时对象),我们就会理解它通常不适用于绑定到引用:引用是左值,它总是指一个对象。因此,如果我们将值绑定到引用,我们需要创建临时对象来保存这些值。如果引用的初始化表达式是纯右值(它是一个值或临时对象),情况确实如此:
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)转换。