| University of Cambridge > Engineering Department > computing help > Languages > C++ |
This document aims to help you understand bind1st and bind2nd, which may seem a rather odd and arbitrary objective, but
C++ is quite a strongly typed language - if a function requires an argument of type int and you provide a string, the compiler will complain. This is a good thing in that errors are reduced but it makes generic code (code which will work with different types of arguments) harder to write. Some of the apparent complexity of C++ (bind1st, for example) is due to the methods required to resolve this conflict of needs.
Templates can help with producing generic code. Consider the following simple routine.
bool greater_than(int a, int b){
return a>b;
}
The basic code would be the same if the arguments were floating point numbers
or even strings, but because C++ is fussy about types we'd have to write a
separate routine for each type, replacing int each time. This is where templates prove useful
template <class T>
bool greater_than(T a, T b){
return a>b;
}
This uses the same basic code as the earlier example but instead
of int we have T, which at compile time is replaced by
whatever is required by subsequent code. So
int a=6, b=9; string s="apple", t="pear"; cout << greater_than(a,b); cout << greater_than(s,t);will work, the compiler creating the appropriate code. If we try
greater_than(a,t);however, compilation won't work, so we haven't compromised on safety.
The standard library makes heavy use of templates internally, making it possible to use generic routines without having to learn all the template-related syntax. A little knowledge can go a long way. For example, in the code below,
string s="apple";
vector<int> v(3);
v[0]=1; v[1]=2; v[2]=3;
// the following line checks if find returns s.end()
cout << (find(s.begin(),s.end(),'l') != s.end()) << endl;
cout << (find(v.begin(),v.end(),4) != v.end()) << endl;
Another routine is find_if which
is like find except that it
doesn't take a value as its 3rd argument. Instead it needs the name of
a predicate -
a routine which is given an element and returns true only for
elements which fulfill
the required condition. This offers more flexibility but before we
exploit that, let's replicate what the previous example does, dealing
with the integer case first.
bool equal_to_4(int it)
{
return it==4;
}
vector<int> v(3);
v[0]=1; v[1]=2; v[2]=3;
cout << (find_if(v.begin(),v.end(), equal_to_4)!= v.end()) << endl;
v[1]=4;
cout << (find_if(v.begin(),v.end(), equal_to_4)!= v.end()) << endl;
find_if finds the first instance. Now
we'll use another standard library routine,
count_if, which counts the instances,
incrementing the 4th argument accordingly
bool equal_to_4(int it)
{
return it==4;
}
int how_many=0;
vector<int> v(3);
v[0]=1; v[1]=2; v[2]=3;
count_if(v.begin(),v.end(), equal_to_4, how_many);
cout << how_many << endl;
v[1]=4;
how_many=0;
count_if(v.begin(),v.end(), equal_to_4, how_many);
cout << how_many << endl;
It's now a small step to be able to count how many elements are greater
than 2.
bool greater_than_2(int it)
{
return it>2;
}
int how_many=0;
vector<int> v(3);
v[0]=1; v[1]=2; v[2]=3;
count_if(v.begin(),v.end(), greater_than_2, how_many);
cout << how_many << endl;
v[0]=4;
how_many=0;
count_if(v.begin(),v.end(), greater_than_2, how_many);
cout << how_many << endl;
There's another way to do this using functors.
When you create a new class you can define what "+" does for objects of
that class. Other operators can be defined too.
Objects with "()" defined are called function objects or functors.
Here's a simple example
class more_than_2 {
public:
bool operator() (int val) {return val>2;}
};
which we can use with count_if
count_if(v.begin(),v.end(), more_than_2() , how_many);
When we used templates earlier to write the greater_than routine, a different version of source code was produced (behind the scenes by the compiler) for each new type of argument used. It's possible to do a similar thing so that functions like our greater_than_2 can be generated on demand. We'll do this in 2 ways - first using a comparison function of our own, then using a standard routine.
template <int fixed_val>
class more_than {
public:
bool operator() (int val) {return val>fixed_val;}
};
This is a rather different use of the template idea. Here, different
instantiations of the template differ not in the type of the variables,
but in the value of an integer. Using
more_than<2>() in the code makes the compiler generate
the same source code as the above more_than_2.
There's a risk of code bloat here - whenever a new integer value
is used, more code is generated - but it does give us a way to pass an
argument to the comparison function by using something like
count_if(v.begin(),v.end(), more_than<2>() , how_many);
#include <functional>
...
int how_many=0;
vector<int> v(3);
v[0]=1; v[1]=2; v[2]=3;
count_if(v.begin(),v.end(),bind2nd(greater<int>(), 0), how_many);
cout << how_many << " values more than 0" << endl;
how_many=0;
count_if(v.begin(),v.end(),bind2nd(greater<int>(), 1), how_many);
cout << how_many << " values more than 1" << endl;
The count_if call may look
bewildering, but even if you don't want to understand it you can
still adapt and use it. For example, if you wanted to count how many
floats in a vector were greater than 5.67, you could use
count_if(v.begin(),v.end(),bind2nd(greater<float>(), 5.67), how_many);
But how does it work? Let's first consider greater. It's
defined in the <functional>
header file. It's a binary functor - something
that can be used like a function and takes 2 arguments. bind2nd
is an adaptor, a kind of wrapper that
in this case lets us use a binary function when a unary function is required.
So in this example count_if passes an element to what it thinks is
a unary function, which in turn passes this element plus a comparison value
(0, 1 or 5.67 in these examples) to a binary function whose return value is
given back to count_if.
transform(v.begin(),v.end(),bind2nd(divides<float>(), 3));
The above description glosses over some details.
Both bind1st() and bind2nd() are functions that take as arguments a binary function object and a value, and return, respectively, classes binder1st and binder2nd. The underlying function object must be derived from binary_function.
binder2nd is a function object adaptor: if f is an object of class binder2nd<AdaptableBinaryFunction>, then f(x) returns F(x, c), where F is an object of class AdaptableBinaryFunction and where c is a constant. Both F and c are passed as arguments to binder2nd's constructor.
The easiest way to create a binder2nd is not to call the constructor explicitly, but instead to use the helper function bind2nd.
| | C++ | Languages | computing help | |