Solving the Reference to Reference Problem with std::bind
2011-03-28
Now let's pretend you have a class called Screen
and a few classes that
are derived from a class called Widget
. Each have a method called draw
that takes a class Canvas
as reference. It is the task to draw the screen
and each widget onto the canvas. The basic algorithm is stupidly simple, do some
setup for the screen and call draw with the canvas for each widget.
If you are using a std::vector
you can use a simple for loop:
Screen::draw(Canvas& canvas) const
{
// some setup
for (unsigned int i = 0; i < widgets.size(); i++)
{
widgets[i]->draw(canvas);
}
}
But if you start having many widgets using a std::vector
is not such a good
idea, since you must guarantee continuous segment of memory. Although often not
a real deal, you should use a std::list
for your widgets. Now you need to
change the code to using iterators:
Screen::draw(Canvas& canvas) const
{
// some setup
std::list<Widget*>::const_iterator iter = widgets.begin();
while (iter != widgets.end())
{
(*iter)->draw(canvas);
}
}
Boy this double indirection looks awful, doesn't it. Additionally the explicit looping is kind of cluttered. Why not use the handy functional programming that C++ give us. Like so:
Screen::draw(Canvas& canvas) const
{
// some setup
std::for_each(widgets.begin(), widgets.end(),
std::bind2nd(std::mem_fun(&Widget::draw), canvas));
}
Now this is straight forward and relatively elegant. To bad it fails to compile.
The error message is that the compiler can not convert Canvas&
to
const Canvas&
.
If you search the web, you will find many posts lamenting the problem that
was already described by Herb Sutter: the reference to reference problem. The
main issue here is that the signature for bind2nd
is something like
template <typename Fun, typename Arg>
binder2nd<Fun, Arg> bind2nd(Fun fun, const Arg& arg);
The obvious problem here is the use of a constant reference, which is quite
unfortunate for us. If you read on, many posts will either send you to
boost::bind
or std::tr1::bind
. Since I happen to use sigc++ I may also
add sigc::bind
to the mix of options. But the end result is about the same, they
all build on the same basic implementation scheme. The change to the code is
minimal:
Screen::draw(Canvas& canvas) const
{
// some setup
std::for_each(widgets.begin(), widgets.end(),
sigc::bind(std::mem_fun(&Widget::draw), canvas));
}
This still looks quite pleasant. The bad news, this code also does not compile.
This time the error message is that the compiler can not convert Canvas&
to
Canvas
.
But wait wasn't xxx::bind
supposed to solve the problem? Well technically
xxx::bind
can solve the problem, the remaining problem is that the compiler
fails to determine the correct type to use. This is one of the few cases where
you need to enforce the correct template type. So let's look at the signature of
sigc::bind
:
template <typename Fun, typename Arg>
binder2<Fun, Arg> bind(Fun fun, Arg arg);
Please note that it is strongly simplified version, for the sake of argument. The added complexity is for other features that are irrelevant here.
So this is simple, just change the code to explicitly specify the argument type:
Screen::draw(Canvas& canvas) const
{
// some setup
std::for_each(widgets.begin(), widgets.end(),
sigc::bind<???, Canvas&>(std::mem_fun(&Widget::draw), canvas));
}
Well to bad, we need to specify the first argument too, which is the type of
functor. So what is the type of the functor that is created with
std::mem_fun(&Widget::draw)
? Honestly, I don't want to know and it should
be my compiler's job to determine that, since this is the entire point of
generic programming.
Ok I give up, that loop did not look so bad...
Stop, there is a quite simple solution, reverse the arguments. For this we need to write the binder on our own, just like so:
template <typename Fun, typename Arg1>
struct binder2nd
{
Fun fun;
Arg1 arg1;
binder2nd(Fun f, Arg1 a1)
: fun(f), arg1(a1) {}
template <typename Arg0>
void operator () (Arg0 arg0)
{
fun(arg0, arg1);
}
};
template <typename Arg1, typename Fun>
binder2nd<Fun, Arg1> bind2nd(Fun fun, Arg1 arg1)
{
return binder2nd<Fun, Arg1>(fun, arg1);
}
Now we just need to write the draw method just like so:
Screen::draw(Canvas& canvas) const
{
// some setup
std::for_each(widgets.begin(), widgets.end(),
bind2nd<Canvas&>(std::mem_fun(&Widget::draw), canvas));
}
I chose to use the std::bind2nd
syntax, since it is simpler than
xxx::bind
and does the job for me. You may use my code snippet as you like and
changing it to a bind1st
is as trivial as swapping arg1
and arg0
. Now you
can bind as many references as you like.