Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct Way to Define a Predicate Function in C++

Tags:

c++

stl

predicate

I'm trying to write predicate function for use with STL algorithms. I see that they are two ways to define a predicate:

(1) Use a simple function as below:

bool isEven(unsigned int i)   
{ return (i%2 == 0); }

std::find_if(itBegin, itEnd, isEven); 

(2) Use the operator() function as below:

class checker {  
public:  
  bool operator()(unsigned int i)  
  { return (i%2 == 0); }  
}; 

std::find_if(itBegin, itEnd, checker); 

I have more use for the second type as I usually would like to create a predicate object with some members in it and use them in the algorithm. When I add the same isEven function inside checker and use it as a predicate, I get an error:
3. Syntax which gives error:

class checker { 
    public: 
       bool isEven(unsigned int i) 
       { return (i%2 == 0); }
}; 

checker c; 
std::find_if(itBegin, itEnd, c.isEven); 

Calling c.isEven gives an error during compilation saying undefined reference to some function. Can someone explain why 3. is giving error? Also, I would appreciate any pointers to read about predicate and iterator basics.

like image 266
cppcoder Avatar asked Jul 28 '11 04:07

cppcoder


People also ask

What is a predicate function in C?

Predicate functions are functions that return a single TRUE or FALSE . You use predicate functions to check if your input meets some condition. For example, is. character() is a predicate function that returns TRUE if its input is of type character and FALSE otherwise.

What is a predicate in programming?

Simple Definition A predicate is a function of a set of parameters that returns a boolean as an answer: boolean predicate(set of parameters) A boolean has the value either true or false (yes or no). The input to the function can be any set of parameters we want.


5 Answers

A pointer to a member function requires an instance to be called on, and you are passing only the member function pointer to std::find_if (actually your syntax is incorrect, so it doesn't work at all; the correct syntax is std::find_if(itBegin, itEnd, &checker::isEven) which then still doesn't work for the reasons I gave).

The find_if function expects to be able to call the function using a single parameter (the object to test), but it actually needs two to call a member function: the instance this pointer and the object to compare.

Overloading operator() allows you to pass both the instance and the function object at the same time, because they're now the same thing. With a member function pointer you must pass two pieces of information to a function that expects only one.

There is a way to do this using std::bind (which requires the <functional> header):

checker c;
std::find_if(itBegin, itEnd, std::bind(&checker::isEven, &c, std::placeholders::_1));

If your compiler doesn't support std::bind, you can also use boost::bind for this. Though there's no real advantage to doing this over just overloading operator().


To elaborate a bit more, std::find_if expects a function pointer matching the signature bool (*pred)(unsigned int) or something that behaves that way. It doesn't actually need to be a function pointer, because the type of the predicate is bound by the template. Anything that behaves like a bool (*pred)(unsigned int) is acceptable, which is why functors work: they can be called with a single parameter and return a bool.

As others have pointed out, the type of checker::isEven is bool (checker::*pred)(unsigned int) which doesn't behave like the original function pointer, because it needs an instance of checker to be called on.

A pointer to a member function can be conceptually considered as a regular function pointer that takes an additional argument, the this pointer (e.g. bool (*pred)(checker*, unsigned int)). You can actually generate a wrapper that can be called that way using std::mem_fn(&checker::isEven) (also from <functional>). That still doesn't help you, because now you have a function object that must be called with two parameters rather than only one, which std::find_if still doesn't like.

Using std::bind treats the pointer to a member function as if it was a function taking the this pointer as its first argument. The arguments passed to std::bind specify that the first argument should always be &c, and the second argument should bind to the first argument of the newly returned function object. This function object is a wrapper that can be called with one argument, and can therefore be used with std::find_if.

Although the return type of std::bind is unspecified, you can convert it to a std::function<bool(unsigned int)> (in this particular case) if you need to refer to the bound function object explicitly rather than passing it straight to another function like I did in my example.

like image 190
Sven Avatar answered Oct 18 '22 06:10

Sven


I guess it's because the type of c.isEven() is,

bool (checker::*)(unsigned int) // member function of class

which may not be expected by find_if(). std::find_if should be expecting either a function pointer (bool (*)(unsigned int)) or a function object.

Edit: Another constraint: A non-static member function pointer must be called by the class object. In your case, even if you succeed to pass the member function then still find_if() will not have any information about any checker object; so it doesn't make sense to have find_if() overloaded for accepting a member function pointer argument.

Note: In general c.isEven is not the right way to pass member function pointer; it should be passed as, &checker::isEven.

like image 41
iammilind Avatar answered Oct 18 '22 07:10

iammilind


checker::isEven is not a function; it is a member function. And you cannot call a non-static member function without a reference to a checker object. So you can't just use a member function in any old place that you could pass a function pointer. Member pointers have special syntax that requires more than just () to call.

That's why functors use operator(); this makes the object callable without having to use a member function pointer.

like image 36
Nicol Bolas Avatar answered Oct 18 '22 07:10

Nicol Bolas


I prefer functors (function objects) because make your program more readable and, more importantly, expressing the intent clearly.

This is my favorite example:

template <typename N>
struct multiplies
{
  N operator() (const N& x, const N& y) { return x * y; }
};

vector<int> nums{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Example accumulate with transparent operator functor 
double result = accumulate(cbegin(nums), cend(nums), 1.1, multiplies<>());

Note: In recent years we've got a lambda expression support.

// Same example with lambda expression
double result = accumulate(cbegin(nums), cend(nums), 1.1,
                            [](double x, double y) { return x * y; });
like image 30
Marko Tunjic Avatar answered Oct 18 '22 06:10

Marko Tunjic


The example given says you should use the call operator (operator()) whereas in your example you've called your function isEven. Try re-writing it as:

class checker { 
    public: 
       bool operator()(unsigned int i) 
       { return (i%2 == 0); }
};
like image 32
Hugh Avatar answered Oct 18 '22 05:10

Hugh