villapx
villapx

Reputation: 1873

C++ Forward Declaring Classes Within Classes

The following simple piece of code compiles, although I don't understand why:

class C {
    class B;

    class A {
        B getB() { return B(); }
    };

    class B {
    };
};

int main(int, char**)
{
    return 0;
}

If I then comment out the "class C" stuff, so that the forward declaration of B, the definition of A and the definition of B are no longer nested within a class, the code does not compile, since B is of an incomplete type:

main.cpp: In member function 'B A::getB()':
main.cpp:6: error: return type 'struct B' is incomplete
main.cpp:6: error: invalid use of incomplete type 'struct B'
main.cpp:3: error: forward declaration of 'struct B'

I understand what it means for a type to be incomplete, namely that it has not been defined yet and so the compiler can't possibly know how much space to allocate for it. But why is B not considered incomplete in the code above, where A and B are both declared and defined inside of C?

Upvotes: 18

Views: 1666

Answers (4)

Riccardo Manfrin
Riccardo Manfrin

Reputation: 741

Besides that when I've got the need to forward declare nested classes I tend to smell some bad design in my code, the trick I use is:

// Foo.h
class Foo {
    class Bar {
    };
};
class Foobar : public Foo::Bar {};


// Zoo.h
/* Fwd declare */
class FooBar;

Upvotes: 0

R Sahu
R Sahu

Reputation: 206557

The body of an inline member function is not processed until the class definition has been fully processed.

Hence, you can use:

class A 
{
   class B;
   B getB() { return B(); }

   class B {};
};

That also allows member variables that are not yet declared to be used in inline member function definition.

class Foo
{
   int getBar() { return bar; }

   int bar;
};

I am guessing the same logic is extended to inline definitions of member functions of nested classes -- i.e. they are not processed until the containing class definition is completely processed.

PS I am unable to quickly locate the reference in the standard that would verify my claim.

PS 2 The answer by Barry has the reference in the standard that makes the code in the question valid.

Upvotes: 9

Barry
Barry

Reputation: 302663

I believe this is a consequence of [basic.scope.class]:

The potential scope of a name declared in a class consists not only of the declarative region following the name’s point of declaration, but also of all function bodies, default arguments, exception-specifications, and brace-or-equal-initializers of non-static data members in that class (including such things in nested classes).

That is, the scope of the full declaration of B includes the body of the member function of the nested class:

class C {
    class B; // (1)

    class A { 
        B getB() {
            return B(); // both (1) and (2) in scope here
                        // since (2) is the complete type declaration,
                        // this is perfectly fine
        }
    };

    class B { // (2)
    };
};

By comparison, if C were a namespace instead of a class, the scope of the full declaration of class B would not extend into A::getB(). The only visible declaration would be the forward-declaration of B that I labeled (1) - so B() would be the construction of an incomplete type there.

Upvotes: 10

Richard Hodges
Richard Hodges

Reputation: 69854

The standard is explicit in mandating that the method's body is interpreted after the class that encloses it.

Thus at the time of evaluating the body of C::A::getB(), A, B and C are all complete types.

Upvotes: 5

Related Questions