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.:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
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.