10

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?

Macke
  • 24,812
  • 7
  • 82
  • 118
  • 1
    @Downvoters, what have I missed in thinking this is a good question? – Bathsheba Aug 13 '20 at 14:54
  • @user7860670 Updated with typo, sorry. – Macke Aug 13 '20 at 14:55
  • @Bathsheba we're not seeing/finding the combination that makes it compile on MSVC, as you state. Can you show us the link to a godbolt page that compiles it on MSVC? – Jeffrey Aug 13 '20 at 14:58
  • 1
    @Jeffrey I made the godbolt link in the Q contain the code now. Should've done that from start. – Macke Aug 13 '20 at 14:59
  • 1
    You can get rid of the inheritance and just have `class Bar { friend class Baz; protected: int x; };`. I can still reproduce it with that - compiles with msvc and clang but not gcc. – Kevin Aug 13 '20 at 15:00
  • 2
    [This question](https://stackoverflow.com/questions/19872920/is-it-possible-to-friend-a-class-in-an-anonymous-namespace-in-c) may be relevant. – 1201ProgramAlarm Aug 13 '20 at 15:02
  • 1
    Well, clang compiles it just fine so i'd assume that it is an issue with gcc. Looks like [Bug 71882](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71882) – user7860670 Aug 13 '20 at 15:05

3 Answers3

5

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

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • _Compilers like gcc and Edge choose not to continue looking into enclosing namespaces where MSVC and Edge will_ Ehm? Nobody looks into **enclosing** namespaces, the question is: should the lookup see through using-directives. Anonymous NS doesn't enclose the global NS. – Language Lawyer Aug 13 '20 at 15:18
  • I'm not commenting on the correctness of either interpretation, I am simply indicating where the discrepancy comes from as was mentioned in the linked gcc bug report. I also edited my answer 15 seconds before your comment which includes the fact that GCC does not find `friend` name lookup through `using`-directives due to a discrepancy relating to using directives, as you mentioned – Human-Compiler Aug 13 '20 at 15:22
  • _I also edited my answer 15 seconds before your comment_ I still see the sentense about enclosing namespaces... – Language Lawyer Aug 13 '20 at 15:24
  • That's because my remark is almost verbatim what Jonathon Wakely said in the bug report for an almost identical example. – Human-Compiler Aug 13 '20 at 15:43
4

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.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    TIL that I've been using elaborated type specifiers for friendship my entire life. I did not know this. I imagine you would still need elaborated specifiers for the likes of friendship across class templates though? – Human-Compiler Aug 13 '20 at 16:12
  • _your code declare a class Baz in the global namespace_ If the lookup doesn't find a preceding declaration. And the question is should lookup look up through using-directives. – Language Lawyer Aug 13 '20 at 16:26
  • @LanguageLawyer Fixed. – Oliv Aug 13 '20 at 16:42
  • 1
    _An elaborated type specifier is a declaration unless a previous declaration is found_ [**in the innermost enclosing namespace**](http://eel.is/c++draft/basic.namespace#namespace.memdef-3.sentence-5). _So GCC is wrong_ It is not 100% clear. – Language Lawyer Aug 13 '20 at 18:20
  • @LanguageLawyer OK, is there no open issue for this ambiguity? – Oliv Aug 13 '20 at 20:27
  • Awesomesauce! Thanks for teaching us about elaborated type specifiers! – Macke Aug 14 '20 at 06:16
2

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

Buddy
  • 10,874
  • 5
  • 41
  • 58
  • I don't know if that's relevant here, though. If you change the initial declaration to `namespace { class Baz { }; }` (defining `Baz`), gcc will give a "redefinition of 'class {anonymous}::Baz'" error, so the two anonymous namespaces are being considered as the same space. – 1201ProgramAlarm Aug 13 '20 at 15:10
  • Indeed it seems like unspecified according to your link. Also, according to the bug link above, EDG and GCC went one way, Clang and MSVC the other. Of course. :) – Macke Aug 13 '20 at 15:11
  • @1201ProgramAlarm OTOH, since it's an implied "using " after the first, it makes sense that it clashes since the current lookup already has Baz from the first anon namespace, even inside the second anon namespace. – Macke Aug 13 '20 at 15:12