If you work with the STL library a lot and use the <algorithm>
header, you will definitely write a lot of functional objects. You can construct them using a set of STL adapter functions such as bind1st
, bind2nd
, ptr_fun
, mem_fun
, and mem_fun_ref
, or you can write them by hand (because adapter functions look scary). Here is some good news: Boost.Bind
can be used instead of all of those functions and it provides a more human-readable syntax.
Read the previous recipe to get an idea of placeholders, or just make sure that you are familiar with C++11 placeholders. Knowledge of STL functions and algorithms is welcomed.
Let's see some examples of the usage of Boost.Bind
along with traditional STL classes:
Count values greater than or equal to 5 as shown in the following code:
boost::array<int, 12> values = {{1, 2, 3, 4, 5, 6, 7, 100, 99, 98, 97, 96}}; std::size_t count0 = std::count_if(values.begin(), values.end(), std::bind1st(std::less<int>(), 5)); std::size_t count1 = std::count_if(values.begin(), values.end(), boost::bind(std::less<int>(), 5, _1)); assert(count0 == count1);
This is how we could count empty strings:
boost::array<std::string, 3> str_values = {{"We ", "are", " the champions!"}}; count0 = std::count_if(str_values.begin(), str_values.end(), std::mem_fun_ref(&std::string::empty)); count1 = std::count_if(str_values.begin(), str_values.end(), boost::bind(&std::string::empty, _1)); assert(count0 == count1);
Now let's count strings with a length less than
5
:// That code won't compile! And it is hard to understand //count0 = std::count_if(str_values.begin(), //str_values.end(), //std::bind2nd( // std::bind1st( // std::less<std::size_t>(), // std::mem_fun_ref(&std::string::size) // ) //, 5 //)); // This will become much more readable, // when you get used to bind count1 = std::count_if(str_values.begin(), str_values.end(), boost::bind(std::less<std::size_t>(), boost::bind(&std::string::size, _1), 5)); assert(2 == count1);
Compare the strings:
std::string s("Expensive copy constructor of std::string will be called when binding"); count0 = std::count_if(str_values.begin(), str_values.end(), std::bind2nd(std::less<std::string>(), s)); count1 = std::count_if(str_values.begin(), str_values.end(), boost::bind(std::less<std::string>(), _1, s)); assert(count0 == count1);
The boost::bind
function returns a functional object that stores a copy of the bound values and a copy of the original functional object. When the actual call to operator()
is performed, the stored parameters are passed to the original functional object along with the parameters passed at the time of call.
Take a look at the previous examples. When we are binding values, we copy a value into a functional object. For some classes this operation is expensive. Is there a way to bypass copying?
Yes, there is! And the Boost.Ref
library will help us here! It contains two functions, boost::ref()
and boost::cref()
, the first of which allows us to pass a parameter as a reference, and the second one passes the parameter as a constant reference. The ref()
and cref()
functions just construct an object of type reference_wrapper<T>
or reference_wrapper<const T>
, which is implicitly convertible to a reference type. Let's change our previous examples:
#include <boost/ref.hpp> ... std::string s("Expensive copy constructor of std::string now " "won't be called when binding"); count0 = std::count_if(str_values.begin(), str_values.end(), std::bind2nd(std::less<std::string>(), boost::cref(s))); count1 = std::count_if(str_values.begin(), str_values.end(), boost::bind(std::less<std::string>(), _1, boost::cref(s))); assert(count0 == count1);
Just one more example to show you how boost::ref
can be used to concatenate strings:
void wierd_appender(std::string& to, const std::string& from) { to += from; }; std::string result; std::for_each(str_values.cbegin(), str_values.cend(), boost::bind(&wierd_appender, boost::ref(result), _1)); assert(result == "We are the champions!");
The functions ref
and cref
(and bind
) are accepted to the C++11 standard and defined in the <functional>
header in the std::
namespace. None of these functions dynamically allocate memory in the heap and they do not use virtual functions. The objects returned by them are easy to optimize and they do not apply any optimization barriers for good compilers.
STL implementations of those functions may have additional optimizations to reduce compilation time or just compiler-specific optimizations, but unfortunately, some STL implementations miss the functionality of Boost versions. You may use the STL version of those functions with any Boost library, or even mix Boost and STL versions.
The
Boost.Bind
library is used widely across this book; see Chapter 6, Manipulating Tasks, and Chapter 5, Multithreading, for more examplesThe official documentation contains many more examples and a description of advanced features at http://www.boost.org/doc/libs/1_53_0/libs/bind/bind.html