【问题标题】:What standard clause mandates this lvalue-to-rvalue conversion?什么标准条款要求这种左值到右值的转换?
【发布时间】:2011-06-16 18:12:43
【问题描述】:

给定:

int main() {
   int x = 0;
   int y = x; // <---
}

有人能告诉我标准的哪个条款(2003 年首选)要求在对象初始化中将表达式 xlvalue 转换为 rvalue y?

(或者,如果我弄错了并且没有发生这种转换,那么我也想学习!)

【问题讨论】:

  • 我认为不会发生任何此类转换。在任何情况下,这些都是原始类型,但即使您有非平凡的构造函数,您编写的内容也会调用副本,而不是移动构造函数。
  • @Kerrek:但需要使用该值。这样的转换怎么会发生呢? (而且 2003 年没有移动构造函数)。
  • @Tomalak:我明白了……这只是编译器内部的语义问题,还是转换有任何可见的影响?
  • @Kerrek:我当然希望它没有影响!

标签: c++ language-lawyer


【解决方案1】:

我发现将 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,最后一个项目符号(指基本类型)状态(强调我的):

  • 否则,被初始化对象的初始值是初始化表达式的(可能转换的)。 [...]

那里的 value 意味着您的示例中的左值表达式是 read (即转换为右值)。无论如何,前面提到赋值的段落可以在这里应用:如果初始化需要一个 lvalue 而不是 rvalue,则表达式 int i = 0; 将是不正确的。

【讨论】:

  • 不错的一个。我认为这是一个相当可靠的推论。 :)
  • "从 5.17 [expr.ass] 开始,赋值右侧是右值的要求要么是隐含的,要么是缺失的,但情况确实如此,否则下面的表达式将是一个错误,因为rhs 是一个右值,没有右值到左值的转换”
  • @Tomalak:不,原因是表达式的右侧必须是要么左值或右值,但不能同时是两者。现在:如果要求表达式的右侧是左值表达式,那么i=5 将失败;该表达式有效(不会失败),因此表达式的右侧不能要求为左值。由于唯一的其他选项是 rvalue(至少在 C++03 中),因此表达式 x = y 将左值作为 lhs(这在标准中是明确的)和右值作为 rhs (我找不到准确的报价)
  • 为什么不能是 either 取决于上下文?
【解决方案2】:

确实相信这在某种程度上是直观的(其他人已经说过 - value 是需要的,因此显然需要将对象指示符转换为其中包含的值)。 4p3 之前我能想到的最好的:

表达式 e 可以隐式转换为类型 T 当且仅当声明“T t=e;”对于一些发明的临时变量 t (8.5) 是良构的。隐式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果相同。 如果 T 是引用类型 (8.3.2),则结果为左值,否则为右值。当且仅当初始化将其用作左值时,表达式 e 才用作左值。

注意最后的“当且仅当”——初始化器被用作一个右值,因为初始化使用它作为一个右值(转换的结果)。所以到 3.10p7

只要左值出现在需要右值的上下文中,左值就会转换为右值;请参阅 4.1、4.2 和 4.3。


编辑:输入 4p3 的段落可以在 8.5p16 找到,最后一个项目符号:

否则,被初始化对象的初始值是初始化表达式的(可能转换的)值。

还要注意下面的 cmets。

【讨论】:

  • 但是有转化吗?以int 开头,以int 结尾。仅凭这个推理,我不确定第一句话是否适用。
  • @Tomalak 转换并不是真正从类型开始,而是从表达式开始。并且表达式从 int 的左值转换为 int 的右值。
  • @litb:这是循环逻辑。 “有一个转换,因为这条规则讨论了需要转换时会发生什么。”
  • @Tomalak 我同意可以找到这样的循环推理。可以说是循环的。也可以争辩说,该段落不是在谈论 an“隐式转换*,而是关于 隐式转换序列。也就是说,转换序列 根本没有转换。否则,0 不能“隐式转换”为int 类型的纯右值,这是错误的,因为int a = 0 格式正确。我认为这是一个合理的假设放在措辞中 - 该段落显然不排除包含两个或多个转换的序列。
  • @litb:不,但是在给定的场景中,转换序列首先出现在图片中也没有任何要求。您已经整理好归纳步骤,但仍然缺少基本案例。 :)
