【问题标题】:Why the Standard C++ Grammar for Assignment Expression Looks so Weird为什么赋值表达式的标准 C++ 语法看起来如此奇怪
【发布时间】:2013-08-22 13:00:23
【问题描述】:

从 C++ 标准来看,赋值表达式的语法是这样的:

assignment-expression:
  conditional-expression
  logical-or-expression assignment-operator assignment-expression
  throw-expression

assignment-operator: one of
   = *= /= %= += -= >>= <<= &= ^= |=

请注意,“赋值运算符”的左侧是“逻辑或表达式”,即类似于 (4 || localvar1) = 5;是根据语法的有效赋值表达式。这对我来说没有意义。为什么他们选择“逻辑或表达式”而不是标识符或 id_expression?

【问题讨论】:

  • 语法可以产生特定表达式的事实并不意味着该表达式在标准上是有效的。还有其他限制,例如(除非您为localvar1 的类型重载了operator||)在问题4 || localvar1 中会产生一个无法分配的bool rvalue。那么如果你重载了operator||,它将取决于重载的样子(即返回类型是什么)

标签: c++ grammar


【解决方案1】:

语法有点复杂,但是如果您继续展开前面的定义,您会发现赋值表达式是非常通用的,并且几乎可以处理任何事情。虽然您引用的标准中的 sn-p 侧重于 logical-or-expression,但如果您继续展开其定义,您会发现赋值的左侧和右侧几乎可以是 any 子表达式(虽然不是字面上的 any)。

前面指出的原因是赋值可以应用于枚举或基本类型的任何左值表达式或类类型的右值表达式(其中operator=始终是成员)。许多表达式,在允许运算符重载并且不定义从运算符返回的类型是什么的语言中,可以潜在地满足赋值的需要,并且语法必须允许所有这些用途。

标准中的不同规则稍后将限制可以从语法生成的可能表达式中哪些是实际有效的。

【讨论】:

    【解决方案2】:

    您的特定语句 (4 || localvar1) = 5; 无效(除非 operator|| 被重载),因为您不能将 5 分配给 4(4 是 r 值)。您必须在左侧有一个 l-value(可以分配的东西),例如函数返回的引用。

    例如,假设您有一些函数int&amp; get_my_int(),它返回对整数的引用。然后,您可以这样做:

    `get_my_int() = 5;`
    

    这会将get_my_int() 返回的整数设置为5。 就像在您的第一篇文章中一样,这必须是对整数(而不是值)的引用;否则,上述语句将无法编译。

    【讨论】:

    • s/有效/可能有效/coliru.stacked-crooked.com/…
    • (localvar1 || localvar2) = 5; 不是一个有效的表达式,因为(localvar1 || localvar2) 不是左值。这是一个布尔结果。
    • @JoeZ s/这是/可能是/coliru.stacked-crooked.com/…
    • @R.MartinhoFernandes:很好,我没有考虑到运算符重载。
    • @R.MartinhoFernandes:无论如何,无效的原因是 您不能将 5 分配给 4(4 是 r 值) 非常有限[仅限于 localvar1 是一种重载 operator|| 的类型——无论如何你可能不应该这样做——它总是返回一个值为 4 的 int]...如果我们要在这里挑剔:P
    【解决方案3】:

    关于赋值语句的 C++ 语法实际上有两件有趣的事情,它们都与以下内容的有效性无关:

     (4 || localvar1) = 5;
    

    由于括号,该表达式在语法上是有效的(直到类型检查)。 任何引用类型的括号表达式在赋值运算符的左侧在语法上是正确的。 (而且,正如已经指出的那样,几乎任何涉及用户类型或函数的表达式都可以是引用类型,这是运算符重载的结果。)

    该语法更有趣的是,它建立了赋值运算符的左优先级低于几乎所有其他运算符,包括逻辑或,因此上述表达式在语义上等价于

    4 || localvar1 = 5;
    

    尽管许多读者会将上述内容解释为4 || (localvar1 = 5)(假设localvar1 是可以由int 分配的类型,这将是完全正确的,即使所述分配永远不会发生 - - 当然,除非 || 在这种情况下被重载)。

    那么,赋值运算符左侧的优先级较低的是什么?正如我所说,很少,但一个重要的例外是?:

    // Replace the larger of a and b with c
    a > b ? a = c : b = c;
    

    是有效且方便的无括号。 (许多风格指南在这里坚持使用多余的括号,但我个人更喜欢不带括号的版本。)这与右手优先不同,因此以下也适用于不带括号的情况:

    // Replace c with the larger of a and b
    c = a > b ? a : b;
    

    在赋值运算符左侧绑定比赋值运算符更松的其他运算符是, 运算符和另一个赋值运算符。 (换句话说,与几乎所有其他二元运算符不同,赋值是右关联。)这些都不足为奇——事实上,它们是如此必要,以至于很容易忽略设计的重要性这样的语法。考虑以下不起眼的for 子句:

    for (first = p = vec.begin(), last = vec.end(); p < last; ++p)
    

    这里的, 是一个逗号操作符,它显然需要比它周围的任何一个赋值更紧密地绑定。 (C 和 C++ 仅在此语法中例外,因为它具有逗号运算符;在大多数语言中,, 不被视为运算符。)此外,显然不希望将第一个赋值表达式解析为 (first = p) = vec.begin()

    赋值运算符与右侧相关联这一事实并不引人注目,但出于对历史的好奇,值得注意。当 Bjarne Stroustrup 在四处寻找操作符以重载 I/O 流时,他选择了 &lt;&lt;&gt;&gt;,因为尽管赋值操作符可能更自然 [1],但赋值绑定到右侧,并且流运算符必须绑定到左侧(std::cout &lt;&lt; a &lt;&lt; b 必须是 (std::cout &lt;&lt; a) &lt;&lt; b)。但是,由于 &lt;&lt; 绑定比赋值更紧密,因此使用流式运算符时有许多陷阱。(最近让我抓到的一个是这种移位比按位运算符绑定得更紧密。)

    [注 1]:我没有对此的引用,但我记得很多年前在C++ 编程语言中阅读过它。我记得,对于赋值运算符是否自然并没有达成共识,但似乎比重载移位运算符更自然,使其与正常语义完全不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-22
      相关资源
      最近更新 更多