That is not exactly true. VC++ really wouldn't care about typename at all weren't it for the standard saying that it has to.
There is nothing that prevents a compiler from parsing the template and storing it as an ambiguous parse tree until instantiation. A compiler following this strategy (and VC++ does that, more or less, based on its behaviour) has no need of type disambiguation.
On the other hand, such a compiler also loses the ability for early syntax checking of templates. Consider this code:
Code:
int p;
template <typename T> void foo()
{
some_template<T>::xyz * p;
p->bar();
}
An early-checking compiler decides that xyz is not a type (no typename keyword) and thus treats the first statement as
Code:
lookup::operator *(some_template<T>::xyz, ::p);
Then the second line is treated as
Since the global p has type int, this is faulty and the early-checking compiler immediately complains. It does not require the template to be instantiated to complain! This is important in developing templates. (That's half the point of concepts.)
The late-checking compiler must wait until somebody actually instantiates the code to see whether the p here is a local variable or binds to the global. Only then can it issue compile errors.
Worse! Look at this modified example:
Code:
struct stru { void bar() { do_something(); } stru* operator ->() { return this; } };
struct ct { void bar() { do_something_else(); } };
stru operator *(int, const stru&) { return stru(); }
stru something;
template <typename T> struct templ { typedef ct xyz; };
template <> struct templ<float> { static int xyz; };
int templ<float>::xyz;
template <typename T> void foo()
{
templ<T>::xyz * something;
something->bar();
}
int main()
{
foo<int>();
foo<float>();
}
Here, both interpretations of xyz are valid. The foo<int>() will have a local something of type ct* and thus call do_something_else() (yes, it's undefined because it's through an uninitialized pointer, but given that ct::bar() doesn't access the this pointer, this is what will happen in typical systems). The foo<float>() will only have a global something of type stru, and thanks to the operator ->, it will call stru::bar() and thus do_something().
Yes, contrived, but it shows that the ambiguity is real and potentially dangerous.