Using Function Objects (Functors) in STL / C++

Generically, function objects (or functors) are class instances whose member function operator() has been defined. This member function allows the object to be used with the same syntax as a regular function call, and therefore its type can be used as template parameter when a generic function type is expected.

So why not just use straight function calls?

1. Function objects (functors) can contain states
2. A function object is a type, so it can be used as a template parameter.

Unary Function Objects (single argument)

Suppose you wanted to find the sum total of all the values contained in a vector.  You could loop through them sequentially in a for loop and increment them, or you could use a functor.  A suitable functor might look like this:

struct adder : public unary_function<double, void>
{
    adder() : sum(0) {}
    double sum;
    void operator() (double x) { sum += x; }
};

“adder” is now a functor that takes the values stored in the vector and adds them to find a grand total.  For  example I could fill a vector array with some values:

double vals[] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
vector< double >A( vals, vals + 5 );

And then the for_each operator is used to apply the function object to each element in the range.  “adder” looks like a call to a function, but it is actually the calling operator() of the Functor type:

adder sum = for_each( A.begin(), A.end(), adder() );
double total = sum.sum;

This similarity between calling a function object and a function is how the term function object came about. One more example, this time to read a vector of integers and return them as one string, each number separated by a space:

#include <algorithm>
#include <string>
#include <sstream>
#include <iostream>

struct acc : public std::unary_function< int, void >
{  
    acc() : accum( "" ) {}  
    std::string accum;  
      
    void operator() ( int val )  
    {  
        std::stringstream st;  
        st << val;  
        accum.append( st.str() + " " );  
    }  
};  
      
int main()
{
	int x[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
	int size = sizeof( x ) / sizeof( x[ 0 ] );
      
	acc sum = std::for_each( x, x + size, acc() );  
      
	std::string str = sum.accum;  

	std::cout << "Accumulated numbers: " << str << std::endl;

	return 0;
}

Accumulated numbers: 1 2 3 4 5 6 7 8 9

Binary Function Objects (two arguments)

Some examples of using the binary function object. The std::transform algorithm applies a function to each object in a given range and copies the result of the function to a new range, pointed to by an output iterator:

#include <iostream>
#include <vector>   
#include <algorithm>
#include <functional>
#include <iterator>
      
using namespace std;  
      
int x[] = { 1, 2, 3, 4, 5 };  
int y[] = { 1, 1, 1, 4, 5 };  
      
void Output()  
{  
    copy( x,  
          x + sizeof( x ) / sizeof( x[ 0 ] ),  
          ostream_iterator<int>( cout, " " ) );  
      
    cout << endl;  
}  
      
template<class T>   
struct IncreaseByVal : public binary_function<T, T, T>
{  
    T operator()( T a, T b ) const  
    {  
        return a + b;  
    }  
};  
      
int IncreaseBy1 (int i)  
{  
    return ++i;  
}  
      
int main()  
{  
	std::cout << "Example std::transform usage" << std::endl << std::endl;
	std::cout << "Initial values:" << std::endl;
	Output();

    // Increase x values using simple function:  
    // all values contained in x  incremented by 1  
    transform( x,  
               x + sizeof( x ) / sizeof( x[ 0 ] ),  
               x,  
               IncreaseBy1 );  
      
	std::cout << "Values incremented by 1 using ordinary function:" << std::endl;
    Output();  
      
    // Increase using function adaptor taking 2 input ranges:  
    // IncreaseByVal 2 arguments supplied by x,y array values  
    transform( x,  
               x + sizeof( x ) / sizeof( x[ 0 ] ),  
               y,  
               x,  
               IncreaseByVal<int>() );  
      
	std::cout << "Values increased by supplied array values, using functor:" << std::endl;
    Output();  
      
    // Convert IncreaseByVal binary function object (2 args)  
    // into unary function object (1 arg) by binding 2nd argument of  
    // binary function object to a  
    // specified value (2)  
    transform( x,  
               x + sizeof( x ) / sizeof( x[ 0 ] ),  
               x,  
               bind2nd( IncreaseByVal<int>(), 2 ) );  
      
    std::cout << "Values increased by binding 2nd arg of binary functor to specified val.:" << std::endl;
    Output();    
      
    return 0;  
}  

Example std::transform usage

Initial values:
1 2 3 4 5
Values incremented by 1 using ordinary function:
2 3 4 5 6
Values increased by supplied array values, using functor:
3 4 5 9 11
Values increased by binding 2nd arg of binary functor to specified val.:
5 6 7 11 13

Using comparison function objects

Typically used by algorithms that need to compare two objects for the purpose of sorting or searching. A comparison object is a boolean function taking two objects and returns true or false, depending on whatever criteria you decide to use. An example here shows the application of comparison function objects for sorting arrays of strings:

#include <algorithm>
#include <string>
#include <iterator>
#include <sstream>
#include <iostream>

std::string names[] = { "Abe", "Andrew", "Aaron", "Arthur", "Alfred", "Al", 
	                    "Abdul", "Adrian", "Albert", "Alessandro" };

const int size = sizeof( names ) / sizeof( names[ 0 ] );

struct Less 
{  
    bool operator()( const std::string& a, const std::string& b ) const  
    {  
        return a < b;  
    }  
};  

struct Greater 
{  
    bool operator()( const std::string& a, const std::string& b ) const  
    {  
        return a > b;  
    }  
};  

struct Length 
{  
    bool operator()( const std::string& a, const std::string& b ) const  
    {  
		return a.length() < b.length();  
    }  
};  
      
void Output()    
{    
    copy( names,    
          names + sizeof( names ) / sizeof( names[ 0 ] ),    
          std::ostream_iterator&lt;std::string&gt;( std::cout, " " ) );    
        
    std::cout << std::endl << std::endl;    
}    
      
int main()  
{  
	std::cout << "Original unsorted names:" << std::endl;
	Output();

	std::sort( names, names + size, Less() );
	std::cout << "Sorted names (alphabetical, ascending):" << std::endl;
	Output();

	std::sort( names, names + size, Greater() );
	std::cout << "Sorted names (alphabetical, descending):" << std::endl;
	Output();

	std::sort( names, names + size, Length() );
	std::cout << "Sorted names (string length, ascending):" << std::endl;
	Output();
	      
    return 0;  
}  

Original unsorted names:
Abe Andrew Aaron Arthur Alfred Al Abdul Adrian Albert Alessandro

Sorted names (alphabetical, ascending):
Aaron Abdul Abe Adrian Al Albert Alessandro Alfred Andrew Arthur

Sorted names (alphabetical, descending):
Arthur Andrew Alfred Alessandro Albert Al Adrian Abe Abdul Aaron

Sorted names (string length, ascending):
Al Abe Abdul Aaron Arthur Andrew Alfred Albert Adrian Alessandro




Latest Comments

  1. Ronnse 2 November 2015

Leave a Reply