c++ 11中单个限定名和两个连续限定名之间的歧义

C++11 ambiguity between single qualified name and two consecutive?

本文关键字:连续 两个 歧义 之间 单个限 c++      更新时间:2023-10-16

下面的c++ 11程序是病态的吗?

struct a
{
struct b {  };
void f() {};
};
extern struct a b;
struct a ::b;
int main()
{
b.f();
}

为什么/为什么不?

这里有趣的是这一行:

struct a ::b;

是内部类a::b的前向声明吗?

或者这是全局变量b的定义?相当于:

struct a (::b);

struct a ::b;没有声明类型为a的名为b的变量,如果这就是您要问的。这是嵌套类型a::b的(冗余)前向声明。在c++程序中,空格通常不重要。因此,程序声明了一个名为b的变量,但从未定义它。这违反了"一个定义规则":程序因此是病态的,链接器会告诉你这些。

第2张

struct a ::b;struct a::b的格式良好的声明。

它不是forward声明,因为struct a::b已经是定义的。标准并没有正式定义前向声明,但是它被普遍接受的意思是在和之前声明某事吗是与其定义分开的。

也不是全局变量b的声明。

空格是不重要的,所以为了使假定的声明有建议的歧义,

/*A*/ struct a::b;

当然也必须具有同样的模糊性。像这样重写,我们可以我们同意宣言的含义是非常明显的;但是我们我想知道这个明显的意思是否被《标准》所证实。

我将把我的考虑局限于c++ 11标准。我想是一个决议c++ 03标准中这个问题的答案实际上更直接)。

标准将一元作用域操作符::作用域区分开来解析算子::.

§3.4.3限定名查找第1段:

类或命名空间成员或枚举数的名称可以在::作用域解析之后引用操作符(5.1)应用于嵌套名称说明符,表示其类、名称空间或枚举。

§3.4.3限定名查找,第4段:

以一元作用域操作符::(5.1)为前缀的名称在翻译单元的全局作用域中查找

这些引用不是为了解释的区别,只是为了表明这一点它的存在。但他们马上传达了/*A*/的观点对于§3.4.3/4,操作符必须是一元作用域操作符维持/*A*/的全球::b解释。

很明显,/*A*/中的作用域操作符是而不是一元操作符。但是我们提到了§5.1主表达式一元范围操作符范围的语法定义解析运算符,在任何情况下,我们都还没有看到关于当if是作用域的右操作数时,查找b解析操作符

.在§5.1.1/8,我们发现运算符::被定义为§3.4.3/1的中缀运算符,也可作为的一元运算符§3.4.3/4在qualified-id:

的语法
qualified-id:
nested-name-specifier template[opt] unqualified-id
:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id
nested-name-specifier:
::[opt] type-name ::
::[opt] namespace-name ::
decltype-specifier ::
nested-name-specifier identifier ::
nested-name-specifier template[opt] simple-template-id ::

当它是a的最后一个记号时,它是中缀操作符嵌套名称说明符后面可选地跟着template和然后是非限定id,否则是中的一元操作符背景:

:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id

根据这个语法,::运算符必须左关联a嵌套名称说明符(形成范围解析操作符)如果它可以。根据语法,/*A*/中的a::嵌套名称说明符a::b是第一种形式的限定idnested-name-specifier template[opt] unqualified-id.

所以我们确信§3.4.3限定名查找适用于bin/*A*/,可参考§3.4.3.1类成员,第1段;在该上下文中,b应该在a中查找,而不是全局查找:

如果限定id的嵌套名称说明符指定了一个类,则在嵌套名称后面指定的名称指定符在类(10.2)的作用域中查找,下面列出的情况除外。

"下列情况"均不适用于/*A*/,如果有任何疑问嵌套类a::b计数为a成员,§9.2类成员,第1段,删除它:

类的成员包括数据成员、成员函数(9.3)、嵌套类型和计数器。

发现a::b明确要求ba中查找,/*A*/的良构性问题变成了是否/*A*/是一个声明标准(当然struct a;在相同的

是的,我们可以通过几个步骤来确保这一点声明语法:-

  • Per§7声明,第1段,声明可以是简单声明简单声明可以是decl-specifier-seq

  • Per§7.1说明,第1段,adecl-specifier-seq可以是decl-specifierdecl-specifier可以是类型说明符

  • Per§7.1.6类型说明符在第1段中,类型说明符可以是elaborated-type-specifier.

  • Per§7.1.6.3详细的类型说明符,第0段,详细类型说明符可以是:

    class-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier

whereclass-keyclass,structunion(per§9Classes),第1段)而可选的属性指定符-seq并不重要,因为我们不需要需要它。

/*A*/满足:

class-key nested-name-specifier identifier

所以它是一个声明。

Per Standard,/*A*/a::b的重新声明。

关于GCC和CLANG的一些评论

Clang 3.3诊断发布的代码:

error: forward declaration of struct cannot have a nested name specifier

这是错误的,因为声明不是向前的。如果我们转移犯罪clang声明,使得a::b的定义之前,而不是错误格式错误的前向声明,而是诊断为:

error: use of undeclared identifier 'a'

如果将程序中的struct a ::b;替换为struct a ::b x;,则Clang认为这句话没有错,因此他找到了一个定义a::b在先前它持有该类型为(非法的)的同一点forward-declared .

GCC 4.8.1诊断:

warning: declaration ‘struct a::b’ does not declare anything
undefined reference to `b'

警告只是一个警告,与声明是一致的是一个格式良好的重新声明,但可能会更有帮助词"新"。如果我们移动冒犯的声明,使它实际上是向前的,那么GCC,自然会给出与clang相同的错误。

第二个链接错误是指未定义的extern struct bmain中调用,并且正确地提供了而不是struct a ::b;的定义