Search code examples
c++friend

Why can't I forward declare an internal friend class?


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?


Solution

  • 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