下面的第一个代码片段可以编译通过,但第二个却不能。为什么?

47

以下代码片段可以被编译(演示):

struct A{ int i = 10; };

int main() {
    struct A{ int i = 20; };
    struct A;
    struct A a;
}
但是这个不会:
struct A{ int i = 10; };

int main() {
//    struct A{ int i = 20; };
    struct A;
    struct A a;
}

我可以看到,答案可能就在标准的这些段落中:

[basic.lookup.elab]/2[basic.scope.pdecl]/7

但是我真的不知道如何从这两个段落中推导出上面所展示的不同行为。

请注意,在第一个示例中,struct A不是elaborated-type-specifier struct A;中首次声明的,而是在main()函数中struct A的定义中声明的。

在第二个示例中,struct A不是elaborated-type-specifier struct A;中首次声明的,而是在全局作用域中struct A的定义中声明的。


1
我想了解为什么@RyanHaining首先将其删除。也许他有原因。 - giusti
4
当然,还是有一些灰色地带的。在C++语言专家问题中,我看到的一般趋势是编译器不一致,或者程序编译时没有保证其具有良好定义行为。这些地方会让“语言专家”们争论哪个编译器的行为是正确的,或者一个程序是否符合标准要求以达到良好定义。由于您应该从此代码接收到准确的错误信息,我认为它不属于通常发布的[language-lawyer]内容之内。 - Ryan Haining
4
实际上,这似乎不是一个适合放置“language-lawyer”标签的好例子。标准很明确,而这段代码片段是不规范的。就这样。这只是我的个人见解。 - skypjack
3
据我所知,“语言律师”只是表示提问者想要一份官方标准的精确参考,以澄清问题中发生的情况。因此,任何行为的问题都可以用这个标签来提问,无论你是否觉得它们有趣。 - Bakuriu
language-lawyer 是一种与语言无关的标签,这似乎是在 C++ 中会被删除的问题类型。我并不反对它符合所述要求的说法。如果我在这里是少数派,那没关系,因为现在它已经回来了。 - Ryan Haining
@skypjack "这似乎不是语言律师标签的好候选者。标准很清楚,但片段格式不正确" 语言律师与标准是否清晰无关! - curiousguy
3个回答

67

每个示例都包含两个不同类的声明,它们都使用名称 A

让我们通过将其中一个重命名为 B 来区分这两个类:

struct A{ int i = 10; };

int main() {
    struct B{ int i = 20; };
    struct B;
    struct B b;
}

上面的代码与您的第一个示例在语义上完全相同。类A从未被使用。

struct A{ int i = 10; };

int main() {
    struct B;
    struct B b;
}

这与您的第二个示例在语义上完全相同。您试图创建一个不完整类型的对象,即前向声明的类B

B改名回A并没有改变任何内容,因为然后在main中对A的声明会使全局作用域中的另一个A的声明被遮蔽。

[basic.lookup.elab]/2

如果 elaborated-type-specifier 没有nested-name-specifier,并且[...] 如果 elaborated-type-specifier 以以下形式在声明中出现:

class-key attribute-specifier-seqopt identifier ;

elaborated-type-specifier 是引入class-name的声明,如[basic.scope.pdecl]所述。

因此,struct A;是一个声明,它在声明范围内引入了类名称。无论如何,它都不能引用在外部作用域中声明的类。

[basic.scope.pdecl]/7

[注:其他形式的 elaborated-type-specifier 不声明新名称[...] - end note]

由此推断,这种形式的 elaborated-type-specifier 声明了一个新名称。


[basic.scope.pdecl]/7从以下语句开始:在 elaborated-type-specifier 中首次声明的类的声明点如下所示:。这意味着,仅当声明class A;首次在 elaborated-type-specifier 中声明时,才考虑项目符号(7.1)和(7.2),而这在我的问题结尾处指出的两个例子中都不是这种情况。 - João Afonso
就[basic.scope.pdecl]/7而言,我认为我们不能像你之前所断言的那样说,在我的第二个例子中,main()中的声明struct A;不能引用在外部作用域中声明的类。无论如何,感谢您的回复。 - João Afonso
我解释了为什么在你的第二个例子中,struct A;A 的第一个声明,因为全局作用域中的 A 是一个完全不同的类,但名称相同。 - Oktalist
[basic.scope.declarative]/1:"声明的作用域与其潜在作用域相同,除非潜在作用域包含另一个具有相同名称的声明。在这种情况下,内部声明区域中声明的潜在作用域将从外部声明区域中的声明作用域中排除。" - Oktalist
1
我认为我在 [class.name]/2 中找到了我的问题的答案,它确切地陈述了你在回答中所说的内容(“在任何情况下,它都不能引用在外部作用域中声明的类。”)。谢谢(+1)。 - João Afonso

44

在第二个示例中,行struct A;是一个在主函数作用域中称为A的结构体的前向声明。这个结构体将优先于全局的struct A。下一行定义了一个名为astruct A类型的变量。由于在主函数作用域中声明了struct A,编译器会在那里搜索它的定义。但它找不到定义(因为它被注释掉了)。第一个示例编译通过,因为在相同的作用域中有定义。下面的示例将编译通过,因为它指定了A位于全局命名空间中:

struct A{ int i = 10; };

int main() {
//    struct A{ int i = 20; };
    struct A;
    struct ::A a;
}

2
@FrançoisAndrieux:我不会将其描述为“疏忽”。您的原始版本在技术上是正确的(struct A;是一个声明,但不是定义)。然而,使用“前向声明”而不是“声明”,确实使得对于非语言专家来说更容易阅读。 - Martin Bonner supports Monica

5

编译失败是因为找不到 A 的定义。

int main() {
//    struct A{ int i = 20; };
      struct A;
      struct A a;
}

上述代码与您的第一个示例相同,因为全局变量A被本地变量A所遮蔽。在第二个示例中,A没有定义。它只是一个原型。当定义放在需要它的代码之后时,应该将原型放置在代码之前。如果编译器找不到该定义,则会失败,因为它不知道A应该是什么(全局定义被本地原型所遮蔽,导致被忽略)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接