Is there a general rule on how to resolve ambiguous grammar?
In particular, a pseudo-destructor-name includes these production rules
nested-name-specifieropt type-name :: ~ type-name
nested-name-specifieropt ~ type-name
And nested-name-specifier includes
nested-name-specifier identifier ::
type-name ::
Given the following
struct A
{
struct B {};
B b;
};
A a;
a.b.A::B::~B();
Which of the following production is choosed for the last line?
type-name :: type-name :: ~ type-name
type-name :: identifier :: ~ type-name
This has significance in a templated context, since [temp.res]
In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword.
Where grammatical ambiguities occur, they should be accompanied by a disambiguation rule. Some such rules are fairly specific (e.g. [expr.unary.op]/10) while others are quite general (e.g. [dcl.ambig.res]/1). If there is no disambiguation rule that applies to a given ambiguity, the ambiguity represents an unintended defect in the standard. There is no disambiguation rule that is general enough to resolve all conceivable ambiguities.
In the C++14 grammar, there is an ambiguity you didn't mention (pointed out by @xskxzr); it's between
postfix-expression .
template
opt id-expression
and
postfix-expression .
pseudo-destructor-name
(see [expr.post]/1).
Since A::B
is actually a class type, presumably this ambiguity is intended to be resolved in favour of the former. In fact, if it were interpreted as a pseudo-destructor-name, it would be ill-formed under [expr.pseudo]/2. The reason why it's called a pseudo-destructor call is that it operates on scalar types, which do not have actual destructors:
The left-hand side of the dot operator shall be of scalar type. [..] This scalar type is the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. [...]
There does not seem to be an applicable disambiguation rule, so this could be viewed as a defect. However, in C++20, the grammar was changed anyway, so it's moot. I will discuss this more later in the answer.
Suppose we change your example so that it involves a scalar type:
struct A
{
using C = int;
C c;
};
A a;
a.c.A::C::~C();
Now, A::C::~C
cannot be an id-expression because if it were an id-expression, it would have to be a qualified-id ([expr.prim.general]/9) consisting of a nested-name-specifier A::C::
and an unqualified-id ~C
, and ~C
can only be an unqualified-id if C
is either a class-name or a decltype-specifier ([expr.prim.general]/1), but C
is not a class-name because it has not been declared as a class ([class]/1). So A::C::~C
can only be a pseudo-destructor-name, not an id-expression.
So now the question becomes how to interpret A::C::~C
as a pseudo-destructor-name. This illustrates the ambiguity that you intended to illustrate.
The submitter of CWG 1753 seemed to think that the nested-name-specifier ~
type-name form is only intended to cover the case where the nested-name-specifier designates a namespace (not a class). The issue page indicates that this issue was voted as a DR. That means that its resolution—which was to remove the nested-name-specifier ~
type-name production—is retroactive. In other words, the committee officially eliminated this ambiguity, not by inserting an ambiguity resolution rule, but by saying that the nested-name-specifier ~
type-name interpretation never should have been legal in the first place.
So the correct interpretation is nested-name-specifier type-name ::
~
type-name.
In C++20, the concept of a pseudo-destructor-name was abolished altogether. This means that the ambiguity mentioned by @xskxzr was also fixed. In C++20, what used to be a pseudo-destructor-name is now just a special type of id-expression that occurs when the destructor that it denotes is a fake destructor (i.e. one of a non-class type). (For the curious, this change was made by P1131R2.)