Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

This code compiles on MSVC but not on GCC, when testing on GodBolt.org

The Baz class is declared in the anonymous namespace, which makes GCC think it's a different class than what I then define below, but MSVC seems to connect them.

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; } };
}

What is correct according to the standard?

like image 219
Macke Avatar asked Aug 13 '20 14:08

Macke


3 Answers

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.

like image 172
Oliv Avatar answered Sep 21 '22 15:09

Oliv


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

like image 28
Human-Compiler Avatar answered Sep 22 '22 15:09

Human-Compiler


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

like image 39
Buddy Avatar answered Sep 24 '22 15:09

Buddy