used[newval] = true;
return newval;
}
#endif // URAND_H ///:~
The uniqueness of Urand is produced by tracking with a bitset all the numbers possible in the random space (the upper bound is set with the template argument) and by recording each number as it’s used by setting the corresponding position bit in used. When the numbers are all used up, the bitset is cleared to start over. Here’s a simple driver that illustrates how to use a Urand object:.
//: C05:UrandTest.cpp
//{-bor}
#include <iostream>
#include "Urand.h"
using namespace std;
int main() {
Urand<10> u;
for(int i = 0; i < 20; ++i)
cout << u() << ' ';
} ///:~
As we explain later in this chapter, non-type template arguments are also important in the optimization of numeric computations.
Default template arguments
You can provide default arguments for template parameters in class templates. (They are not allowed in function templates.) As with default function arguments, they should only be defined once, the first time a template declaration or definition is seen by the compiler; and once you introduce a default argument, all the subsequent template parameters must also have defaults. To make the fixed-size Stack template shown earlier a little friendlier, for example, you can add a default argument like this:.
template<class T, size_t N = 100>
class Stack {
T data[N]; // Fixed capacity is N
size_t count;
public:
void push(const T& t);
// etc.
};
Now, if you omit the second template argument when declaring a Stack object, the value for N will default to 100.
You can choose to provide defaults for all arguments, but you must use an empty set of brackets when declaring an instance so that the compiler knows that a class template is involved. Here’s how:
template<class T = int, size_t N = 100> // Both defaulted
class Stack {
T data[N]; // Fixed capacity is N
size_t count;
public:
void push(const T& t);
// etc.
};
Stack<> myStack; // Same as Stack<int, 100>.
Default arguments are used heavily in the standard C++ library. The vector class template, for instance, is declared as follows:
template <class T, class Allocator = allocator<T> >
class vector;
Note the space between the last two right angle bracket characters. This prevents the compiler from interpreting those two characters (>>) as the right-shift operator.
This declaration reveals that vector actually takes two arguments: the type of the contained objects it holds, and a type that represents the allocator used by the vector. (We talk more about allocators in Chapter 7.) Whenever you omit the second argument, the standard allocator template is used, parameterized by the first template parameter. This declaration also shows that you can use template parameters in other, subsequent template parameters, as T is used here.
Although you cannot use default template arguments in function templates, you can use template parameters as default arguments to normal functions. The following function template adds the elements in a sequence.
//: C05:FuncDef.cpp
#include <iostream>
using namespace std;
template<class T>
T sum(T* b, T* e, T init = T()) {
while(b != e)
init += *b++;
return init;
}
int main() {
int a[] = {1,2,3};
cout << sum(a, a+sizeof a / sizeof a[0]) << endl; // 6
} ///:~
The third argument to sum( ) is the initial value for the accumulation of the elements. Since we omitted it, this argument defaults to T( ), which in the case of int and other built-in types invokes a pseudo-constructor that performs zero-initialization.
Template template parameters
The third type of parameter a template can accept is another class template. This may sound strange, since templates are types, and type parameters are already allowed, but if you are going to use a template type parameter 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];
}
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> theData;
theData.append(1);
theData.append(2);
int* p = theData.begin();
while(p != theData.end())
cout << *p++ << endl;
} ///:~
The Array class template is a trivial sequence container. The Container template takes two parameters: the type of the objects it is to hold, 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:.