C plus plus:Modern C plus plus:Binders
From GPWiki
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello. Modern C++ : Going Beyond "C with Classes"
[edit] IntroductionLast time I showed a way to write a functor that just returns its argument mod some value set at construction. Basically all it did, however, was duplicate std::modulus and let you fix the second argument. This is a common enough task that there are special adapters, called Binders that make it trivial -- you can even do it inline without having to add anything outside your function call. [edit] Old Standard BindersISO C++98 does supply some binders. Unfortunately, they're terrible. They're harder to use, less general, have more pitfalls, can't be chained, and are murder to try to read. You can read up on them if you want, but I don't suggest even bothering. [edit] TR1 BindersTechnical Report 1 (TR1) consists of a number of libraries in the process of being added to C++. Among them is a new binder library ( you can read the proposal ), fashioned on the Boost Bind Library. Since most std::lib implementations don't yet include support for TR1, I'm going to refer to the Boost version, which is currently freely available for even old, terrible compilers. The Boost License is very liberal—you're free to use it for whatever you like and don't need to do anything to distribute applications using it in binary form. You cannot remove the copyright notices from source files, but that's quite fair and I can't see it being a problem for anyone. Last time the best we could end up with was the following, which required that we write a custom functor: std::transform( v.begin(), v.end(), v.begin(), mod_by<int>(10) ); The same thing with Boost.Bind can be done much more easily (don't forget to #include <boost/bind.hpp>) : std::transform( v.begin(), v.end(), v.begin(), boost::bind( std::modulus<int>(), _1, 10 ) ); Just the call to boost::bind magically generates a Functor that does the right thing. The question you're surely asking now is "What is that weird _1 thing?". It's called a Placeholder, and represents the first argument to the Functor. Boost.Bind provides _1, _2, _3, and so on, usually up to _9 ( which I hope is more than you'll ever need ). That, however, just scratches the surface of its capabilities. Since std::generate_n uses a nullary (0-parameter) Functor as its data source, we can do away with the transform call altogether by nesting binds: std::generate_n( std::back_inserter(v), 10, boost::bind( std::modulus<int>(), boost::bind(&std::rand), 10 ) ); In this case, Boost.Bind generates a Functor that, when called, returns the result of std::rand() % 10. Boost.Bind is also capable of turning member function pointers into Functors. Often, for example, a 3D engine will have a Container of pointers to objects that all inherit from some sort of drawable and as such have a draw() member function. Supposing that you have std::vector< drawable* > entities;, then you can use std::for_each and Boost.Bind to draw all the elements: std::for_each( entities.begin(), entities.end(), boost::bind( &drawable::draw, _1 ) ); Remember when using member function pointers is that they have to be called on an instance of an object, so the second parameter to boost::bind is the object on which the function should be called. Note that this works for virtual or non-virtual member functions and for both pointers to objects and references to them. One important detail is that boost::bind binds things "by value", not "by reference". This means that a copy is made, which is often not what you want. For containers, it could be a huge efficiency loss. For some types, such as streams, it's not even possible. The solution to this is boost::reference_wrapper, which you'll almost never use directly, instead using the helper function boost::ref() which deduces the type. Here's a simple example using boost::ref: #include <iostream> #include <ostream> #include <fstream> #include <functional> #include <algorithm> #include <vector> #include <cstdlib> #include <ctime> #include <boost/bind.hpp> std::ostream &write_binary_long(std::ostream &sink, long value) { if ( value > 0x7FFFFFFFl || value < (-0x7FFFFFFFl)-1 ) { sink.clear( sink.rdstate() | std::ios_base::failbit ); } else { char data[4] = { (value>> 0)&0xFF, (value>> 8)&0xFF, (value>>16)&0xFF, (value>>24)&0xFF }; sink.write( data, 4 ); } return sink; } int main() { std::srand( std::time(0) ); std::vector<long> v; std::generate_n( std::back_inserter(v), 256, &std::rand ); std::ofstream outfile( "output.txt", std::ios::binary ); std::for_each( v.begin(), v.end(), boost::bind( &write_binary_long, boost::ref(outfile), _1 ) ); } [edit] The Boost Lambda LibraryThe Boost Lambda Library is a truly remarkable bit of programming. It's not going to be added to the Standard any time soon, but it's too cool to ignore. Using Boost.Lambda, instead of boost::bind( std::modulus<int>(), boost::bind(&std::rand), 10 ) as we needed to do with Boost.Bind, we can simply say bind(&std::rand) % 10. The boost::bind( std::modulus<int>(), _1, 10 ) call above is simply _1 % 10 in Boost.Lambda. My personal favorite example, however, involves output. So far for output, I've used copying to Stream Iterators: std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout," ") ); std::cout << std::endl; Admittedly, that's not the clearest code. Using Boost.Lambda, it can be changed to something incredibly intuitive: std::for_each( v.begin(), v.end(), std::cout << boost::lambda::_1 << ' ' ); std::cout << std::endl; IF you have Boost.Bind installed, you also have Boost.Lambda installed, so give it a try. ( You'll need to #include <boost/lambda/lambda.hpp>. ) [edit] Further Reading
[edit] In ClosingWith Binders, there's no reason not to use Algorithms and Functors for most of your operations on elements of containers. It's no longer necessary to go through the tedium of creating Functor structures; the library takes care of the details. [edit] What's NextOf course, all these Functors will usually have different types. boost::bind( std::modulus<int>(), boost::bind(&std::rand), 10 ) may act like a nullary function that returns an int, but its type is certainly not int(*)() the way &std::rand is, so you can't store it in a function pointer. Luckily, TR1 comes to the rescue again, providing us with a Polymorphic Function Object Wrapper. |


