Both the definition of seq in Container and theData in main( ) use the default. The only way to use something other than the default value is as the previous program (TempTemp2.cpp) illustrated. This is the only exception to the rule stated earlier that default arguments should appear only once in a compilation unit.
Since the standard sequence containers (vector, list, and deque, discussed in depth in Chapter 7) have a default allocator argument, the technique shown above is helpful should you ever want to pass one of these sequences as a template parameter. The following program passes a vector and then a list to two instances of Container.
//: C05:TempTemp4.cpp
//{-bor}
//{-msc}
// Passes standard sequences as template arguments
#include
#include
#include
#include
using namespace std;
template
class Seq>
class Container {
Seq
public:
void push_back(const T& t) { seq.push_back(t); }
typename Seq
typename Seq
};
int main() {
// Use a vector
Container
theData.push_back(1);
theData.push_back(2);
for(vector
p != theData.end(); ++p) {
cout << *p << endl;
}
// Use a list
Container
theOtherData.push_back(3);
theOtherData.push_back(4);
for(list
p2 != theOtherData.end(); ++p2) {
cout << *p2 << endl;
}
} ///:~
In this case we name the first parameter of the inner template Seq (with the name U), because the allocators in the standard sequences must themselves be parameterized with the same type as the contained objects in the sequence. Also, since the default allocator parameter is known, we can omit it in the subsequent references to Seq
The typename keyword
Consider the following:.
//: C05:TypenamedID.cpp
//{-bor}
// Uses 'typename' as a prefix for nested types
template
// 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
xy.f();
} ///:~
The template definition assumes that the class T that you hand it must have a nested identifier of some kind called id. But 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.".
However, that’s exactly what is happening here: the identifier id is being treated as if it were actually 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 template 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, it should be preceded by the typename keyword, unless it appears in a base class specification or initializer list in the same scope (in which case you must not).
All the above explains the use of the typename keyword in the program TempTemp4.cpp. Without it, the compiler would assume that the expression Seq
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}
// A print function for standard C++ sequences
#include
#include
#include
#include
using namespace std;
template
class Seq>
void printSeq(Seq
for (typename Seq
b != seq.end();)
cout << *b++ << endl;
}
int main() {