鉴于:
int main() {
int x = 0;
int y = x; // <---
}
有人可以告诉我标准的哪个条款(首选 2003 年)要求在对象初始化时将表达式x
从左值转换为右值y
?
(或者,如果我弄错了并且没有发生这种转换,那么我也想学习!)
鉴于:
int main() {
int x = 0;
int y = x; // <---
}
有人可以告诉我标准的哪个条款(首选 2003 年)要求在对象初始化时将表达式x
从左值转换为右值y
?
(或者,如果我弄错了并且没有发生这种转换,那么我也想学习!)
我发现将 lvalue-s 视为真实对象并将 rvalue-s 视为存储在对象中的值更容易(如果可能不是 100% 精确)。该表达式x
是一个左值表达式,它引用x
第一行中定义的对象,但是当用作对非用户定义类型的赋值的右侧时,会读取实际值,这就是转换的地方执行从左值到右值:读取对象的内容。
至于标准中规定转换的具体条款......好吧,我能想到的最接近的是 4.1 [conv.lvalue]/2 (左值到右值转换):
左值指示的对象中包含的值是右值结果。
赋值右侧是右值的要求在 5.17 [expr.ass] 中是隐含的或缺失的,但情况确实如此,否则下面的表达式将是一个错误,因为 rhs 是一个右值并且没有右值到左值的转换:
int x = 5;
编辑:对于初始化,8.5 [dcl.init]/14,最后一个项目符号(指基本类型)状态(强调我的):
- 否则,被初始化对象的初始值是初始化表达式的(可能转换的)值。[...]
那里的值意味着您示例中的左值表达式已被读取(即转换为右值)。无论如何,前面提到赋值的段落可以在这里应用:如果初始化需要一个左值而不是右值,那么表达式int i = 0;
将是错误的。
我确实相信这在某种程度上是直观的(其他人已经说过 -需要值,因此显然需要将对象指示符转换为其中包含的值)。我能想到的最好的,4p3:
当且仅当声明“T t=e;”时,表达式 e 可以隐式转换为类型 T 对于一些发明的临时变量 t (8.5) 是良构的。隐式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同。如果 T 是引用类型 (8.3.2),则结果为左值,否则为右值。当且仅当初始化将表达式 e 用作左值时,表达式 e 才用作左值。
请注意末尾的“当且仅当” - 初始化器用作右值,因为初始化将其用作右值(转换的结果)。所以到 3.10p7
每当左值出现在需要右值的上下文中时,左值就会转换为右值;见 4.1、4.2 和 4.3。
编辑:输入 4p3 的段落可以在 8.5p16 找到,最后一个项目符号:
否则,被初始化对象的初始值是初始化表达式的(可能转换的)值。
还要注意下面的评论。
这是你要找的:
§3.10/7
每当左值出现在需要右值的上下文中时,左值就会转换为右值;见 4.1、4.2 和 4.3。
而且我认为当您编写时int y = x
,它基本上复制了对象中包含的值x
,它是一个左值,但值本身是一个右值,因此上下文需要一个右值。
§4.1/2 说,
左值指示的对象中包含的值是右值结果。
也许这两句话可以澄清你的疑问。如果我的理解有误,请纠正我。我想学习新事物。
@Tomalak 的评论:
我的问题是 int& y = x; 是有效的,所以在这种情况下,当然 x 可能不是右值。我不知道我的例子中的差异有多么无关紧要,虽然
那么int &y = x
不会复制该值。它只是创建对象本身的别名。但正如我之前所说int y = x
,基本上复制的是右值。因此,上下文需要一个右值,因为这里正在进行复制。
初始化器具有以下语法:
initializer:
= initializer-clause
( expression-list )
initializer-clause:
assignment-expression
{ initializer-list ,opt }
{ }
在您的示例中,x
是assignment-expression
遵循以下语法产生链的一个:
conditional-expression ==>
logical-or-expression ==>
logical-and-expression ==>
inclusive-or-expression ==>
exclusive-or-expression ==>
and-expression ==>
equality-expression ==>
relational-expression ==>
shift-expression ==>
additive-expression ==>
multiplicative-expression ==>
pm-expression ==>
cast-expression ==>
unary-expression ==>
postfix-expression ==>
primary-expression ==>
id-expression ==>
unqualified-id ==>
identifier
并且标识符“如果实体是函数或变量,则为左值”(5.1/4“主要表达式”)。
所以在你的例子中,右边的=
表达式是一个恰好是一个的表达式lvalue
。当然可以rvalue
,但不一定非得如此。并且没有强制的左值到右值的转换。
不过,我不确定知道这一点有什么价值。
3.10 左值和右值
1 每个表达式都是左值或右值。
2 左值指的是对象或函数。一些右值表达式——类或 cvqualified 类类型——也指对象。47)
3 [注意:一些内置运算符和函数调用产生左值。[示例:如果 E 是指针类型的表达式,则 *E 是指 E 指向的对象或函数的左值表达式。再举一个例子,函数 int& f(); 产生一个左值,所以调用 f() 是一个左值表达式。]
- [注意:一些内置运算符需要左值操作数。[示例:内置赋值运算符都希望它们的左手操作数是左值。] 其他内置运算符产生右值,有些人期望它们。[示例:一元和二元 + 运算符期望右值参数并产生右值结果。] 第 5 节中对每个内置运算符的讨论表明它是否需要左值操作数以及它是否产生左值。]
5 调用不返回引用的函数的结果是一个右值。用户定义的运算符是函数,这些运算符是期望还是产生左值取决于它们的参数和返回类型。
6 保存由强制转换为非引用类型产生的临时对象的表达式是右值(这包括使用函数符号 (5.2.3) 显式创建对象)。
7 每当左值出现在需要右值的上下文中时,左值就会转换为右值;见 4.1、4.2 和 4.3。
8 8.5.3 中对引用初始化和 12.2 中临时变量的讨论表明了左值和右值在其他重要上下文中的行为。
9 类右值可以有 cvqualified 类型;非类右值总是有 cvunqualified 类型。右值应始终具有完整类型或 void 类型;除了这些类型之外,左值还可以有不完整的类型。
10 对象的左值是修改对象所必需的,但在某些情况下,类类型的右值也可用于修改其所指对象。[示例:为对象(9.3)调用的成员函数可以修改该对象。]
11 函数不能修改,但函数指针可以修改。
12 指向不完整类型的指针是可以修改的。在程序中的某个时刻,当指向的类型完成时,指针指向的对象也可以被修改。
13 constqualified 表达式的所指对象不应被修改(通过该表达式),除非它是类类型并且具有可变组件,则该组件可以被修改(7.1.5.1)。
14 如果一个表达式可以用来修改它所指的对象,则该表达式称为可修改的。试图通过不可修改的左值或右值表达式修改对象的程序是错误的。
15 如果程序试图通过非下列类型之一的左值访问对象的存储值,则行为未定义48): — 对象的动态类型, — 对象动态类型的 cv 限定版本, — 对应于对象动态类型的有符号或无符号类型, — 对应于对象动态类型的 cv 限定版本的有符号或无符号类型, — 聚合或联合类型,包括其成员中的上述类型之一(包括,递归地,子聚合或包含联合的成员),- 是对象动态类型的(可能是 cvqualified)基类类型的类型,- char 或 unsigned char类型。