C plus plus:Modern C plus plus:Binders

From GPWiki
Jump to: navigation, search

Modern C++ : Going Beyond "C with Classes"

Introduction

Last 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.

Old Standard Binders

ISO 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.

TR1 Binders

Technical 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. GCC and VC++ do have TR1 support now, in their latest versions, but it's still easiest to refer to the Boost version for now, 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.

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 is more than most will ever need, but if you do, there's a #define).

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 ) );
 
}

The Boost Lambda Library

The 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.

One of the nicest examples, however, involves output. So far for output, the most general option has been 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>. )

Further Reading

In Closing

With 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.

What's Next

Of 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.