The logic is not the same. The difference is that in your second example function Base::test()
uses objects of its own class Base
(as opposed to a completely foreign class Derived
).
The language gives a special treatment to this situation in 8.3.5/6 (C++03)
The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).
This rule can be seen as a "satellite" for another similar rule - the one that says that the class type is always seen in its entirety (and as complete type) from bodies of class member functions, default arguments and constructor initializer lists. See 9.2/2 (C++03)
A class is considered a completely-defined object type (3.9) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments and constructor ctor-initializers (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
Note that in all other contexts before the closing }
the class is considered to be incomplete
struct S {
S foo(S s) // <- OK, due to 8.3.5/6
{ return s; }
void bar(int a = sizeof(S)) // <- OK, due to 9.2/2
{ S s; } // <- OK, due to 9.2/2
int (*baz())[sizeof(S)] // <- ERROR: incomplete type in `sizeof`
{ return NULL; }
void qux(int a[sizeof(S)]) // <- ERROR: incomplete type in `sizeof`
{}
};