I am looking for the definition of when I am allowed to do forward declaration of a class in another class's header file:
Am I allowed to do it for a base class, for a class held as a member, for a class passed to member function by reference, etc. ?
Put yourself in the compiler's position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it's called an incomplete type. Therefore, you cannot use the type to declare a member, or a base class, since the compiler would need to know the layout of the type.
Assuming the following forward declaration.
Here's what you can and cannot do.
What you can do with an incomplete type:
Declare a member to be a pointer or a reference to the incomplete type:
Declare functions or methods which accept/return incomplete types:
Define functions or methods which accept/return pointers/references to the incomplete type (but without using its members):
What you cannot do with an incomplete type:
Use it as a base class
Use it to declare a member:
Define functions or methods using this type
Use its methods or fields, in fact trying to dereference a variable with incomplete type
When it comes to templates, there is no absolute rule: whether you can use an incomplete type as a template parameter is dependent on the way the type is used in the template.
For instance,
std::vector<T>
requires its parameter to be a complete type, whileboost::container::vector<T>
does not. Sometimes, a complete type is required only if you use certain member functions; this is the case forstd::unique_ptr<T>
, for example.A well-documented template should indicate in its documentation all the requirements of its parameters, including whether they need to be complete types or not.
You will usually want to use forward declaration in a classes header file when you want to use the other type (class) as a member of the class. You can not use the forward-declared classes methods in the header file because C++ does not know the definition of that class at that point yet. That's logic you have to move into the .cpp-files, but if you are using template-functions you should reduce them to only the part that uses the template and move that function into the header.
I'm writing this as a separate answer rather than just a comment because I disagree with Luc Touraille's answer, not on the grounds of legality but for robust software and the danger of misinterpretation.
Specifically, I have an issue with the implied contract of what you expect users of your interface to have to know.
If you are returning or accepting reference types, then you are just saying they can pass through a pointer or reference which they may in turn have known only through a forward declaration.
When you are returning an incomplete type
X f2();
then you are saying your caller must have the full type specification of X. They need it in order to create the LHS or temporary object at the call site.Similarly, if you accept an incomplete type, the caller has to have constructed the object which is the parameter. Even if that object was returned as another incomplete type from a function, the call site needs the full declaration. i.e.:
I think there's an important principle that a header should supply enough information to use it without a dependency requiring other headers. That means header should be able to be included in a compilation unit without causing a compiler error when you use any functions it declares.
Except
If this external dependency is desired behaviour. Instead of using conditional compilation you could have a well-documented requirement for them to supply their own header declaring X. This is an alternative to using #ifdefs and can be a useful way to introduce mocks or other variants.
The important distinction being some template techniques where you are explicitly NOT expected to instantiate them, mentioned just so someone doesn't get snarky with me.
None of the answers so far describe when one can use a forward declaration of a class template. So, here it goes.
A class template can be forwarded declared as:
Following the structure of the accepted answer,
Here's what you can and cannot do.
What you can do with an incomplete type:
Declare a member to be a pointer or a reference to the incomplete type in another class template:
Declare a member to be a pointer or a reference to one of its incomplete instantiations:
Declare function templates or member function templates which accept/return incomplete types:
Declare functions or member functions which accept/return one of its incomplete instantiations:
Define function templates or member function templates which accept/return pointers/references to the incomplete type (but without using its members):
Define functions or methods which accept/return pointers/references to one of its incomplete instantiations (but without using its members):
Use it as a base class of another template class
Use it to declare a member of another class template:
Define function templates or methods using this type
What you cannot do with an incomplete type:
Use one of its instantiations as a base class
Use one of its instantiations to declare a member:
Define functions or methods using one of its instantiations
Use the methods or fields of one of its instantiations, in fact trying to dereference a variable with incomplete type
Create explicit instantiations of the class template
As well as pointers and references to incomplete types, you can also declare function prototypes that specify parameters and/or return values that are incomplete types. However, you cannot define a function having a parameter or return type that is incomplete, unless it is a pointer or reference.
Examples:
Lakos distinguishes between class usage
I've never seen it pronounced more succinctly :)