Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
The following FunctionObjects.cpp example provides
simple tests for most of the built-in basic function object templates. This
way, you can see how to use each template, along with the resulting behavior.
This example uses one of the following generators for convenience:
//: C06:Generators.h
// Different ways to fill sequences.
#ifndef GENERATORS_H
#define GENERATORS_H
#include <cstring>
#include <set>
#include <cstdlib>
// A generator that can skip over numbers:
class SkipGen {
int i;
int skp;
public:
SkipGen(int start = 0, int skip = 1)
: i(start), skp(skip) {}
int operator()() {
int r = i;
i += skp;
return r;
}
};
// Generate unique random numbers from 0 to mod:
class URandGen {
std::set<int> used;
int limit;
public:
URandGen(int lim) : limit(lim) {}
int operator()() {
while(true) {
int i = int(std::rand()) % limit;
if(used.find(i) == used.end()) {
used.insert(i);
return i;
}
}
}
};
// Produces random characters:
class CharGen {
static const char* source;
static const int len;
public:
char operator()() {
return source[std::rand() % len];
}
};
#endif // GENERATORS_H ///:~
//: C06:Generators.cpp {O}
#include "Generators.h"
const char* CharGen::source = "ABCDEFGHIJK"
"LMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const int CharGen::len = std::strlen(source);
///:~
We ll be using these generating functions in various
examples throughout this chapter. The SkipGen function object returns
the next number of an arithmetic sequence whose common difference is held in
its skp data member. A URandGen object generates a unique random
number in a specified range. (It uses a set container, which we ll
discuss in the next chapter.) A CharGen object returns a random
alphabetic character. Here is a sample program using UrandGen:
//: C06:FunctionObjects.cpp {-bor}
// Illustrates selected predefined function object
// templates from the Standard C++ library.
//{L} Generators
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
#include "Generators.h"
#include "PrintSequence.h"
using namespace std;
template<typename Contain, typename UnaryFunc>
void testUnary(Contain& source, Contain& dest,
UnaryFunc f) {
transform(source.begin(), source.end(), dest.begin(),
f);
}
template<typename Contain1, typename Contain2,
typename BinaryFunc>
void testBinary(Contain1& src1, Contain1& src2,
Contain2& dest, BinaryFunc f) {
transform(src1.begin(), src1.end(),
src2.begin(), dest.begin(), f);
}
// Executes the expression, then stringizes the
// expression into the print statement:
#define T(EXPR) EXPR; print(r.begin(), r.end(), \
"After " #EXPR);
// For Boolean tests:
#define B(EXPR) EXPR; print(br.begin(), br.end(), \
"After " #EXPR);
// Boolean random generator:
struct BRand {
bool operator()() { return rand() % 2 == 0; }
};
int main() {
const int SZ = 10;
const int MAX = 50;
vector<int> x(SZ), y(SZ), r(SZ);
// An integer random number generator:
URandGen urg(MAX);
srand(time(0)); // Randomize
generate_n(x.begin(), SZ, urg);
generate_n(y.begin(), SZ, urg);
// Add one to each to guarantee nonzero divide:
transform(y.begin(), y.end(), y.begin(),
bind2nd(plus<int>(), 1));
// Guarantee one pair of elements is ==:
x[0] = y[0];
print(x.begin(), x.end(), "x");
print(y.begin(), y.end(), "y");
// Operate on each element pair of x & y,
// putting the result into r:
T(testBinary(x, y, r, plus<int>()));
T(testBinary(x, y, r, minus<int>()));
T(testBinary(x, y, r, multiplies<int>()));
T(testBinary(x, y, r, divides<int>()));
T(testBinary(x, y, r, modulus<int>()));
T(testUnary(x, r, negate<int>()));
vector<bool> br(SZ); // For Boolean results
B(testBinary(x, y, br, equal_to<int>()));
B(testBinary(x, y, br, not_equal_to<int>()));
B(testBinary(x, y, br, greater<int>()));
B(testBinary(x, y, br, less<int>()));
B(testBinary(x, y, br, greater_equal<int>()));
B(testBinary(x, y, br, less_equal<int>()));
B(testBinary(x, y, br,
not2(greater_equal<int>())));
B(testBinary(x,y,br,not2(less_equal<int>())));
vector<bool> b1(SZ), b2(SZ);
generate_n(b1.begin(), SZ, BRand());
generate_n(b2.begin(), SZ, BRand());
print(b1.begin(), b1.end(), "b1");
print(b2.begin(), b2.end(), "b2");
B(testBinary(b1, b2, br, logical_and<int>()));
B(testBinary(b1, b2, br, logical_or<int>()));
B(testUnary(b1, br, logical_not<int>()));
B(testUnary(b1, br, not1(logical_not<int>())));
} ///:~
This example uses a handy function template, print( ),
which is capable of printing a sequence of any type along with an optional
message. This template appears in the header file PrintSequence.h, and
is explained later in this chapter.
The two template functions automate the process of testing
the various function object templates. There are two because the function
objects are either unary or binary. The testUnary( ) function takes
a source vector, a destination vector, and a unary function
object to apply to the source vector to produce the destination vector.
In testBinary( ), two source vectors are fed to a binary
function to produce the destination vector. In both cases, the template
functions simply turn around and call the transform( ) algorithm,
which applies the unary function or function object found in its fourth
parameter to each sequence element, writing the result to the sequence indicated
by its third parameter, which in this case is the same as the input sequence.
For each test, you want to see a string describing the test,
followed by the results of the test. To automate this, the preprocessor comes
in handy; the T( ) and B( ) macros each take the
expression you want to execute. After evaluating the expression, they pass the
appropriate range to print( ). To produce the message the
expression is stringized using the preprocessor. That way you see the code of
the expression that is executed followed by the result vector.
The last little tool, BRand, is a generator object
that creates random bool values. To do this, it gets a random number
from rand( ) and tests to see if it s greater than (RAND_MAX+1)/2.
If the random numbers are evenly distributed, this should happen half the time.
In main( ), three vectors of int
are created: x and y for source values, and r for results.
To initialize x and y with random values no greater than 50, a
generator of type URandGen from Generators.h is used. The
standard generate_n( ) algorithm populates the sequence specified
in its first argument by invoking its third argument (which must be a
generator) a given number of times (specified in its second argument). Since
there is one operation where elements of x are divided by elements of y,
we must ensure that there are no zero values of y. This is accomplished
by once again using the transform( ) algorithm, taking the source values from y and putting the results back into y. The function
object for this is created with the expression:
This expression uses the plus function object to add
1 to its first argument. As we did earlier in this chapter, we use a binder adaptor
to make this a unary function so it can applied to the sequence by a single
call to transform( ).
Another test in the program compares the elements in the two
vectors for equality, so it is interesting to guarantee that at least
one pair of elements is equivalent; here element zero is chosen.
Once the two vectors are printed, T( )
tests each of the function objects that produces a numeric value, and then B( )
tests each function object that produces a Boolean result. The result is placed
into a vector<bool>, and when this vector is printed, it
produces a 1 for a true value and a 0 for a false value. Here
is the output from an execution of FunctionObjects.cpp:
x:
4 8 18 36 22 6 29 19 25 47
y:
4 14 23 9 11 32 13 15 44 30
After testBinary(x, y, r, plus<int>()):
8 22 41 45 33 38 42 34 69 77
After testBinary(x, y, r, minus<int>()):
0 -6 -5 27 11 -26 16 4 -19 17
After testBinary(x, y, r, multiplies<int>()):
16 112 414 324 242 192 377 285 1100 1410
After testBinary(x, y, r, divides<int>()):
1 0 0 4 2 0 2 1 0 1
After testBinary(x, y, r, limit<int>()):
0 8 18 0 0 6 3 4 25 17
After testUnary(x, r, negate<int>()):
-4 -8 -18 -36 -22 -6 -29 -19 -25 -47
After testBinary(x, y, br, equal_to<int>()):
1 0 0 0 0 0 0 0 0 0
After testBinary(x, y, br, not_equal_to<int>()):
0 1 1 1 1 1 1 1 1 1
After testBinary(x, y, br, greater<int>()):
0 0 0 1 1 0 1 1 0 1
After testBinary(x, y, br, less<int>()):
0 1 1 0 0 1 0 0 1 0
After testBinary(x, y, br, greater_equal<int>()):
1 0 0 1 1 0 1 1 0 1
After testBinary(x, y, br, less_equal<int>()):
1 1 1 0 0 1 0 0 1 0
After testBinary(x, y, br,
not2(greater_equal<int>())):
0 1 1 0 0 1 0 0 1 0
After testBinary(x,y,br,not2(less_equal<int>())):
0 0 0 1 1 0 1 1 0 1
b1:
0 1 1 0 0 0 1 0 1 1
b2:
0 1 1 0 0 0 1 0 1 1
After testBinary(b1, b2, br, logical_and<int>()):
0 1 1 0 0 0 1 0 1 1
After testBinary(b1, b2, br, logical_or<int>()):
0 1 1 0 0 0 1 0 1 1
After testUnary(b1, br, logical_not<int>()):
1 0 0 1 1 1 0 1 0 0
After testUnary(b1, br,
not1(logical_not<int>())):
0 1 1 0 0 0 1 0 1 1
If you want the Boolean values to display as true and
false instead of 1 and 0, call cout.setf(ios::boolalpha).
A binder doesn t have to produce a unary predicate;
it can also create any unary function (that is, a function that returns
something other than bool). For example, you can to multiply every
element in a vector by 10 using a binder with the transform( )
algorithm:
//: C06:FBinder.cpp
// Binders aren't limited to producing predicates.
//{L} Generators
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
#include "Generators.h"
using namespace std;
int main() {
ostream_iterator<int> out(cout," ");
vector<int> v(15);
srand(time(0)); // Randomize
generate(v.begin(), v.end(), URandGen(20));
copy(v.begin(), v.end(), out);
transform(v.begin(), v.end(),
v.begin(),
bind2nd(multiplies<int>(), 10));
copy(v.begin(), v.end(), out);
} ///:~
Since the third argument to transform( ) is the
same as the first, the resulting elements are copied back into the source vector.
The function object created by bind2nd( ) in this case produces an int result.
The bound argument to a binder cannot be a function
object, but it does not have to be a compile-time constant. For example:
//: C06:BinderValue.cpp
// The bound argument can vary.
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <cstdlib>
using namespace std;
int boundedRand() { return rand() % 100; }
int main() {
const int SZ = 20;
int a[SZ], b[SZ] = {0};
generate(a, a + SZ, boundedRand);
int val = boundedRand();
int* end = remove_copy_if(a, a + SZ, b,
bind2nd(greater<int>(), val));
// Sort for easier viewing:
sort(a, a + SZ);
sort(b, end);
ostream_iterator<int> out(cout, " ");
cout << "Original Sequence: << endl;
copy(a, a + SZ, out); cout << endl;
cout << "Values <= " << val
<< endl;
copy(b, end, out); cout << endl;
} ///:~
Here, an array is filled with 20 random numbers between 0
and 100, and the user provides a value on the command line. In the remove_copy_if( ) call, you can see that the bound argument to bind2nd( )
is random number in the same range as the sequence. Here is the output from one
run:
Original Sequence:
4 12 15 17 19 21 26 30 47 48 56 58 60 63 71 79 82 90 92
95
Values <= 41
4 12 15 17 19 21 26 30
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |