Template Fun
During training for my new job, we had a session on using the Standard Template Library in C++. I was quite familiar with the containers portion of the STL, but I found it a nice opportunity to get to know the algorithms portion. At one point the person instructing us gave an exercise intended to illustrate the usage of the transform and copy algorithms as well as ptr_fun, bind1st, and binf2nd. The exercise was easy enough, but what caught my interest while planning my solution was the initialization step; our instructions called for starting with a vector of the first 10 integers. Everyone else just whole an explicit loop to initially fill their vector, but that didn't seem to me to be quite in the spirit of using the algorithms library.
The STL provided fill algorithm makes it easy to fill a container with the same value in repetition, but what we were doing really begged the use of the generate algorithm, which uses values provided by a function or a function object when filling. All that is required something to supply the values, and generate happily takes care of things. The choice, then, is how to implement a source of numbers. The simplest way is just to use a function and global variable, which can be done in two lines without sacrificing legibility:
int currentNum=1;
int numbers() { return currentNum++; }
I don't like this way much, though, because of the global variable that gets just dumped out into the program's namespace. One could encapsulate it better by making it a static variable of the function, but when you get to that point, why not just use an object? The whole point of objects is that they encapsulate state and allow it to be accessed and altered in a controlled manner. Function objects are a beautiful result of the flexibility of C++, namely operator overloading1. Also, while I could have easily implemented a function object that would just produce the natural numbers, I figured, why stop there? So here's my solution, templated to handle any type that can be added and assigned2, and for sequences with any starting point and constant increment:
template <class T>
class SequenceGenerator{
public:
SequenceGenerator(const T &s, const T &i){
start = s;
current = start;
increment = i;
}
T operator() (){
T temp=current;
current = current + increment;
return(temp);
}
T operator++ (){
current = current + increment;
return(current);
}
T operator++ (int){
T temp=current;
current = current + increment;
return(temp);
}
void reset(){
current=start;
}
T value(){
return(current);
}
private:
T start;
T increment;
T current;
};
Using this class, filling a vector v1 with the first ten natural numbers is as easy as:
SequenceGenerator<int> numbers(1,1);
generate_n(std::back_inserter(v1),10, numbers);
What I love about this is that although the SequenceGenerator is rather lengthier than the simple function solution, it's totally general. If I want to I can reuse this same code without modification to generate a sequence of points in space, assuming I have a class to represent points, and all I have to change is my code calling the SequenceGenerator. Templating is a very powerful tool, and it allows one to do so much more than create generic containers.
-
Anyone who claims that operator overloading is bad should be sentenced to implementing and then using a complex number class in Java. Sure, a programmer can abuse operator overloading, but there are always plenty of other ways to write bad code, while properly used operator overloading allows for some truly elegant code that isn't otherwise possible. ↩
-
This is the very sort of thing that operator overloading makes beautifully simple. ↩