同一翻译单元中多未命名命名空间的唯一性判定及编译器行为正确性咨询
这是个很容易让人产生误解的问题,咱们一步步拆解清楚:
标准与cppreference的解读澄清
你之前对标准[namespace.unnamed]节的理解出现了偏差,咱们重新梳理这段描述的核心含义:
all occurrences of unique in a translation unit are replaced by the same identifier, and this identifier differs from all other identifiers in the translation unit
这里的关键是同一翻译单元(TU)内的所有未命名命名空间定义,都会被替换为同一个唯一标识符——换句话说,同一个TU里你写多少次namespace { ... },它们都是同一个命名空间的分段扩展,就像多次定义同一个具名命名空间(比如namespace Foo { ... }之后再写namespace Foo { ... }是在扩展Foo命名空间)。
cppreference的说法完全符合标准的实际含义:
The unique name is unique over the entire program, but within a translation unit each unnamed namespace definition maps to the same unique name: multiple unnamed namespace definitions in the same scope denote the same unnamed namespace.
两者并没有矛盾,只是你一开始误把每个匿名命名空间块当成了独立的命名空间,实际上它们是同一个命名空间的拆分定义。
编译器行为的正确性判断
Clang的行为是符合标准的,而GCC在这个场景下的行为是错误的。
看你的代码示例:
#include <iostream> namespace { class AnonClass; } class A { public: void print() { std::cout << a << '\n'; } private: int a; friend class AnonClass; }; namespace { class AnonClass { public: AnonClass(A* parent) { parent->a = 42; } }; } int main() { A parent; AnonClass ac{&parent}; parent.print(); }
第一个匿名命名空间里的class AnonClass;是对同一个匿名命名空间内类的前向声明,第二个匿名命名空间块是在扩展这个命名空间,所以里面的class AnonClass定义就是之前前向声明的那个类。A中的friend class AnonClass;自然能正确关联到这个类,因此AnonClass的构造函数可以访问A的私有成员a,最终输出42。
而GCC错误地将两个匿名命名空间块视为完全独立的命名空间,导致前向声明的AnonClass和后面定义的是两个无关的类,A的友元声明只对第一个匿名空间里的(未定义的)AnonClass生效,后面的AnonClass没有访问A私有成员的权限,因此编译报错。
内容来源于stack exchange

