Я пишу это как отдельный ответ, а не просто как комментарий, потому что я не согласен с ответом Люка Турайля не из-за законности, а из-за надежного программного обеспечения и опасности неправильного толкования.
В частности, у меня есть проблема с подразумеваемым договором о том, что пользователи вашего интерфейса должны знать.
Если вы возвращаете или принимаете ссылочные типы, то вы просто говорите, что они могут проходить через указатель или ссылку, которые они, в свою очередь, могут знать только через предварительное объявление.
Когда вы возвращаете неполный тип, X f2();
вы говорите, что у вызывающей стороны должна быть полная спецификация типа X. Им это нужно для создания LHS или временного объекта на сайте вызова.
Точно так же, если вы принимаете неполный тип, вызывающая сторона должна создать объект, который является параметром. Даже если этот объект был возвращен из функции как еще один неполный тип, сайту вызова необходимо полное объявление. то есть:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
Я думаю, что есть важный принцип, что заголовок должен предоставлять достаточно информации, чтобы использовать его без зависимости, требующей других заголовков. Это означает, что заголовок должен быть включен в модуль компиляции, не вызывая ошибки компилятора при использовании любых функций, которые он объявляет.
Кроме
Если это внешняя зависимость желаемого поведения. Вместо использования условной компиляции у вас может быть хорошо задокументированное требование предоставить им собственный заголовок, объявляющий X. Это альтернатива использованию #ifdefs и может быть полезным способом представить макеты или другие варианты.
Важным отличием является то, что некоторые шаблонные методы, в которых вы явно НЕ должны создавать их экземпляры, упоминаются просто для того, чтобы кто-то не стал меня раздражать.