devxlogo

Use Function Adapters to Extend Generic Algorithms’ Usage

Use Function Adapters to Extend Generic Algorithms’ Usage

As useful as pointers to member functions are, in certain situations?for example when using STL algorithms?you need to use more sophisticated constructs that wrap bare pointers to members and transform them into a slightly different beast.


STL algorithms that take a function argument don’t care whether it’s a real function, a member function, or a function object, so long as they can use the notation f() to call it. The problem is that you can’t call a member function directly; you need to call it for a pointer or an object:

(p->*pmf)();  //invoke through a pointer(obj.*pmf)(); //invoke through an object

How is it possible, then, to pass only a pointer to a member and have the algorithm call it?


Use the standard set of function adapters to bind a member function to an object or a pointer.

Step 1: Demonstrating the Problem
Suppose you want to write an OS service that iterates through each of the currently active tasks. First, you store the Task objects in a container class such as std::vector:

class Task{public: explicit Task(int id) : pid(id) {} void show_pid() const  {   std::cout << "pid: " << pid << std::endl; }//..};std::vector  vt;//populate the vectorvt.push_back(Task(1));vt.push_back(Task(2));vt.push_back(Task(3));
 
Figure 1: Here’s the output for this for-loop:.

The tricky part is traversing the vector. You can use a for-loop for this purpose (as shown in Figure 1):

for (std::vector::const_iterator it=vt.begin();     it!=vt.end();     ++it){  it->show_pid(); //list every task's pid}

However, this solution is only suitable for simple cases. In real world applications, for-loops such as this could become a maintenance problem. If you decide to use a different container, say std::list, you’ll have to modify the for-loop as well:

for (std::list::const_iterator it=vt.begin();//... 

Step 2: Using for_each()
The Standard Library defines an algorithm std::for_each(), which is ideal for this purpose. Not only does it eliminate iterator type dependencies, it also takes a parameterized function argument that can be a real function, a member function, or a function object. You can thus rewrite the previous loop like this, at least theoretically:

#include  //for for_each()std::for_each (vt.begin(), vt.end(), &Task::show_pid);

This implementation is simpler, more readable, and iterator-independent. Alas, it won’t compile. As previously explained, passing a member function’s address isn’t enough; you can’t invoke it without specifying an object. Worse, the generic design of for_each() doesn’t allow you to pass a fourth argument.

 
Figure 2: Here is the output of this for_each() call.

Step 3: Using a Function Adapter
Fortunately, you don’t really need a fourth argument because the member function show_pid() should be called for every object in the range vt.begin() vt.end(). But how do you tell for_each() to do this?

The Standard Library also defines a set of function adapters that bind a member function to an object and return a matching function object. For example, std::mem_fun_ref() takes a member function’s address and binds it to an object’s reference, which is exactly what you need:

std::for_each (vt.begin(),               vt.end(),               std::mem_fun_ref(&Task::show_pid));

mem_fun_ref() function binds &Task::show_pid to a reference to Task. The effect is the same as calling show_pid() for every Task object in the sequence vt.begin() vt.end(). Figure 2 shows the output of this for_each() call.

Notice that the results of this example and the previous for-loop are identical. The benefit of using for_each() is maintenance ease and improved readability.

Further Adaptation
Containers of pointers are used, among other things, for implementing of a heterogeneous container:

class Base{public: virtual void  speak() const {std::cout<<"I'm Base"< vpb; //heterogeneousvpb.push_back(new Base);vpb.push_back(new Derived);vpb.push_back(new Base);

Suppose you want to call speak() for every element of vpb. Again, using for_each() is a preferable choice. However, because vpb stores pointers rather than objects, you have to use a different adapter this time, namely mem_fun():

 
Figure 3: This shows the output from this for_each() call:.

std::for_each (vpb.begin(),                vpb.end(),                std::mem_fun(&Base::speak));

Figure 3 shows the output of this for_each() call.

Summary
The Standard Library defines a rich set of adapters:

  • ptr_fun takes a function pointer as its argument and returns a function pointer adaptor, which is a kind of a function object.
  • The mem_fun_ref family binds a member function to an object’s reference and returns a function object.
  • The mem_fun functions binds a member function to an object’s pointer and returns a matching function object.

Each adapter is classified according to these criteria: does it apply to an ordinary function or a member function? If it’s the latter, the adapter functions contain the affix mem_. If the adapter binds a member function to a reference rather than a pointer, it contains the affix ref_. Finally, if the member function takes an argument, then the adapter contains the affix fun1_ instead of fun_. For example mem_fun1_ref() binds a member function taking one argument to a reference, whereas mem_fun1() binds a member function taking one argument to a pointer.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist