Given the following class template:
template<typename T>
struct Outer
{
struct Inner;
auto f(Inner) -> void;
};
we define Inner
separately for each specialization of Outer
:
template<>
struct Outer<int>::Inner {};
template<>
struct Outer<double>::Inner {};
and then define the member function f
once for all specializations of Outer
:
auto Outer<T>::f(Inner) -> void
{
}
but Clang (9.0.0) complains:
error: variable has incomplete type 'Outer::Inner'
auto Outer<T>::f(Inner) -> void
^
We can evade the compiler error by also providing a definition of Inner
for all other specializations of Outer
:
template<typename T>
struct Outer<T>::Inner {};
or by defining f
separately for each specialization:
template<>
auto Outer<int>::f(Inner) -> void
{
}
template<>
auto Outer<double>::f(Inner) -> void
{
}
Both GCC and MSVC accept the initial code, which begs the question; is this a Clang bug or is it the only conformant implementation out of the three?
Try on Compiler Explorer
I believe Clang is wrong to reject your code. We must ask ourselves, how does your function declaration and definition compare to
auto f(typename T::Inner) -> void;
// ...
template<typename T>
auto Outer<T>::f(typename T::Inner) -> void
{ }
In this example, T::Inner
is obviously a dependent type. So Clang may not assume it's incomplete until instantiation. Does the same hold true in your example? I would say so. For we have this in the standard:
[temp.dep.type]
5 A name is a member of the current instantiation if it is
- An unqualified name that, when looked up, refers to at least one member of a class that is the current instantiation or a non-dependent base class thereof. [ Note: This can only occur when looking up a name in a scope enclosed by the definition of a class template. — end note ]
- ...
A name is a dependent member of the current instantiation if it is a member of the current instantiation that, when looked up, refers to at least one member of a class that is the current instantiation.
9 A type is dependent if it is
- ...
- a member of an unknown specialization,
- a nested class or enumeration that is a dependent member of the current instantiation,
- ...
So the first bullet in paragraph 9 covers the case typename T::Inner
. That is a dependent type.
Meanwhile your case is covered by the second bullet. Outer::Inner
is a name that is found in the current instantiation of Outer
, moreover it's found inside Outer
itself, and not in a base class. That makes it a dependent member of the current instantiation. This name refers to a nested class. Which means all the conditions in the second bullet apply, thus making Outer::Inner
a dependent type as well!
Since we have ourselves a dependent type in both cases, compilers should treat them equally as dependent types. My conclusion is that GCC and MSVC are right.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With