GCC doesn't like to make friends with anonymous namespace forward declarations, but MSVC does. What?
This appears to be a discrepancy in the language wording, with different compilers taking different sides on the issue. MSVC and clang will accept the code as-is, but compilers like GCC and Edge reject it.
The conflicting wording comes from:
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.
The struct Baz
is not declared in the innermost enclosing namespace, but it is visible there, so normal name lookup would find it. But since this isn't normal name lookup, Compilers like gcc and Edge don't look into the enclosing namespaces, only the innermost.
This information is from this filed gcc bug which discusses the topic.
It appears that MSVC and Edge choose to interpret using anonymous namespaces differently, which would transform OP's code to the following:
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; } }; }
This equivalent code is also rejected by compilers like gcc and Edge, but accepted by MSVC and clang due to a different interpretation of whether types that are brought in via using
declarations or directives are considered for friend
name lookup. More can be seen on this issue at cwg-138
The problem is that you are using an elaborated type specifier for the friendship declaration and GCC use it to declares a class Baz
in the global namespace. An elaborated type specifier is a declaration unless a previous declaration is found in the inner most enclosing namespace. Apparently it is not clear if the declaration of Baz
should be considered to be in global namespace.
To fix this, just use the name of the class in the friend declaration:
namespace { class Baz; }
class Foo { protected: int x; };
class Bar : public Foo { friend Baz; };
namespace {
class Baz { void f() { Bar b; b.x = 42; } };
}
The use of elaborated type specifier in a friend declaration is an idiomatic pathological habit. There is no reason to use elaborated type specifier unless the name of the type is also the name of a variable.
Anonymous namespaces act as if they have a unique name and are only available to the current translation unit.
It seems plausible that some compilers would give all anonymous namespaces within a translation unit the same name, and others might not (just a guess at a possible implementation), but seems not like something you can rely on.
More details about anonymous namespaces can be found here: https://en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces