The type Foo1
is complete only at the end of };
struct Foo1 : Base1<Foo1> {
// still incomplete
} /* now complete */;
But before Foo1
start to become defined, it must first instantiate the base class for the base class to be complete.
template <typename Derived>
struct Base1 {
// Here, no type are complete yet
// function declaration using a member of incomplete type
int baz(typename Derived::value_type) {
return 42;
}
};
Inside the base class body, no class are complete yet. You cannot use nested typename there. The declaration must all be valid when defining a class type.
Inside the body of member function, it's different.
Just like this code don't work:
struct S {
void f(G) {}
using G = int;
};
But this one is okay:
struct S {
void f() { G g; }
using G = int;
};
Inside the body of member functions, all types are considered complete.
So... why does the auto
return type works if it deduce to the type you cannot access?
auto
return type is indeed special, since it allows function with deduced return types to be forward declared, like this:
auto foo();
// later
auto foo() {
return 0;
}
So the deduction of auto can be used to defer usage of types in the declaration that would be otherwise incomplete.
If auto
was deduced instantaneously, types in the body of the function would not be complete as the specification imply, since it would have to instantiate the body of the function when defining the type.
As for parameter types, they are also part of the declaration of the function, so the derived class is still incomplete.
Although you cannot use the incomplete types, you can check if the deduced parameter type is really typename Derived::value_type
.
Even though the instantiated function recieve typename Derived::value_type
(when called with the right set of argument), it is only defined at the instantiation point. And at that point, the types are complete.
There's something analoguous to the auto return type but for parameter and that means a template:
template<typename T>
int baz(T) {
static_assert(std::is_same_v<typename Derived::value_type, T>)
return 42;
}
As long as you don't directly use the name from the incomplete type inside declarations, you'll be okay. You can use indirections such as templates or deduced return types and that will make the compiler happy.