【解决方案3】:

这是你要找的吗:

§3.10/7

只要左值出现在需要右值的上下文中,左值就会转换为右值;请参阅 4.1、4.2 和 4.3。

我认为当你写int y = x时,它基本上复制了对象x中包含的,这是一个左值,但本身是一个右值,因此上下文需要一个右值。

§4.1/2 说,

左值指示的对象中包含的值是右值结果。

也许这两个引文可以澄清你的疑问。如果我的理解有误,请纠正我。我想学习新的东西。


@Tomalak 的评论:

我的问题是 int& y = x;是有效的,所以在这种情况下,当然 x 可能不是右值。不过,我不知道我的示例中的差异有多么无关紧要

int &amp;y = x 不会复制该值。它只是创建对象本身的别名。但正如我之前所说的int y = x,基本上复制 value 这是一个右值。因此,上下文需要一个右值,因为这里正在进行复制。

【讨论】:

  • 差不多,但是右值应该在哪里?
  • 我的问题是int&amp; y = x; 是有效的,所以在这种情况下x 当然可能不是一个rvalue。不过,我不知道我的示例中的差异有多么无关紧要。
  • @Tomalak Geret'kal:我已经在上面回答了这个问题,整数赋值(任何非用户定义类的赋值)将右侧参数作为右值。否则,int x = 5; 将无效,因为5 是右值,而右值不能转换为左值。
  • @Downvoter:我不明白投反对票的原因。你能指出来吗?
  • @Tomalak Geret'kal:如果评论“赋值!=初始化”指的是我上面的评论,你是对的,它们不一样,我滥用了这个词,然而,评论立场。将注释中的“初始化”替换为“赋值”即可。
【解决方案4】:

初始化器的语法如下:

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,但不一定是这样。并且没有强制的左值到右值的转换。

不过,我不确定知道这一点有什么价值。

【讨论】:

    【解决方案5】:

    3.10 左值和右值

    1 每个表达式要么是左值 或右值。

    2 左值指的是一个对象或 功能。一些右值 表达式——类的或 cvqualified 类类型——也可以参考 对象.47)

    3 [注意:一些内置运算符和 函数调用产生左值。 [示例:如果 E 是 指针类型,那么 *E 是一个左值 表示对象或 E 指向的函数。作为另一个 例如,函数 int& f();产量 一个左值,所以调用 f() 是一个 左值表达式。 ]

    1. [注意:一些内置运算符需要左值操作数。 [示例:内置 赋值运算符都期望他们的 左手操作数是左值。 ] 其他内置运算符产生右值, 有些人期待他们。 [例如: 一元和二元 + 运算符期望 右值参数和产生右值 结果。 ] 每个人的讨论 第 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 如果程序试图访问 通过一个对象的存储值 不是其中一个的左值 以下类型的行为是 undefined48): — 的动态类型 对象,——一个 cvqualified 版本 对象的动态类型——a 有符号或无符号的类型 动态类型对应的类型 对象的,- 一种类型,即 有符号或无符号类型对应 到 cvqualified 版本的 对象的动态类型,- 聚合或联合类型,包括 上述类型之一 它的成员(包括,递归地,一个 子聚合或包含的成员 union), — 一个类型是(可能 cvqualified) 的基类类型 对象的动态类型,——一个字符 或无符号字符类型。

    【讨论】:

      猜你喜欢
      • 2011-07-03
      • 2014-01-29
      • 2017-11-24
      • 1970-01-01
      • 2013-07-12
      • 1970-01-01
      • 2014-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多