public:
void f() {
cout << "U* used\n";
}
};
template<class T, class U> class C<T*, U*> {
public:
void f() {
cout << "T* and U* used\n";
}
};
template<class T> class C<T, T> {
public:
void f() {
cout << "T == U\n";
}
};
int main() {
C<float, int>().f(); // 1: Primary template
C<int, float>().f(); // 2: T == int
C<float, double>().f(); // 3: U == double
C<float, float>().f(); // 4: T == U
C<float*, float>().f(); // 5: T* used [T is float]
C<float, float*>().f(); // 6: U* used [U is float]
C<float*, int*>().f(); // 7: T* and U* used [float,int]
// The following are ambiguous:
// 8: C<int, int>().f();
// 9: C<double, double>().f();
// 10: C<float*, float*>().f();
// 11: C<int, int*>().f();
// 12: C<int*, int*>().f();
} ///:~
As you can see, you can partially specify template parameters according to whether they are pointer types, or whether they are equal. When the T* specialization is used, such as is the case in line 5, T itself is not the top-level pointer type that was passed—it is the type that the pointer refers to (float, in this case). The T* specification is a pattern to allow matching against pointer types. If you were to use int** as the first template argument, T would be int*. Line 8 is ambiguous because having the first parameter as an int vs. having the two parameters equal are independent issues—one is not more specialized than the other. Similar logic applies to lines 9 through 12.
A practical example
You can easily derive from a class template, and you can create a new template that instantiates and inherits from an existing template. If the vector template does most everything you want, for example, but in a certain application you’d also like a version that can sort itself, you can easily reuse the vector code. The following example derives from vector<T> and adds sorting.
//: C05:Sorted.h
// Template specialization
#ifndef SORTED_H
#define SORTED_H
#include <string>
#include <vector>
template<class T>
class Sorted : public std::vector<T> {
public:
void sort();
};
template<class T>
void Sorted<T>::sort() { // A simple sort
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(at(j-1) > at(j)) {
T t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
// Partial specialization for pointers:
template<class T>
class Sorted<T*> : public std::vector<T*> {
public:
void sort();
};
template<class T>
void Sorted<T*>::sort() {
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(*at(j-1) > *at(j)) {
T* t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
// Full specialization for char*
// (Made inline here for convenience –
// normally would place function body in separate file
// and only leave declaration here)
template<>
inline void Sorted<char*>::sort() {
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(std::strcmp(at(j-1), at(j)) > 0) {
char* t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
#endif // SORTED_H ///:~
The Sorted template imposes a restriction on all but one of the classes for which it is instantiated: they must contain a > operator. It works correctly only with non-pointer objects (including objects of built-in types). The full specialization compares the elements using strcmp( ) to sort vectors of char* according to the null-terminated strings to which they refer.
Here’s a driver for Sorted.h that uses the randomizer introduced earlier in the chapter:.
//: C05:Sorted.cpp
//{bor} (because of bitset in Urand.h)
// Testing template specialization
#include <cstddef>
#include <iostream>
#include "Sorted.h"
#include "Urand.h"
using namespace std;
#define asz(a) (sizeof a / sizeof a[0])
char* words[] = {
"is", "running", "big", "dog", "a",
};
char* words2[] = {
"this", "that", "theother",
};
int main() {
Sorted<int> is;
Urand<47> rand;
for(size_t i = 0; i < 15; i++)
is.push_back(rand());
for(size_t i = 0; i < is.size(); i++)
cout << is[i] << ' ';
cout << endl;
is.sort();
for(size_t i = 0; i < is.size(); i++)
cout << is[i] << ' ';
cout << endl;
// Uses the template partial specialization:
Sorted<string*> ss;
for(size_t i = 0; i < asz(words); i++)
ss.push_back(new string(words[i]));
for(size_t i = 0; i < ss.size(); i++)
cout << *ss[i] << ' ';
cout << endl;
ss.sort();
for(size_t i = 0; i < ss.size(); i++) {
cout << *ss[i] << ' ';
delete ss[i];
}
cout << endl;
// Uses the full char* specialization:
Sorted<char*> scp;
for(size_t i = 0; i < asz(words2); i++)
scp.push_back(words2[i]);
for(size_t i = 0; i < scp.size(); i++)
cout << scp[i] << ' ';
cout << endl;
scp.sort();
for(size_t i = 0; i < scp.size(); i++)
cout << scp[i] << ' ';
cout << endl;
} ///:~
Each of the template instantiations above uses a different version of the template. Sorted<int> uses the primary template. Sorted<string*> uses the partial specialization for pointers. Last, Sorted<char*> uses the full specialization for char*. Without this full specialization, you could be fooled into thinking that things were working correctly because the words array would still sort out to "a big dog is running" since the partial specialization would end up comparing the first character of each array. However, words2 would not sort correctly.