Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Consider the following:
//: C05:TypenamedID.cpp {-bor}
// Uses 'typename' as a prefix for nested types.
template<class T> class X {
// Without typename, you should get an error:
typename T::id i;
public:
void f() { i.g(); }
};
class Y {
public:
class id {
public:
void g() {}
};
};
int main() {
X<Y> xy;
xy.f();
} ///:~
The template definition assumes that the class T that
you hand it must have a nested identifier of some kind called id. Yet id
could also be a static data member of T, in which case you can perform
operations on id directly, but you can t create an object of the type
id. In this example, the identifier id is being treated as if it
were a nested type inside T. In the case of class Y, id is
in fact a nested type, but (without the typename keyword) the compiler
can t know that when it s compiling X.
If the compiler has the option of treating an identifier as
a type or as something other than a type when it sees an identifier in a
template, it will assume that the identifier refers to something other than a
type. That is, it will assume that the identifier refers to an object
(including variables of primitive types), an enumeration, or something similar.
However, it will not cannot just assume that it is a type.
Because the default behavior of the compiler is to assume that
a name that fits the above two points is not a type, you must use typename
for nested names (except in constructor initializer lists, where it is neither
needed nor allowed). In the above example, when the compiler sees typename
T::id, it knows (because of the typename keyword) that id
refers to a nested type and thus it can create an object of that type.
The short version of the rule is: if a type referred to
inside template code is qualified by a template type parameter, you must use
the typename keyword as a prefix, unless it appears in a base class
specification or initializer list in the same scope (in which case you must
not).
The above explains the use of the typename keyword in
the program TempTemp4.cpp. Without it, the compiler would assume that
the expression Seq<T>::iterator is not a type, but we were using
it to define the return type of the begin( ) and end( )
member functions.
The following example, which defines a function template
that can print any Standard C++ sequence, shows a similar use of typename:
//: C05:PrintSeq.cpp {-msc}{-mwcc}
// A print function for Standard C++ sequences.
#include <iostream>
#include <list>
#include <memory>
#include <vector>
using namespace std;
template<class T, template<class U, class =
allocator<U> >
class Seq>
void printSeq(Seq<T>& seq) {
for(typename Seq<T>::iterator b = seq.begin();
b != seq.end();)
cout << *b++ << endl;
}
int main() {
// Process a vector
vector<int> v;
v.push_back(1);
v.push_back(2);
printSeq(v);
// Process a list
list<int> lst;
lst.push_back(3);
lst.push_back(4);
printSeq(lst);
} ///:~
Once again, without the typename keyword the compiler
will interpret iterator as a static data member of Seq<T>,
which is a syntax error, since a type is required.
Typedefing a typename
It s important not to assume that the typename
keyword creates a new type name. It doesn t. Its purpose is to inform the
compiler that the qualified identifier is to be interpreted as a type. A line that
reads:
typename Seq<T>::iterator It;
causes a variable named It to be declared of type Seq<T>::iterator.
If you mean to create a new type name, you should use typedef, as usual,
as in:
typedef typename Seq<It>::iterator It;
Using typename instead of class
Another role of the typename keyword is to provide
you the option of using typename instead of class in the template
argument list of a template definition:
//: C05:UsingTypename.cpp
// Using 'typename' in the template argument list.
template<typename T> class X {};
int main() {
X<int> x;
} ///:~
To some, this produces clearer code.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |