语法二义性如何解决?

Pas*_* By 5 c++ language-lawyer c++14

是否有关于如何解决歧义语法的一般规则?

特别是,伪析构函数名称包括这些产生规则

嵌套名称说明符opt类型名称 :: ~ 类型名称

嵌套名称说明符opt ~ 类型名称

嵌套名称说明符包括

嵌套名称说明符标识符 ::

类型名称 ::

鉴于以下情况

struct A
{
    struct B {};
    B b;
};

A a;
a.b.A::B::~B();
Run Code Online (Sandbox Code Playgroud)

最后一条生产线选择以下哪一种产品?

类型名称 :: 类型名称 :: ~ 类型名称

类型名称 :: 标识符 :: ~ 类型名称

这在模板化上下文中具有重要意义,因为[temp.res]

在立即包含依赖于模板参数的嵌套名称说明符的嵌套名称说明符中,隐式假定标识符或 simple-template-id 来命名类型,而不使用 typename 关键字。

Bri*_*ian 1

当出现语法歧义时,应附有消歧规则。一些这样的规则是相当具体的(例如[expr.unary.op]/10),而其他规则则相当笼统(例如[dcl.abig.res]/1)。如果没有适用于给定歧义的消歧规则,则该歧义代表标准中的意外缺陷。不存在足够通用的消歧规则来解决所有可能的歧义。

\n
\n

在C++14语法中,有一个你没有提到的歧义(由@xskxzr指出);它介于

\n

\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0后缀表达式 . templateopt id 表达式

\n

\n

\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0后缀表达式 . 伪析构函数名称

\n

(参见[expr.post]/1)。

\n

由于A::B它实际上是一个类型,因此大概是为了有利于前者而解决这种歧义。事实上,如果它被解释为伪析构函数名称,那么在 [expr.pseudo]/2 下它将是错误的。它被称为伪析构函数调用的原因是它对标量类型进行操作,标量类型没有实际的析构函数

\n
\n

点运算符的左侧应为标量类型。[..] 该标量类型是对象类型。对象类型和伪析构函数名称指定的类型的 cv 未限定版本应为同一类型。[...]

\n
\n

似乎没有适用的消歧规则,因此这可以被视为缺陷。然而,在 C++20 中,语法无论如何都发生了变化,所以它没有实际意义。我将在稍后的答案中详细讨论这一点。

\n
\n

假设我们更改您的示例,使其涉及标量类型:

\n
struct A\n{\n    using C = int;\n    C c;\n};\n\nA a;\na.c.A::C::~C();\n
Run Code Online (Sandbox Code Playgroud)\n

现在,A::C::~C不能是id-expression,因为如果它是id-expression,则它必须是由嵌套名称说明符非限定 id组成的限定 id ([expr.prim.general]/9) ,并且如果是类名decltype 说明符([expr.prim.general]/1),则只能是非限定 ID ,但不是类名,因为它尚未声明为类 ( [类]/1)。所以只能是伪析构函数名称,而不是id-表达式 A::C:: ~C~CCCA::C::~C

\n

所以现在的问题变成了如何解释A::C::~C伪析构函数名称。这说明了您想要说明的歧义。

\n

CWG 1753的提交者似乎认为嵌套名称说明符 ~ 类型名称形式仅旨在涵盖嵌套名称说明符指定命名空间(而不是类)的情况。问题页面显示该问题被投票为 DR。这意味着其删除嵌套名称说明符 ~ 类型名称生成\xe2\x80\x94 的解决方案\xe2\x80\x94 具有追溯力。换句话说,委员会正式消除了这种歧义,不是通过插入歧义解决规则,而是通过说嵌套名称说明符 ~ 类型名称解释从一开始就不应该是合法的。

\n

所以正确的解释是nested-name-specifier type-name :: ~ type-name

\n
\n

在 C++20 中,伪析构函数名称的概念被完全废除。这意味着@xskxzr 提到的歧义也得到了修复。在 C++20 中,曾经的伪析构函数名称现在只是一种特殊类型的id 表达式,当它表示的析构函数是假析构函数(非类类型之一)时,就会出现这种情况。(出于好奇,这个更改是由P1131R2进行的。)

\n