When using templates, sometimes you need to write typename
for types – when and why to do it?
template & lt; class T & gt;
inline void PRINT_ELEMENTS (const T & amp; coll, const char * optcstr = "")
{
typename T :: const_iterator pos;
std :: cout & lt; & lt; optcstr;
for (pos = coll.begin (); pos! = coll.end (); ++ pos) {
std :: cout & lt; & lt; * pos & lt; & lt; '';
}
std :: cout & lt; & lt; std :: endl;
}
Answer 1, authority 100%
The problem is that T :: const_iterator
is a dependent name: it depends on the T
template parameter. At this point, the compiler does not know what T
will be, and cannot predict whether T :: const_iterator
will be a type name or, for example, the name of a static field or a template in general. Therefore, he does not try to guess, and assumes that this is a field.
If prompted, he will assume that T :: const_iterator
is a type, and understand that
typename T :: const_iterator pos;
– variable declaration.
Why can’t the compiler wait to find out the meaning of the expression T :: const_iterator
until the type of T
is already known (that is, until the template is expanded with specific type T
)? And here’s why: at the time the template is applied, the type T
has the right to be undefined! And it can also depend on the template itself. So it is impossible to postpone the clarification of the meaning of the expression. Example:
template & lt; class T & gt;
class comparable & lt; T & gt;
{
bool compare (T & amp; other) {return this == other; }
};
class length: public comparable & lt; length & gt; // at this point comparable
// the type of T is not fully known yet!
{
...
An example of code illustrating the slippery points is shown below. It does not compile with gcc (since there is no standard typename
), but the more liberal MSVC 2012 compiles and runs it.
typename
is there to avoid such surprises.
# include "stdafx.h" // needed for MSVC
#include & lt; iostream & gt;
using namespace std;
template & lt; class T & gt;
struct A
{
void f ()
{
// if T :: iterator is a type, this is a provisional function declaration
// if T :: iterator is a number, this is a variable declaration with initialization
int x (T :: iterator);
}
void g ()
{
int x = 5;
{
// if T :: iterator is a template that takes a numeric argument,
// this is the instantiation of the template into the variable x, overlapping x
// behind curly braces
// if T :: iterator is an instance of a class with an overloaded & lt; operator,
// this is comparing T :: iterator with zero and then comparing the result
// with the value of the variable x!
T :: iterator & lt; 0 & gt; x;
// Someone still doubts that C++ is an unpredictable language?
}
}
};
struct T1
{
typedef int iterator;
};
struct T2
{
static const int iterator = 5;
};
struct T3
{
template & lt; int C & gt; struct iterator
{
iterator () {cout & lt; & lt; "constructing template with C =" & lt; & lt; C & lt; & lt; endl; }
};
};
struct T4
{
struct Titerator
{
Titerator operator & lt; (int value)
{
cout & lt; & lt; "in operator & lt;" & lt; & lt; value & lt; & lt; endl;
return Titerator ();
}
bool operator & gt; (int value)
{
cout & lt; & lt; "in operator & gt;" & lt; & lt; value & lt; & lt; endl;
return false;
}
};
static Titerator iterator;
};
T4 :: Titerator T4 :: iterator = T4 :: Titerator ();
int main (int argc, char * argv [])
{
A & lt; T1 & gt; a1; a1.f ();
A & lt; T2 & gt; a2; a2.f ();
A & lt; T3 & gt; a3; a3.g ();
A & lt; T4 & gt; a4; a4.g ();
return 0;
}
The result is as follows:
constructing template with C = 0
in operator & lt; 0
in operator & gt; 5