Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Template template parameters

The third type of parameter a template can accept is another class template. If you are going to use a template type parameter itself as a template in your code, the compiler needs to know that the parameter is a template in the first place. The following example illustrates a template template parameter:

//: C05:TempTemp.cpp
// Illustrates a template template parameter.
#include <cstddef>
#include <iostream>
using namespace std;
 
template<class T>
class Array { // A simple, expandable sequence
enum { INIT = 10 };
T* data;
size_t capacity;
size_t count;
public:
Array() {
count = 0;
data = new T[capacity = INIT];
}
~Array() { delete [] data; }
void push_back(const T& t) {
if(count == capacity) {
// Grow underlying array
size_t newCap = 2 * capacity;
T* newData = new T[newCap];
for(size_t i = 0; i < count; ++i)
newData[i] = data[i];
delete [] data;
data = newData;
capacity = newCap;
}
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() { return data; }
T* end() { return data + count; }
};
 
template<class T, template<class> class Seq>
class Container {
Seq<T> seq;
public:
void append(const T& t) { seq.push_back(t); }
T* begin() { return seq.begin(); }
T* end() { return seq.end(); }
};
 
int main() {
Container<int, Array> container;
container.append(1);
container.append(2);
int* p = container.begin();
while(p != container.end())
cout << *p++ << endl;
} ///:~
 

The Array class template is a trivial sequence container. The Container template takes two parameters: the type that it holds, and a sequence data structure to do the holding. The following line in the implementation of the Container class requires that we inform the compiler that Seq is a template:

Seq<T> seq;
 

If we hadn t declared Seq to be a template template parameter, the compiler would complain here that Seq is not a template, since we re using it as such. In main( ) a Container is instantiated to use an Array to hold integers, so Seq stands for Array in this example.

Note that it is not necessary in this case to name the parameter for Seq inside Container s declaration. The line in question is:

template<class T, template<class> class Seq>
 

Although we could have written

template<class T, template<class U> class Seq>
 

the parameter U is not needed anywhere. All that matters is that Seq is a class template that takes a single type parameter. This is analogous to omitting the names of function parameters when they re not needed, such as when you overload the post-increment operator:

T operator++(int);
 

The int here is merely a placeholder and so needs no name.

The following program uses a fixed-size array, which has an extra template parameter representing the array length:

//: C05:TempTemp2.cpp
// A multi-variate template template parameter.
#include <cstddef>
#include <iostream>
using namespace std;
 
template<class T, size_t N> class Array {
T data[N];
size_t count;
public:
Array() { count = 0; }
void push_back(const T& t) {
if(count < N)
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() { return data; }
T* end() { return data + count; }
};
 
template<class T,size_t N,template<class,size_t> class Seq>
class Container {
Seq<T,N> seq;
public:
void append(const T& t) { seq.push_back(t); }
T* begin() { return seq.begin(); }
T* end() { return seq.end(); }
};
 
int main() {
const size_t N = 10;
Container<int, N, Array> container;
container.append(1);
container.append(2);
int* p = container.begin();
while(p != container.end())
cout << *p++ << endl;
} ///:~
 

Once again, parameter names are not needed in the declaration of Seq inside Container s declaration, but we need two parameters to declare the data member seq, hence the appearance of the non-type parameter N at the top level.

Combining default arguments with template template parameters is slightly more problematic. When the compiler looks at the inner parameters of a template template parameter, default arguments are not considered, so you have to repeat the defaults in order to get an exact match. The following example uses a default argument for the fixed-size Array template and shows how to accommodate this quirk in the language:

//: C05:TempTemp3.cpp {-bor}{-msc}
// Template template parameters and default arguments.
#include <cstddef>
#include <iostream>
using namespace std;
 
template<class T, size_t N = 10> // A default argument
class Array {
T data[N];
size_t count;
public:
Array() { count = 0; }
void push_back(const T& t) {
if(count < N)
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() { return data; }
T* end() { return data + count; }
};
 
template<class T, template<class, size_t = 10> class Seq>
class Container {
Seq<T> seq; // Default used
public:
void append(const T& t) { seq.push_back(t); }
T* begin() { return seq.begin(); }
T* end() { return seq.end(); }
};
 
int main() {
Container<int, Array> container;
container.append(1);
container.append(2);
int* p = container.begin();
while(p != container.end())
cout << *p++ << endl;
} ///:~
 

The default dimension of 10 is required in the line:

template<class T, template<class, size_t = 10> class Seq>
 

Both the definition of seq in Container and container in main( ) use the default. The only way to use something other than the default value was shown in TempTemp2.cpp. 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 <iostream>
#include <list>
#include <memory> // Declares allocator<T>
#include <vector>
using namespace std;
 
template<class T, template<class U, class = allocator<U> >
class Seq>
class Container {
Seq<T> seq; // Default of allocator<T> applied implicitly
public:
void push_back(const T& t) { seq.push_back(t); }
typename Seq<T>::iterator begin() { return seq.begin(); }
typename Seq<T>::iterator end() { return seq.end(); }
};
 
int main() {
// Use a vector
Container<int, vector> vContainer;
vContainer.push_back(1);
vContainer.push_back(2);
for(vector<int>::iterator p = vContainer.begin();
p != vContainer.end(); ++p) {
cout << *p << endl;
}
// Use a list
Container<int, list> lContainer;
lContainer.push_back(3);
lContainer.push_back(4);
for(list<int>::iterator p2 = lContainer.begin();
p2 != lContainer.end(); ++p2) {
cout << *p2 << endl;
}
} ///:~
 

Here 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<T>, as we did in the previous program. To fully explain this example, however, we have to discuss the semantics of the typename keyword.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire