Forward declaration lets us postpone defining an actual type till the implementation file. This is allowed in the header for pointers or references to a forward declared type.>
Returning by value does not require the type definition. A forward declaration is sufficient
Declaring a function that returns by value does not require the type definition. A well-formed demo:
struct S;
S foo();
struct S {};
int main() {
foo();
}
S foo() {
return {};
}
Defining or calling a function that returns by value does require the type definition. Standard draft [basic.def.odr]:
5 Exactly one definition of a class is required in a translation unit if the class is used in a way that requires the class type to be complete. [ Example: ... [snip] ... [ Note: The rules for declarations and expressions describe in which contexts complete class types are required. A class type T must be complete if:
- [snip]
- 5.9 a function with a return type or argument type of type T is defined ([basic.def]) or called ([expr.call]), or
- [snip]
The declaration of a function with incomplete return type is implicitly allowed by virtue of not being forbidden by any of the rules in the list.
The rule is re-worded later in the standard, and it is relaxed by an exception [dcl.fct] (thanks to @cpplearner for pointing this rule out):
11 Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete (possibly cv-qualified) class type in the context of the function definition unless the function is deleted ([dcl.fct.def.delete]).
An ill-formed demo:
struct S;
S foo() {
return {};
} // oops
struct S {};
Another ill-formed demo:
struct S;
S foo();
int main() {
foo(); // oops
}
struct S {};
S foo() {
return {};
}