Monday, October 11, 2010

std::tr1::bind the hard way

I'll probably move this to a tech blog when I start one..

Usually, I'd just go up to my colleague (Mr. Skarpio, a.k.a. DirkGently) when in trouble with the STL. This time he's on leave, and I had to figure it out on my own. The thing in question is tr1::bind, a function template available in C++ for binding values to parameters in functions, and functors. It is supposed to be easier to use than the older std::bind1st and std::bind2nd. But, I'm new to STL, and of course to the "functional" way of life. So, I had a real hard time to get this right. So how do you use it...

Here's how :


Example 1: Using it with regular functions

void foo(int bar1, int bar2)
{
std::cout << bar1 << bar2 << std::endl;
}

And then some where in your code, if you use..

std::tr1::bind(foo, 10, std::tr1::placeholders::_1)(20)

What this does is of course useless (mostly). The list of arguments after "foo" above has one of the following two things (actually, it can have more, but for now two will suffice):

1. a value
2. a placeholder

So the expression above is equivalent to calling foo(10, 20);

If you do it the other way around, i.e.
std::tr1::bind(foo, std::tr1::placeholders::_1, 10)(20);
then that's equivalent to calling foo(20, 10);.

If you specify placeholders for both, i.e.
std::tr1::bind(foo, std::tr1::placeholders::_1, std::tr1::placeholders::_1)(20); ,
then both parameters are taken from the actual list of parameters, i.e. it is equivalent to calling foo(20,20);.

Example 2: Using it with function objects (functors). This is where I got stuck and then read the documentation here carefully to discover this
In the general case, the return type of the generated function object's operator() has to be specified explicitly (without a typeof operator the return type cannot be inferred):
So, for example, here is a simple rendition of the same function "foo" as a functor:

struct foo_object {
void operator()(int x, int y) {
std::cout << x << "," << y << std::endl;
}
};

Now, using std::tr1::bind(foo_object(), 10, std::tr1::placeholders::_1)(20); does not work. So, basically, the struct has to be modified to add a new result_type typedef. That is,

struct foo_object {
typedef void result_type;
void operator()(int x, int y) {
std::cout << x << "," << y << std::endl;
}
};

Now we can use this, std::tr1::bind(foo_object(), 10, std::tr1::placeholders::_1)(20); , and it is indeed equivalent to calling foo_object()(10, 20).


Now for the actual utility of it... of Mr. tr1::bind :

Binding is used for clever "functional"-type programming in C++. For example,
  • suppose you have a std::vector<std::string> strings;
  • and a function std::string get_suffix(const std::string& str, long suffix_len); which gives you a suffix of the string that is suffix_len in length,
  • and now you want to extract out all suffixes of length 2 and length 4 from the list of strings and store them into distinct vectors std::vector<std::string> suffix_2, and suffix_4.
The regular way to do this is to use a for-loop that looks like this:
std::vector<std::string>::const_iterator iter = strings.begin(), iterEnd = strings.end();
for (; iter != iterEnd; ++iter) {
suffix_2.push_back(get_suffix(*iter, 2));
suffix_4.push_back(get_suffix(*iter, 4));
}
But, the STL way to do this is:
std::transform(strings.begin(), strings.end(), std::back_inserter(suffix_2),
std::tr1::bind(get_suffix, std::tr1::placeholders::_1, 2));
std::transform(strings.begin(), strings.end(), std::back_inserter(suffix_4),
std::tr1::bind(get_suffix, std::tr1::placeholders::_1, 4));

Of course, if Mr. Skarpio ever saw this, I'd be in for some rebuke, because he'd probably use some compositional stuff and do it in a single expression.






1 comment:

Protean said...

One magic feature of tr1::bind. If you have an object Obj of class C with a member function foo(a, b, c), then the following statement calls foo with the given args as before: -

std::tr1::bind(&C::foo, Obj, 10, 20, std::tr1::placeholders::_1)(30)
is equivalent to calling Obj.foo(10, 20, 30).

Wow! As a consequence, what we can do is the following:-
Let's say mymap is a std::map<key_t, value_t> and we want to transform all the values in this map using a member function t() of the value_t class, and read them into a vector v, we can call it using:

std::transform(mymap.begin(), mymap.end(),
std::back_inserter(v),
std::tr1::bind(&value_t::t,
std::tr1::bind(&std::map<key_t, value_t>::value_type::second)));

Followers

Blog Archive

About me

A soul possessed forever...