Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
Consider how to write a program that converts strings
representing floating-point numbers to their actual numeric values. To get
things started, here s a generator that creates the strings:
//: C06:NumStringGen.h
// A random number generator that produces
// strings representing floating-point numbers.
#ifndef NUMSTRINGGEN_H
#define NUMSTRINGGEN_H
#include <cstdlib>
#include <string>
class NumStringGen {
const int sz; // Number of digits to make
public:
NumStringGen(int ssz = 5) : sz(ssz) {}
std::string operator()() {
std::string digits("0123456789");
const int ndigits = digits.size();
std::string r(sz, ' ');
// Don't want a zero as the first digit
r[0] = digits[std::rand() % (ndigits - 1)] + 1;
// Now assign the rest
for(int i = 1; i < sz; ++i)
if(sz >= 3 && i
== sz/2)
r[i] = '.'; // Insert a decimal point
else
r[i] = digits[std::rand() % ndigits];
return r;
}
};
#endif // NUMSTRINGGEN_H ///:~
You tell it how big the strings should be when you create
the NumStringGen object. The random number generator selects digits, and
a decimal point is placed in the middle.
The following program uses NumStringGen to fill a vector<string>.
However, to use the standard C library function atof( ) to convert
the strings to floating-point numbers, the string objects must first be
turned into char pointers, since there is no automatic type conversion
from string to char*. The transform( ) algorithm can
be used with mem_fun_ref( ) and string::c_str( ) to
convert all the strings to char*, and then these can be
transformed using atof.
//: C06:MemFun3.cpp
// Using mem_fun().
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "NumStringGen.h"
using namespace std;
int main() {
const int SZ = 9;
vector<string> vs(SZ);
// Fill it with random number strings:
srand(time(0)); // Randomize
generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout,
"\t"));
cout << endl;
const char* vcp[SZ];
transform(vs.begin(), vs.end(), vcp,
mem_fun_ref(&string::c_str));
vector<double> vd;
transform(vcp, vcp + SZ, back_inserter(vd),
std::atof);
cout.precision(4);
cout.setf(ios::showpoint);
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout,
"\t"));
cout << endl;
} ///:~
This program does two transformations: one to convert
strings to C-style strings (arrays of characters), and one to convert the
C-style strings to numbers via atof( ). It would be nice to combine
these two operations into one. After all, we can compose functions in
mathematics, so why not C++?
The obvious approach takes the two functions as arguments
and applies them in the proper order:
//: C06:ComposeTry.cpp
// A first attempt at implementing function composition.
#include <cassert>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
using namespace std;
template<typename R, typename E, typename F1,
typename F2>
class unary_composer {
F1 f1;
F2 f2;
public:
unary_composer(F1 fone, F2 ftwo) : f1(fone), f2(ftwo)
{}
R operator()(E x) { return f1(f2(x)); }
};
template<typename R, typename E, typename F1,
typename F2>
unary_composer<R, E, F1, F2> compose(F1 f1, F2
f2) {
return unary_composer<R, E, F1, F2>(f1, f2);
}
int main() {
double x = compose<double, const string&>(
atof,
mem_fun_ref(&string::c_str))("12.34");
assert(x == 12.34);
} ///:~
The unary_composer object in this example stores the function pointers atof and string::c_str such that the latter
function is applied first when its operator( ) is called. The compose( ) function adaptor is a convenience, so we don t need to supply all
four template arguments explicitly F1 and F2 are deduced from the
call.
It would be much better if we didn t need to supply any
template arguments. This is achieved by adhering to the convention for type
definitions for adaptable function objects. In other words, we will assume that
the functions to be composed are adaptable. This requires that we use ptr_fun( )
for atof( ). For maximum flexibility, we also make unary_composer
adaptable in case it gets passed to a function adaptor. The following program
does so and easily solves the original problem:
//: C06:ComposeFinal.cpp {-edg}
// An adaptable composer.
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "NumStringGen.h"
using namespace std;
template<typename F1, typename F2> class
unary_composer
: public unary_function<typename F2::argument_type,
typename F1::result_type> {
F1 f1;
F2 f2;
public:
unary_composer(F1 f1, F2 f2) : f1(f1), f2(f2) {}
typename F1::result_type
operator()(typename F2::argument_type x) {
return f1(f2(x));
}
};
template<typename F1, typename F2>
unary_composer<F1, F2> compose(F1 f1, F2 f2) {
return unary_composer<F1, F2>(f1, f2);
}
int main() {
const int SZ = 9;
vector<string> vs(SZ);
// Fill it with random number strings:
generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout,
"\t"));
cout << endl;
vector<double> vd;
transform(vs.begin(), vs.end(), back_inserter(vd),
compose(ptr_fun(atof),
mem_fun_ref(&string::c_str)));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout,
"\t"));
cout << endl;
} ///:~
Once again we must use typename to let the compiler
know that the member we are referring to is a nested type.
Some implementations support
composition of function objects as an extension, and the C++ Standards Committee
is likely to add these capabilities to the next version of Standard C++.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |