10

以下 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);
4

2 回答 2

1

struct a ::b;不声明名为btype的变量a,如果这是您要问的。这是嵌套类型的(冗余)前向声明a::b。空白在 C++ 程序中通常不重要。因此,您的程序声明但从未定义一个名为b. 这违反了一个定义规则:因此程序格式错误,链接器会告诉你很多。

于 2013-07-30T21:39:44.707 回答
-3

拿 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 的限定ida::/*A*/a::b

因此,我们确信 § 3.4.3限定名称查找适用于bin /*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-keyclass, structor 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;

于 2013-07-30T22:51:49.637 回答