GCC 不喜欢与匿名名称空间前向声明交朋友,但 MSVC 喜欢。什么?

GCC doesn't like to make friends with anonymous namespace forward declarations, but MSVC does. What?

GodBolt.org

上测试时,此代码可在 MSVC 上编译,但不能在 GCC 上编译

Baz class 在匿名命名空间中声明,这使得 GCC 认为它与我在下面定义的不同 class,但 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; } };
}

根据标准,什么是正确的?

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

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

可以在此处找到有关匿名名称空间的更多详细信息: https://en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces

这似乎是语言措辞上的差异,不同的编译器在这个问题上有不同的立场。 MSVC 和 clang 将接受代码 as-is,但 GCC 和 Edge 等编译器拒绝它。

冲突的措辞来自:

10.3.1.2 [namespace.memdef]

... the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

结构 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 接受,因为对通过 using 声明或指令引入的类型是否被考虑为 friend 名称查找。在 cwg-138

可以看到更多关于这个问题的信息

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

要解决此问题,只需在好友声明中使用 class 的名称:

namespace { class Baz; }

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

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

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