Consider the following:
template<class> struct T
{
struct T1
{
struct T2
{
};
};
/*typename*/ T1::T2 m;
};
Without typename
, compilation fails as T2 is considered a dependent name, and thus not a type. After looking to a C++17 draft standard (N4659), I believe I've determined why:
§ 17.6.2.1 ¶ 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.
...
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.
T1 is a dependent member of the current instantiation. T2 is not a member of the current instantiation (it is a member of T1),
§ 17.6.2.1 ¶ 9.3
A type is dependent if it is
...
— a nested class or enumeration that is a dependent member of the current instantiation,
...
T1 is a nested class and thus a dependent type.
§ 17.6 ¶ 3
When a qualified-id is intended to refer to a type that is not a member of the current instantiation (17.6.2.1) and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming a typename-specifier. ...
Thus, typename
is needed.
Is my understanding correct? If so, what's the rationale behind this? How could the lookup of T1::T2
ever find anything other than the T2 nested within T1?
Yes, you are correct.
In your case it doesn't matter because you cannot possibly specialize T1
without instantiating m
(as it is a class member). But you could do this, put m
in a function:
template<class> struct T
{
struct T1
{
struct T2
{
};
};
void foo() {
typename T1::T2 m;
}
};
template<>
struct T<void>::T1 {
int T2;
};
If T1
were not dependent, your code could possibly change meaning because T2
would refer to a value and not a type.