When the compiler parses the prototype, X will not be a complete type, so the compiler cannot see the constructor and destructor. Since it's the compiler's job to call the constructor and destructor, it will warn at this point.
The problem is that your blueprint tells the compiler how to call the code. Let's assume you use the function Y::foo somewhere in your code. At this point, the compiler has no idea what the constructor/destructor is because X is an incomplete type at this point, so it cannot generate correct code for where you use Y::foo.
On the other hand, if you make it a reference or a pointer, then the compiler does not need to call the constructor or destructor at the point where the function is initiated. Instead, the actual object is created inside the function, your implementation, and at this point, you would have included the correct header so the type X would be complete.
Let's take an example:
Code:
void foo()
{
Y y;
X x = y.foo();
}
This should translate to something like:
Code:
void foo()
{
Y y;
X local_x;
X temp_x = y.foo(); // Object returned from y.foo()
local_x.x(temp_x); // Copy constructor
temp_x.~x(); // Destructor
}
To generate the code, the compiler looks at Y's prototype and sees an incomplete type X and cannot generate correct code.
If y::foo returns, say, a pointer, then the code becomes:
Code:
void foo()
{
Y y;
X* local_x;
X* temp_x = y.foo(); // Object returned from y.foo()
local_x.x(temp_x); // Copy constructor
temp_x.~x(); // Destructor
}
Except, the type is no longer x, but x*, which is a variable, not a class, so the compiler can call the constructor and destructor fine.