2

this code compiles under MSVC 2013, but not under Clang 500.2.79:

class A
{
    friend class B;
    B *b;
};

class B
{
};

> error: unknown type name 'B'

Why?

japreiss
  • 11,111
  • 2
  • 40
  • 77
  • "but if I remove the friend it works fine, as expected." It doesn't. You actually declare a nested class `A::B` and a separate, unrelated class `B`. – n. m. could be an AI May 16 '14 at 20:46
  • It does compile under VC++2012. A friend declaration there doubles as a forward declaration, but perhaps this is a nonstandard extension? – dlf May 16 '14 at 20:53
  • you're right, but that was a red herring, edited because I really want to understand the root question. And yeah, it worked in MSVC for me too – japreiss May 16 '14 at 20:54
  • @dlf Are you confusing local classes (i.e., local to a function) with nested (inside a class definition) classes? My C++11§11.3/11 draft copy (n3485 from 2012-11) refers to that. – Jeff May 16 '14 at 21:15
  • @Jeff Looks like it. Retracted. – dlf May 16 '14 at 21:17
  • See this question: http://stackoverflow.com/q/14114956/3549027 – dlf May 16 '14 at 21:22
  • @dlf Yeah, that appears to be wrong, so I should probably flag it for a moderator's attention. – Jeff May 16 '14 at 21:38
  • @jeff Do you know whether it's VC++ or Clang that is wrong here? The part I and the other question's answerer quoted is the only thing I saw that seemed to address the issue. – dlf May 16 '14 at 21:47
  • @dlf It looks live VC++ is in the wrong here. (fwiw, g++ agrees with Clang) I think the relevant clause is in C++11 §7.3.1.2/3 - [namespace.memdef]: *If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2).* Only functions appear to get the special treatment of not-yet declared but being accessible. – Jeff May 16 '14 at 21:58

1 Answers1

4

A friend declaration doesn't itself (always) require a forward declaration, but subsequent uses of the friend pointers/references do. VC++ appears to be allowing functionality not allowed by the language specification. Functions do have slightly more liberal lookup rules than classes though:

C++11 §7.3.1.2/3 (Namespace member definitions) [namespace.memdef]:

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace. The name of the friend is not found by unqualified lookup (3.4.1) or by qualified lookup (3.4.3) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2).

The specification's example:

// Assume f and g have not yet been declared.
void h(int);
template <class T> void f2(T);

namespace A {
  class X {
    friend void f(X);        // A::f(X) is a friend

    class Y {
      friend void g();       // A::g is a friend
      friend void h(int);    // A::h is a friend, ::h not considered
      friend void f2<>(int); // ::f2<>(int) is a friend
    };
  };

  // A::f, A::g and A::h are not visible here
  X x;

  void g()    { f(x); }      // definition of A::g
  void f(X)   { /* ... */}   // definition of A::f
  void h(int) { /* ... */ }  // definition of A::h
  // A::f, A::g and A::h are visible here and known to be friends
}

using A::x;
void h() {
  A::f(x);
  A::X::f(x); // error: f is not a member of A::X
  A::X::Y::g(); // error: g is not a member of A::X::Y
}

Internal ("nested") classes are automatically friended, but they have to be defined, not just declared, internally:

class A {
  public:
    // This class is nested, and referred to as "A::B" outside of A.
    class B {
      public:
        int foo(A &a) { return a.x; } // OK by default to access A's privates
    };

  private:
    int x;
};

A a;
A::B b;
int n = b.foo(a);

If you move your definition of B (or just make a forward declaration), you can properly friend a non-nested class:

class B;

class A {
  friend class B; // OK, since B (not nested) declared before this directive
  B *b; 
}

class B { }; // can define B later, since only B pointer/references used earlier
Community
  • 1
  • 1
Jeff
  • 3,475
  • 4
  • 26
  • 35