【问题标题】:What is the purpose of the Most Vexing Parse?Most Vexing Parse 的目的是什么?
【发布时间】:2021-09-18 04:08:44
【问题描述】:

Wikipedia 我发现了这个:

A a( A() );

[This] 可以被消歧为

  1. 类 [A] 的变量定义,采用类 [A] 的匿名实例或
  1. 一个函数的函数声明,它返回一个 [A] 类型的对象,并接受一个(未命名)参数,该参数是一个返回类型 [A] 的函数(并且不接受任何输入)。

大多数程序员期望第一个,但 C++ 标准要求将其解释为第二个。

但是为什么呢?如果 C++ 社区的大多数人都期望前一种行为,为什么不将其作为标准呢?此外,如果您考虑到解析的歧义,上述语法是一致的。

有人可以请教我吗?为什么标准会要求这样做?

【问题讨论】:

  • 哪里有函数指针?
  • 阅读错误信息——ideone.com/12sT80#view_edit_boxAren't function pointers of type T (*)()?
  • @templateboy:该消息具有误导性:在那种情况下,a 本质上已衰减为用于表达式a.f 的函数指针,但a 的声明本身无关带有函数指针。
  • @downvoter 我可以解释一下吗?

标签: c++ most-vexing-parse


【解决方案1】:

假设 MVP 不存在。

你会如何声明一个函数?

A foo();

将是一个变量定义,而不是一个方法声明。你会介绍一个新的关键字吗?函数声明的语法会更尴尬吗?或者你更愿意拥有

A foo;

定义一个变量并

A foo();

声明一个函数?

您稍微复杂一点的示例只是为了与这个基本示例保持一致。 “所有可以解释为声明的东西都将被解释为声明”而不是 “所有可以解释为声明的东西都将被解释为声明,这更容易说,除非它是单个变量定义,在这种情况下它是一个变量定义".

可能不是它背后的动机,而是它是一件事情的原因。

【讨论】:

    【解决方案2】:

    对于 C++,这非常简单:因为规则是在 C 中制定的。

    在 C 语言中,只有typedef 和一些相当晦涩的代码才会产生歧义。几乎没有人会意外触发它——事实上,除了专门为演示这种可能性而设计的代码之外,它可能是罕见的。然而,无论好坏,这种模棱两可的可能性意味着必须有人解决它——如果没记错的话,正是丹尼斯·里奇(Dennis Ritchie)解决了这个问题,丹尼斯·里奇(Dennis Ritchie)下令任何可以被解释为声明的东西都是声明,即使定义也有模棱两可的解释。

    C++ 增加了使用括号进行初始化以及函数调用作为分组的能力,这将模糊性从模糊变为常见。然而,改变它需要打破来自 C 的规则。像大多数人所期望的那样解决这个特殊的歧义,而不创造更多更令人惊讶的六个可能也相当不简单,除非你愿意完全放弃与 C 的兼容性。

    【讨论】:

    • 我正在为本科生的 C++ 课做办公时间,其中一个学生的作业中有这个:)
    • C 中的歧义是如何得到的?即使使用 typedef,通常的 C++ 示例也没有 C 类似物,因为 C 没有函数式强制转换表示法。
    • @BrianBi: pdos.csail.mit.edu/archive/l/c/roskind.html 对这种情况的阐述相当不错。
    • 一旦考虑到先前声明的范围,这些示例实际上并不模棱两可;以T(*b)[4]; 为例,您只需要知道此时可见的T 的声明是否属于typedef-name。但是,即使您知道某物是否是一种类型,C++ 令人烦恼的解析仍然是模棱两可的。 (1/2)
    • @BrianBi:经过更多思考,(并查看)我终于找到了实际规则(§6.7.5.3/11):“在参数声明中,括号中的单个 typedef 名称是被认为是一个抽象的声明符,它指定一个带有单个参数的函数,而不是声明符标识符周围的多余括号。”我不记得允许它的情况,但它是关于一个被冗余括号包围的 typedef 名称。哦,这至少在 C99 之前仍然存在。我不确定这是唯一的例子,但无论如何它是一个。
    【解决方案3】:

    这是递归定义语法的副作用。

    不是故意这样设计的。它被发现并记录为最令人烦恼的解析。

    【讨论】:

    • 你能详细说明一下吗?
    • @TanveerBadar 他们并没有故意将语言设计成复杂的,它应该是合乎逻辑的。虽然由于 C++ 的复杂性,一般来说有一些事情超出了最初的设计者,我们现在被他们困住了。 “Most Vexing Parse”只是该语言使用一段时间后发现的一个常见问题的简单名称。我们不能破坏向后兼容性,所以我们不能修复语言(不是直接修复,尽管我们已经添加到语言中以减少这个问题)。
    【解决方案4】:

    这只是一个猜测,但可能是因为使用给定的方法,您可以获得两种行为:

    A a( A() ); // this is a function declaration
    A a( (A()) ); // this is a variable definition
    

    如果您要将其行为更改为变量定义,那么函数声明会复杂得多。

    typedef A subfunction_type();
    
    A a( A() ); // this would be a variable declaration
    A a( subfunction_type ); // this would be a function declaration??
    

    【讨论】:

    • 不太正确,A a( A() ); 是一个函数声明。它可以改写为A a (A (*)()),即一个名为a 的函数并返回一个A 类型的对象,其参数是一个函数:void -> A。尝试编译它以进行最简单的检查)
    【解决方案5】:

    没有特别的原因,除了 [可能] K-ballo 确定的情况。

    这只是遗产。已经有了 int x; 构造形式,因此当没有 ctor 参数在起作用时,似乎永远不会要求 T x;

    事后看来,如果该语言是从零开始设计的,那么 MVP 将不复存在……以及大量其他 C++ 怪事。

    回想一下,C++ 已经演变了几十年,即使是现在,也只是由委员会设计的(另请参阅:camel)。

    【讨论】:

      【解决方案6】:

      考虑一下程序是否是这样的:

      typedef struct A { int m; } A;
      int main() { A a( A() ); }
      

      这将是有效的 C,并且 C 的语法只允许一种可能的解释:a 被声明为函数。 C 只允许使用=(不是括号)进行初始化,并且不允许将A() 解释为表达式。 (函数样式转换是 C++ 独有的特性。)这不是 C 中的“令人头疼的解析”。

      正如 Wikipedia 指出的那样,C++ 的语法使这个例子变得模棱两可。但是,如果您希望 C++ 赋予该程序与 C 相同的含义,那么显然,C++ 编译器将不得不像 C 编译器一样将 a 解释为函数。当然,C++可以改变了这个程序的含义,使a 成为A 类型变量的定义。但是,只有在有充分理由时才将与 C 的不兼容性引入 C++,我想 Stroustrup 特别希望避免像这样的潜在无声破坏,因为它们会引起极大的挫败感适用于迁移到 C++ 的 C 用户。

      因此,C++ 也将其解释为函数声明,而不是变量定义;更一般地说,采用的规则是,如果看起来像函数样式转换的东西可以在其句法上下文中被解释为声明,那么它应该是。通过确保不采用 C 中不可用的解释(涉及函数样式转换的解释),这消除了在所有令人烦恼的解析情况下与 C 不兼容的可能性。

      Cfront 2.0 Selected Readings(第 1-42 页)在表达式声明歧义的情况下提到了 C 兼容性问题,这是最令人头疼的解析的相关类型。

      【讨论】:

        猜你喜欢
        • 2011-05-01
        • 2015-11-09
        • 2016-08-13
        • 2012-11-18
        • 1970-01-01
        相关资源
        最近更新 更多