void apply(Seq& sq, R(T::*f)(A1, A2),
A1 a1, A2 a2) {
typename Seq::iterator it = sq.begin();
while(it != sq.end()) {
((*it)->*f)(a1, a2);
it++;
}
}
// Etc., to handle maximum likely arguments ///:~
The apply( ) function template takes a reference to the container class and a pointer-to-member for a member function of the objects contained in the class. It uses an iterator to move through the Stack and apply the function to every object.
Notice that there are no STL header files (or any header files, for that matter) included in applySequence.h, so it is actually not limited to use with an STL container. However, it does make assumptions (primarily, the name and behavior of the iterator) that apply to STL sequences.
You can see there is more than one version of apply( ), further illustrating overloading of function templates. Although these templates allow any type of return value (which is ignored, but the type information is required to match the pointer-to-member), each version takes a different number of arguments, and because it’s a template, those arguments can be of any type. The only limitation here is that there’s no "super template" to create templates for you; you must decide how many arguments will ever be required.
To test the various overloaded versions of apply( ), the class Gromit[57] is created containing functions with different numbers of arguments:.
//: C05:Gromit.h
// The techno-dog. Has member functions
// with various numbers of arguments.
#include <iostream>
class Gromit {
int arf;
public:
Gromit(int arf = 1) : arf(arf + 1) {}
void speak(int) {
for(int i = 0; i < arf; i++)
std::cout << "arf! ";
std::cout << std::endl;
}
char eat(float) {
std::cout << "chomp!" << std::endl;
return 'z';
}
int sleep(char, double) {
std::cout << "zzz..." << std::endl;
return 0;
}
void sit() {
std::cout << " Sitting...)" << std::endl;
}
}; ///:~
Now you can use the apply( ) template functions to apply the Gromit member functions to a vector<Gromit*>, like this:.
//: C05:ApplyGromit.cpp
// Test ApplySequence.h
#include <cstddef>
#include <iostream>
#include <vector>
#include "ApplySequence.h"
#include "Gromit.h"
using namespace std;
int main() {
vector<Gromit*> dogs;
for(size_t i = 0; i < 5; i++)
dogs.push_back(new Gromit(i));
apply(dogs, &Gromit::speak, 1);
apply(dogs, &Gromit::eat, 2.0f);
apply(dogs, &Gromit::sleep, 'z', 3.0);
apply(dogs, &Gromit::sit);
for (size_t i = 0; i < dogs.size(); ++i)
delete dogs[i];
} ///:~
Although the definition of apply( ) is somewhat complex and not something you’d ever expect a novice to understand, its use is remarkably clean and simple, and a novice could easily use it knowing only what it is intended to accomplish, not how. This is the type of division you should strive for in all your program components: The tough details are all isolated on the designer’s side of the wall. Users are concerned only with accomplishing their goals and don’t see, know about, or depend on details of the underlying implementation. We’ll explore even more flexible ways to apply functions to sequences in the next chapter.
Partial ordering of function templates
We mentioned earlier that an ordinary function overload of min( ) is preferable to using the template. If a function already exists to match a function call, why generate another? In the absence of ordinary functions, however, it is possible that overloaded function templates can lead to ambiguities. To minimize the chances of this, an ordering is defined for function templates that chooses the most specialized template, if such exists. A function template is considered more specialized than another if every possible list of arguments that matches it also matches the other, but not the other way around. Consider the following function template declarations, taken from an example in the C++ standard document:.
template<class T> void f(T);
template<class T> void f(T*);
template<class T> void f(const T*);
The first template can be matched with any type. The second template is more specialized than the first because only pointer types match it. In other words, you can look upon the set of possible calls that match the second template as a subset of the first. A similar relationship exists between the second and third template declarations above: the third can only be called for pointers to const, but the second accommodates any pointer type. The following program illustrates these rules.
//: C05:PartialOrder.cpp
// Reveals Ordering of Function Templates
#include <iostream>
using namespace std;
template<class T>
void f(T) {
cout << "T\n";
}
template<class T>
void f(T*) {
cout << "T*\n";
}
template<class T>
void f(const T*) {
cout << "const T*\n";
}
int main() {
f(0); // T
int i = 0;
f(&i); // T*
const int j = 0;
f(&j); // const T*
} ///:~
The call f(&i) certainly matches the first template, but since the second is more specialized, it is called. The third can’t be called in this case since the pointer is not a pointer to const. The call f(&j) matches all three templates (for example, T would be const int in the second template), but again, the third template is more specialized, so it is used instead.
If there is no "most specialized" template among a set of overloaded function templates, an ambiguity remains and the compiler will report an error. That is why this feature is called a "partial ordering"—it may not be able to resolve all possibilities. Similar rules exist for class templates (see the section "Partial specialization" below).
Template specialization
The term specialization has a specific, template-related meaning in C++. A template definition is, by its very nature, a generalization, because it describes a family of functions or classes in general terms. When template arguments are supplied, the result is a specialization of the template, because it fixes a unique instance out of the many possible instances of the family of functions or classes. The min function template seen at the beginning of this chapter is a generalization of a minimum-finding function, because the type of its parameters is not specified. When you supply the type for the template parameter, whether explicitly or implicitly via argument deduction, the resultant code generated by the compiler (for example, min<int>) is a specialization of the template. The code generated is also considered an instantiation of the template, of course, as are all code bodies generated by the template facility.