Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Writing your own function object adaptors

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[93] 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

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire