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

Function pointer adaptors

Wherever a function-like entity is expected by an algorithm, you can supply either a pointer to an ordinary function or a function object. When the algorithm issues a call, if it is through a function pointer, than the native function-call mechanism is used. If it is through a function object, then that object s operator( ) member executes. In CopyInts2.cpp, we passed the raw function gt15( ) as a predicate to remove_copy_if( ). We also passed pointers to functions returning random numbers to generate( ) and generate_n( ).

You cannot use raw functions with function object adaptors such as bind2nd( ) because they assume the existence of type definitions for the argument and result types. Instead of manually converting your native functions into function objects yourself, the standard library provides a family of adaptors to do the work for you. The ptr_fun( ) adaptors take a pointer to a function and turn it into a function object. They are not designed for a function that takes no arguments they must only be used with unary functions or binary functions.

The following program uses ptr_fun( ) to wrap a unary function:

//: C06:PtrFun1.cpp
// Using ptr_fun() with a unary function.
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
 
int d[] = { 123, 94, 10, 314, 315 };
const int DSZ = sizeof d / sizeof *d;
 
bool isEven(int x) { return x % 2 == 0; }
 
int main() {
vector<bool> vb;
transform(d, d + DSZ, back_inserter(vb),
not1(ptr_fun(isEven)));
copy(vb.begin(), vb.end(),
ostream_iterator<bool>(cout, " "));
cout << endl;
// Output: 1 0 0 0 1
} ///:~
 

We can t simply pass isEven to not1, because not1 needs to know the actual argument type and return type its argument uses. The ptr_fun( ) adaptor deduces those types through template argument deduction. The definition of the unary version of ptr_fun( ) looks something like this:

template<class Arg, class Result>
pointer_to_unary_function<Arg, Result>
ptr_fun(Result (*fptr)(Arg)) {
return pointer_to_unary_function<Arg, Result>(fptr);
}
 

As you can see, this version of ptr_fun( ) deduces the argument and result types from fptr and uses them to initialize a pointer_to_unary_function object that stores fptr. The function call operator for pointer_to_unary_function just calls fptr, as you can see by the last line of its code:

template<class Arg, class Result>
class pointer_to_unary_function
: public unary_function<Arg, Result> {
Result (*fptr)(Arg); // Stores the f-ptr
public:
pointer_to_unary_function(Result (*x)(Arg)) : fptr(x) {}
Result operator()(Arg x) const { return fptr(x); }
};
 

Since pointer_to_unary_function derives from unary_function, the appropriate type definitions come along for the ride and are available to not1.

There is also a binary version of ptr_fun( ), which returns a pointer_to_binary_function object (which derives from binary_function) that behaves analogously to the unary case. The following program uses the binary version of ptr_fun( ) to raise numbers in a sequence to a power. It also reveals a pitfall when passing overloaded functions to ptr_fun( ).

//: C06:PtrFun2.cpp {-edg}
// Using ptr_fun() for a binary function.
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
 
double d[] = { 01.23, 91.370, 56.661,
023.230, 19.959, 1.0, 3.14159 };
const int DSZ = sizeof d / sizeof *d;
 
int main() {
vector<double> vd;
transform(d, d + DSZ, back_inserter(vd),
bind2nd(ptr_fun<double, double, double>(pow), 2.0));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, " "));
cout << endl;
} ///:~
 

The pow( ) function is overloaded in the Standard C++ header <cmath> for each of the floating-point data types, as follows:

float pow(float, int); // Efficient int power versions ...
double pow(double, int);
long double pow(long double, int);
float pow(float, float);
double pow(double, double);
long double pow(long double, long double);
 

Since there are multiple versions of pow( ), the compiler has no way of knowing which to choose. Here, we have to help the compiler by using explicit function template specialization, as explained in the previous chapter.[91]

It s even trickier to convert a member function into a function object suitable for using with the generic algorithms. As a simple example, suppose we have the classical shape problem and want to apply the draw( ) member function to each pointer in a container of Shape:

//: C06:MemFun1.cpp
// Applying pointers to member functions.
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
#include "../purge.h"
using namespace std;
 
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
 
class Circle : public Shape {
public:
virtual void draw() { cout << "Circle::Draw()" << endl; }
~Circle() { cout << "Circle::~Circle()" << endl; }
};
 
class Square : public Shape {
public:
virtual void draw() { cout << "Square::Draw()" << endl; }
~Square() { cout << "Square::~Square()" << endl; }
};
 
int main() {
vector<Shape*> vs;
vs.push_back(new Circle);
vs.push_back(new Square);
for_each(vs.begin(), vs.end(), mem_fun(&Shape::draw));
purge(vs);
} ///:~
 

The for_each( ) algorithm passes each element in a sequence to the function object denoted by its third argument. Here, we want the function object to wrap one of the member functions of the class itself, and so the function object s argument becomes the pointer to the object for the member function call. To produce such a function object, the mem_fun( ) template takes a pointer to a member as its argument.

The mem_fun( ) functions are for producing function objects that are called using a pointer to the object that the member function is called for, while mem_fun_ref( ) calls the member function directly for an object. One set of overloads of both mem_fun( ) and mem_fun_ref( ) is for member functions that take zero arguments and one argument, and this is multiplied by two to handle const vs. non-const member functions. However, templates and overloading take care of sorting all that out all you need to remember is when to use mem_fun( ) vs. mem_fun_ref( ).

Suppose you have a container of objects (not pointers), and you want to call a member function that takes an argument. The argument you pass should come from a second container of objects. To accomplish this, use the second overloaded form of the transform( ) algorithm:

//: C06:MemFun2.cpp
// Calling member functions through an object reference.
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
 
class Angle {
int degrees;
public:
Angle(int deg) : degrees(deg) {}
int mul(int times) { return degrees *= times; }
};
 
int main() {
vector<Angle> va;
for(int i = 0; i < 50; i += 10)
va.push_back(Angle(i));
int x[] = { 1, 2, 3, 4, 5 };
transform(va.begin(), va.end(), x,
ostream_iterator<int>(cout, " "),
mem_fun_ref(&Angle::mul));
cout << endl;
// Output: 0 20 60 120 200
} ///:~
 

Because the container is holding objects, mem_fun_ref( ) must be used with the pointer-to-member function. This version of transform( ) takes the start and end point of the first range (where the objects live); the starting point of the second range, which holds the arguments to the member function; the destination iterator, which in this case is standard output; and the function object to call for each object. This function object is created with mem_fun_ref( ) and the desired pointer to member. Notice that the transform( ) and for_each( ) template functions are incomplete; transform( ) requires that the function it calls return a value, and there is no for_each( ) that passes two arguments to the function it calls. Thus, you cannot call a member function that returns void and takes an argument using transform( ) or for_each( ).

Most any member function works with mem_fun_ref( ). You can also use standard library member functions, if your compiler doesn t add any default arguments beyond the normal arguments specified in the standard.[92] For example, suppose you d like to read a file and search for blank lines. Your compiler may allow you to use the string::empty( ) member function like this:

//: C06:FindBlanks.cpp
// Demonstrates mem_fun_ref() with string::empty().
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <fstream>
#include <functional>
#include <string>
#include <vector>
#include "../require.h"
using namespace std;
 
typedef vector<string>::iterator LSI;
 
int main(int argc, char* argv[]) {
char* fname = "FindBlanks.cpp";
if(argc > 1) fname = argv[1];
ifstream in(fname);
assure(in, fname);
vector<string> vs;
string s;
while(getline(in, s))
vs.push_back(s);
vector<string> cpy = vs; // For testing
LSI lsi = find_if(vs.begin(), vs.end(),
mem_fun_ref(&string::empty));
while(lsi != vs.end()) {
*lsi = "A BLANK LINE";
lsi = find_if(vs.begin(), vs.end(),
mem_fun_ref(&string::empty));
}
for(size_t i = 0; i < cpy.size(); i++)
if(cpy[i].size() == 0)
assert(vs[i] == "A BLANK LINE");
else
assert(vs[i] != "A BLANK LINE");
} ///:~
 

This example uses find_if( ) to locate the first blank line in the given range using mem_fun_ref( ) with string::empty( ). After the file is opened and read into the vector, the process is repeated to find every blank line in the file. Each time a blank line is found, it is replaced with the characters A BLANK LINE. All you have to do to accomplish this is dereference the iterator to select the current string.

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

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