以下 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;
不声明名为b
type的变量a
,如果这是您要问的。这是嵌套类型的(冗余)前向声明a::b
。空白在 C++ 程序中通常不重要。因此,您的程序声明但从未定义一个名为b
. 这违反了一个定义规则:因此程序格式错误,链接器会告诉你很多。
拿 2
struct a ::b;
是 的格式良好的声明struct a::b
。
它不是前向声明,因为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 节的一元范围运算符才能维持对 的全局::b
解释/*A*/
。
很明显,范围运算符 in不是/*A*/
一
元运算符。但是我们参考了§ 5.1一元作用域运算符和作用域解析运算符的语法定义的基本表达式,无论如何,我们还没有看到关于何时查找if 是作用域的右手操作数的内容。解析算子。b
在第 5.1.1/8 节,我们发现该运算符::
被定义为第 3.4.3/1 节的中缀运算符,并且在 qualified-id的语法中也被定义为第 3.4.3/4 节的一元运算符:
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 ::
当它是
嵌套名称说明符的最后一个标记时,它是中缀运算符,可选地是template
,然后是非限定标识符,否则它是上下文中的一元运算符:
:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id
根据此语法,如果可以,运算符必须与嵌套名称说明符(形成范围解析运算符)::
左关联
。根据语法,in是一个nested-name-specifier
并且是第一种形式
nested-name-specifier template[opt] unqualified-id 的限定id。a::
/*A*/
a::b
因此,我们确信 § 3.4.3限定名称查找适用于b
in
/*A*/
并且可以参考 § 3.4.3.1类成员,第 1 段,以得出b
在该上下文中查找 ina
而非全局的结论:
如果限定 id 的嵌套名称说明符指定了一个类,则在该类的范围内查找嵌套名称说明符之后指定的名称 (10.2),但下面列出的情况除外。
“下面列出的情况”均不适用于/*A*/
,如果对嵌套类a::b
算作 的成员有任何疑问,则a
§ 9.2类成员第 1 段将其删除:
类的成员是数据成员、成员函数 (9.3)、嵌套类型和枚举器。
发现a::b
明确需要b
在 中查找后a
,格式良好的/*A*/
问题就变成了是否
/*A*/
是每个标准的声明struct a;
的问题(肯定会在同一个地方)。
是的,我们可以通过声明-语法通过几个步骤来确保这 一点:-
根据 § 7 Declarations的第 1 段,声明可以是simple-declaration,而simple-declaration可以是decl-specifier-seq。
根据 § 7.1 Specifiers的第 1 段,一个decl-specifier-seq可以是一个decl-specifier,而decl-specifier可以是一个type-specifier。
根据第 7.1.6 节类型说明符第 1 段,类型说明符可以是 详细类型说明符。
根据 § 7.1.6.3详细类型说明符,第 0 段,详细类型说明符 可以是:
class-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier
其中class-key是class
, struct
or union
(per § 9 Classes , para 1) 和可选的attribute-specifier-seq无关紧要,因为我们不需要它。
/*A*/
满足:
class-key nested-name-specifier identifier
所以这是一个声明。
根据标准,/*A*/
是对a::b
.
关于 GCC 和 CLANG 的一些评论
Clang 3.3 诊断发布的代码:
error: forward declaration of struct cannot have a nested name specifier
这是错误的,因为声明没有转发。如果我们移动有问题的声明,使其确实在定义之前,a::b
那么 clang 不会对格式错误的前向声明进行错误处理,而是会诊断:
error: use of undeclared identifier 'a'
如果我们struct a ::b;
在程序中替换为struct a ::b x;
then clang 不会发现该行有任何错误,因此
a::b
在之前它持有该类型(非法)前向声明的同一点上找到了定义。
GCC 4.8.1 诊断:
warning: declaration ‘struct a::b’ does not declare anything
undefined reference to `b'
该警告只是一个警告,与作为格式良好的重新声明的声明一致,但可能更有助于附加“新”一词。如果我们移动有问题的声明,使其实际上是向前的,那么 GCC 自然会给出与 clang 相同的错误。
第二个链接错误是指extern struct b
通过 in 调用的未定义,并且未main
正确提供定义 by 。struct a ::b;