【问题标题】:GCC doesn't like to make friends with anonymous namespace forward declarations, but MSVC does. What?GCC 不喜欢与匿名命名空间前向声明交朋友,但 MSVC 喜欢。什么?
【发布时间】:2020-08-13 14:49:48
【问题描述】:

GodBolt.org 上测试时,此代码在 MSVC 上编译,但在 GCC 上不编译

Baz 类是在匿名命名空间中声明的,这使 GCC 认为它与我在下面定义的类不同,但 MSVC 似乎将它们连接起来。

namespace { class Baz; }

class Foo { protected: int x; };
class Bar : public Foo { friend class Baz; };

namespace {
  class Baz { void f() { Bar b; b.x = 42; } };
}

根据标准什么是正确的?

【问题讨论】:

  • @Downvoters,我认为这是一个好问题时错过了什么?
  • @Jeffrey 我现在让 Q 中的神螺栓链接包含代码。从一开始就应该这样做。
  • 你可以摆脱继承而只拥有class Bar { friend class Baz; protected: int x; };。我仍然可以用它来重现它 - 用 msvc 和 clang 但不是 gcc 编译。
  • This question 可能是相关的。
  • 嗯,clang 编译得很好,所以我认为这是 gcc 的问题。看起来像Bug 71882

标签: c++ language-lawyer


【解决方案1】:

这似乎是语言措辞上的差异,不同的编译器在这个问题上采取了不同的立场。 MSVC 和 clang 将按原样接受代码,但 GCC 和 Edge 等编译器会拒绝它。

矛盾的措辞来自:

10.3.1.2 [namespace.memdef]

...确定实体是否先前已声明的查找不应考虑最内层封闭命名空间之外的任何范围。

结构Baz 没有在最里面的封闭命名空间中声明,但它在那里可见,因此正常的名称查找会找到它。但由于这不是正常的名称查找,像 gcc 和 Edge 这样的编译器不会查找封闭的命名空间,只查找最里面的。

此信息来自该文件gcc bug,该文件讨论了该主题。

似乎 MSVC 和 Edge 选择使用匿名命名空间进行不同的解释,这会将 OP 的代码转换为以下内容:

namespace unnamed { }
using namespace unnamed;
namespace unnamed { struct Baz; }

class Foo { protected: int x; };
class Bar : public Foo { friend class Baz; };

namespace unnamed { class Baz { void f() { Bar b; b.x = 42; } }; }

这个等效代码也被 gcc 和 Edge 等编译器拒绝,但被 MSVC 和 clang 接受,因为对于 friend 名称查找是否考虑通过 using 声明或指令引入的类型有不同的解释。有关此问题的更多信息,请访问cwg-138

【讨论】:

  • gcc 和 Edge 之类的编译器选择不继续研究 MSVC 和 Edge 将使用的封闭命名空间 嗯?没有人查看 enclosure 命名空间,问题是:查找是否应该通过 using-directives。匿名 NS 不包含全局 NS。
  • 我没有评论任何一种解释的正确性,我只是指出链接的 gcc 错误报告中提到的差异来自何处。我还在您的评论前 15 秒编辑了我的答案,其中包括由于与使用指令有关的差异,GCC 没有通过 using-directives 找到 friend 名称查找这一事实,正如您提到的那样
  • 我还在您发表评论前 15 秒编辑了我的答案我仍然看到关于封闭命名空间的句子...
  • 那是因为我的评论几乎是 Jonathon Wakely 在错误报告中所说的几乎一模一样的例子。
【解决方案2】:

问题是您使用了一个详细的类型说明符来声明友谊,而 GCC 使用它在全局命名空间中声明一个类 BazAn elaborated type specifier is a declarationunless a previous declaration is found in the inner most enclosing namespace。显然,尚不清楚 Baz 的声明是否应被视为在全局命名空间中。

要解决这个问题,只需在朋友声明中使用类的名称:

namespace { class Baz; }

class Foo { protected: int x; };
class Bar : public Foo { friend Baz; };

namespace {
  class Baz { void f() { Bar b; b.x = 42; } };
}

在朋友声明中使用详细类型说明符是一种惯用的病态习惯。除非类型的名称也是变量的名称,否则没有理由使用详细类型说明符。

【讨论】:

  • 直到我一生都在使用精心设计的类型说明符来建立友谊。我不知道这个。我想您仍然需要为跨类模板的友谊之类的详细说明符?
  • 您的代码在全局命名空间中声明一个类 Baz 如果查找未找到前面的声明。问题是应该通过 using-directives 进行查找。
  • @LanguageLawyer 已修复。
  • 一个详细的类型说明符是一个声明,除非找到之前的声明 in the innermost enclosing namespace所以 GCC 是错误的 这不是 100% 清楚的。
  • @LanguageLawyer 好的,这种歧义没有未解决的问题吗?
【解决方案3】:

匿名命名空间就好像它们有一个唯一的名称,并且只对当前翻译单元可用。

似乎有些编译器会给翻译单元内的所有匿名命名空间同名,而其他编译器可能不会(只是对可能实现的猜测),但似乎不是您可以依赖的东西。

关于匿名命名空间的更多细节可以在这里找到: https://en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces

【讨论】:

  • 不过,我不知道这是否与此相关。如果将初始声明更改为namespace { class Baz { }; }(定义Baz),gcc 将给出“redefinition of 'class {anonymous}::Baz'”错误,因此两个匿名命名空间被视为同一个空间。
  • 确实,根据您的链接,它似乎未指定。此外,根据上面的错误链接,EDG 和 GCC 是一种方式,Clang 和 MSVC 是另一种方式。当然。 :)
  • @1201ProgramAlarm OTOH,因为它是在第一个之后隐含的“使用 ”,所以它发生冲突是有道理的,因为当前查找已经具有来自第一个匿名命名空间的 Baz,即使在第二个匿名内也是如此命名空间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-10
  • 1970-01-01
  • 2017-02-08
  • 1970-01-01
  • 2020-04-07
相关资源
最近更新 更多