问题 在类中引入的类名不被视为嵌套类名


采取这些类定义:

班级定义1:

struct A 
{
   struct B* m_b;
};

班级定义2:

struct A 
{
   struct B;
   B* m_b;
};

两种类别的定义都应该声明 B 作为嵌套类。至少,通过阅读C ++ 11标准草案中的以下内容,我的想法是:

9.1 / 2类声明将类名引入声明它的作用域,并在封闭作用域中隐藏该名称的任何类,变量,函数或其他声明(3.3)。如果在声明了同名变量,函数或枚举器的作用域中声明了类名,那么当两个声明都在作用域中时,只能使用 详尽型说明符 `

但是,g ++ 4.8.2对它们的处理方式不同。在第一个定义中,它对待 B 作为一个同伴的类 A

以下程序成功构建:

struct A 
{
   struct B* m_b;
};

void myfun(const B& b )
{
}

int main()
{
   A a;
   myfun(*a.m_b);
}

而以下程序没有:

struct A 
{
   struct B;
   B* m_b;
};

void myfun(const B& b )
{
}

int main()
{
   A a;
   myfun(*a.m_b);
}

我理解为什么第二个程序没有编译,但我不明白为什么第一个程序是成功构建的。

我在标准的解释中遗漏了什么吗?

编译第一个程序时g ++ 4.8.2是否正确?


11523
2018-06-26 06:08


起源



答案:


g ++在这里的行为是完全正确的。这在标准的§3.3.2[basic.scope.pdecl] / p7中指定:

一个类的声明点首先在一个声明    详尽型说明符 如下:

  • 声明表格
    class-key attribute-specifier-seq选择 标识符; 
     标识符声明为a 班级名称 在包含的范围内   声明,否则
  • 详尽型说明符 形式
    类密钥标识符
     如果 详尽型说明符 用于 DECL说明符-SEQ 要么 参数声明子句 一个   在命名空间作用域中定义的函数,标识符声明为    班级名称 在包含声明的命名空间中;否则,除了朋友声明*,标识符被声明   在包含的最小命名空间或块范围内   宣言。

请注意,在第二种情况下,声明始终放在命名空间或块作用域中,而不是类作用域,因此它永远不能声明嵌套类。此外,在第二种情况下,将执行查找,并且仅在先前声明的情况下执行 类型名 是不会找到的 详尽型说明符 被用来声明一个新名称(§3.4.4[basic.lookup.elab] / p2,§9.1[class.name] / p3 note)。


* 朋友声明有他们自己奇怪的规则。在友元声明中首先声明的名称仍然放在命名空间(对于非本地类)或块(对于本地类)作用域中,但是对于大多数名称查找(除了函数的ADL除外)它们都不可见,直到它们为止。也在包含它们的范围内声明。非本地类的规则在§7.3.1.2[namespace.memdef] / p3中指定: 

如果非本地类中的友元声明首先声明一个类或函数,那么友元类或函数是最内层封闭命名空间的成员。通过非限定查找(3.4.1)或限定查找(3.4.3)找不到朋友的名称,直到在该命名空间范围内提供匹配声明(在授予友谊的类定义之前或之后)。如果调用了友元函数,则可以通过名称查找找到其名称,该名称查找考虑名称空间中的函数和与函数参数类型相关联的类(3.4.2)。如果   朋友声明中的姓名既不合格也不合格 模板id 而声明是一种功能或一种功能 详尽型说明符,确定实体是否先前已声明的查找不应考虑最内层封闭命名空间之外的任何范围。

本地类的规则在§11.3[class.friend] / p11中指定:

如果友元声明出现在本地类(9.8)中并且指定的名称是非限定名称,则会查找先前声明,而不考虑最内部封闭非类作用域之外的作用域。 [...]对于友元类声明,如果没有先前的声明,则指定的类属于最内层的非类作用域,但如果随后引用它,则名称查找时找不到其名称,直到匹配声明在最内层的非类范围内提供。


15
2018-06-26 06:45



你能不能更详细地解释一下你上面写的“注意在第二种情况下......”?我的理解是这个评论适用于 第一 代码和标识符 B 被认为是在包含声明的最小名称空间或块作用域中声明的类名,该声明是全局名称空间。那是对的吗?如果是这样,这背后的理性是什么? - Wake up Brazil
@WakeupBrazil“第二个案例”在标准引用的第二个要点中,而不是问题中的第二个代码。 - T.C.


答案:


g ++在这里的行为是完全正确的。这在标准的§3.3.2[basic.scope.pdecl] / p7中指定:

一个类的声明点首先在一个声明    详尽型说明符 如下:

  • 声明表格
    class-key attribute-specifier-seq选择 标识符; 
     标识符声明为a 班级名称 在包含的范围内   声明,否则
  • 详尽型说明符 形式
    类密钥标识符
     如果 详尽型说明符 用于 DECL说明符-SEQ 要么 参数声明子句 一个   在命名空间作用域中定义的函数,标识符声明为    班级名称 在包含声明的命名空间中;否则,除了朋友声明*,标识符被声明   在包含的最小命名空间或块范围内   宣言。

请注意,在第二种情况下,声明始终放在命名空间或块作用域中,而不是类作用域,因此它永远不能声明嵌套类。此外,在第二种情况下,将执行查找,并且仅在先前声明的情况下执行 类型名 是不会找到的 详尽型说明符 被用来声明一个新名称(§3.4.4[basic.lookup.elab] / p2,§9.1[class.name] / p3 note)。


* 朋友声明有他们自己奇怪的规则。在友元声明中首先声明的名称仍然放在命名空间(对于非本地类)或块(对于本地类)作用域中,但是对于大多数名称查找(除了函数的ADL除外)它们都不可见,直到它们为止。也在包含它们的范围内声明。非本地类的规则在§7.3.1.2[namespace.memdef] / p3中指定: 

如果非本地类中的友元声明首先声明一个类或函数,那么友元类或函数是最内层封闭命名空间的成员。通过非限定查找(3.4.1)或限定查找(3.4.3)找不到朋友的名称,直到在该命名空间范围内提供匹配声明(在授予友谊的类定义之前或之后)。如果调用了友元函数,则可以通过名称查找找到其名称,该名称查找考虑名称空间中的函数和与函数参数类型相关联的类(3.4.2)。如果   朋友声明中的姓名既不合格也不合格 模板id 而声明是一种功能或一种功能 详尽型说明符,确定实体是否先前已声明的查找不应考虑最内层封闭命名空间之外的任何范围。

本地类的规则在§11.3[class.friend] / p11中指定:

如果友元声明出现在本地类(9.8)中并且指定的名称是非限定名称,则会查找先前声明,而不考虑最内部封闭非类作用域之外的作用域。 [...]对于友元类声明,如果没有先前的声明,则指定的类属于最内层的非类作用域,但如果随后引用它,则名称查找时找不到其名称,直到匹配声明在最内层的非类范围内提供。


15
2018-06-26 06:45



你能不能更详细地解释一下你上面写的“注意在第二种情况下......”?我的理解是这个评论适用于 第一 代码和标识符 B 被认为是在包含声明的最小名称空间或块作用域中声明的类名,该声明是全局名称空间。那是对的吗?如果是这样,这背后的理性是什么? - Wake up Brazil
@WakeupBrazil“第二个案例”在标准引用的第二个要点中,而不是问题中的第二个代码。 - T.C.