Skip to content

Callable Objects: Part 2

Nathan Crookston edited this page Aug 15, 2014 · 1 revision

Exercise 1:

First, we'll create an adaptor for print_function that can be called on a list of ints.

void print_function(const std::string& prefix, int val)
{ std::cout << prefix << val << std::endl; }

struct print_function_adaptor
{
  std::string prefix_;
  explicit print_function_adaptor(const std::string& prefix)
   : prefix_(prefix) {}

  void operator()(int val) const
  {
    print_function(prefix_, val);
  }
};
//Usage:
std::for_each(vals.begin(), vals.end(), print_function_adaptor(" Value: "));

Next, we write an adaptor that can call the print method of print_object:

struct print_object
{
  typedef void result_type;
  explicit print_object(const std::string& prefix_)
    : prefix(prefix_)
  {}

  void print(int val) const
  { std::cout << prefix << val << std::endl; }

private:
  std::string prefix;
};

struct print_object_adaptor
{
  print_object obj_;
  explicit print_object_adaptor(const print_object& obj) : obj_(obj) {}

  void operator()(int val) const
  { obj_.print(val); }
};
//Usage:
print_object obj(" Value: ");
std::for_each(vals.begin(), vals.end(), print_object_adaptor(obj));

Exercise 2:

Writing adaptors for those functions was pretty verbose -- compare the length of code in the former case if we didn't use std::for_each at all:

for(auto i = vals.begin(), end = vals.end(); i != end; ++i)
  print_function(" Value: ", *i);

Yet, using algorithms is strongly encouraged [^1]. (A future training will expand on that.) To reduce the need to write boilerplate, the C++98 standard included several adaptors. We won't discuss those, however because boost::bind (later moved into the standard) was created later and is both easier to use and more general. While I use C++11 lambda functions instead of bind in new code, bind is both an impressively small & useful library, and widely used enough to merit a basic understanding. We'll show how to rewrite the 'usage' sections of exercise 1 to use std::bind.

using namespace std::placeholders;
std::for_each(vals.begin(), vals.end(),
  std::bind(print_function, " Value: ", _1));
print_object obj(" Value: ");
std::for_each(vals.begin(), vals.end(),
  std::bind(&print_object::print, obj, _1));

It's clear that bind required a lot less typing, though readability was sacrificed (exactly how much depends on your familiarity with bind). It filled a need very well, but it has now been superseded by C++11 lambdas[^2], as the following examples show.


Exercise 3:

When we passed obj into bind above, it was copied [^3]. It wasn't a large object, so we probably wouldn't notice the cost. However, sometimes we do have an object we don't wish to copy -- it's either impossible to copy, or too expensive in terms of memory or time. In those cases, std::ref and std::cref help: We'll show with the following:

struct expensive_copyable_object
{
  expensive_copyable_object() : v(10000, 5) {}
  expensive_copyable_object(const expensive_copyable_object& rhs) : v(rhs.v)
  { std::cout << "Copy called!" << std::endl; }//Keep this from being called.

  std::vector<int> v;
};

void print_difference(int i, const expensive_copyable_object& obj,
  bool& was_changed)
{
  std::cout << i - obj.v[0] << std::endl;
  was_changed = true;
}

const ExpensiveCopyableObject obj;
bool was_changed = false;
std::for_each(vals.begin(), vals.end(),
  std::bind(&print_difference, _1, boost::cref(obj), was_changed));

Question 1:

Above we used std::cref. Could we have used std::ref? When should you use std::ref versus std::cref?

Answer 1:

Both std::ref and std::cref should be used when the argument should not (or cannot) be copied. std::cref should be used when the object should be treated as immutable by the bound function, use std::ref when it may be treated as mutable.

It is true that in the previous example, both ref and cref would produce identical results -- mutable objects (type T) passed using std::ref can be converted to either T & or const T&, while immutable objects passed using std::ref can only be converted to const T&. Mutable and immutable objects passed via std::cref can only be implicitly converted to const T&. A cooked-up example of how this could cause trouble:

void bar(int i const Obj& immutable);
void foo(int i, Obj& rMutable);

...

int main()
{
  //Object we expect to be unchanged after for_each calls below.
  Obj obj1;
  const Obj obj2;

  std::vector<int> ints(50);
  ...
  //Calls bar for each int.  obj1 is passed by reference, but when bar is called,
  // that reference is converted to a const reference.
  std::for_each(ints.begin(), ints.end(), std::bind(&bar, _1, std::ref(obj1));
  //obj2 is passed by const reference here, since it's declared const.
  std::for_each(ints.begin(), ints.end(), std::bind(&bar, _1, std::ref(obj2));

  //Calls bar for each int.  obj1 is passed by reference and foo presumably will
  // modify it.
  std::for_each(ints.begin(), ints.end(), std::bind(&foo, _1, std::ref(obj1));
  //Oops, we thought obj1 and obj2 would be unchanged at this point...

Avoiding these kinds of bugs is usually best done with the compiler. Here are examples which will fail to compile, alerting us that we're trying to modify obj1 or obj2:

  //Compile error!  obj2 is declared const!
  std::for_each(ints.begin(), ints.end(), std::bind(&foo, _1, std::ref(obj2));
  //Compile error! obj1 is passed by const reference!
  std::for_each(ints.begin(), ints.end(), std::bind(&foo, _1, std::cref(obj1));

Even if you remember the details of when ref acts like cref, don't make everyone who reads your code know them! Remember that explicit is better than implicit! Convey your intent by using std::cref for const T&, std::ref for T&.


Exercise 4:

bind can also be a little tricky when it comes to using overloaded functions. Consider the following:

struct Obj
{
  void a(int)
  { std::cout << "Non-const a!" << std::endl; }

  void a(int) const
  { std::cout << "Const a!" << std::endl; }
};

const Obj obj;
std::for_each(vals.begin(), vals.end(),
  std::bind(&Obj::a, std::cref(obj), _1));

Since obj is a const&, it's pretty obvious to us that we want to call the const overload of a. It's not so cut-and-dry for the compiler, however. It will error out. To resolve the apparent ambiguity, we can use a cast:

std::for_each(vals.begin(), vals.end(),
  std::bind(static_cast<void (Obj::*)(int) const>(&Obj::a), std::cref(obj), _1));

Not pretty, but effective. Note that C++11 lambdas don't suffer from this problem, as the compiler knows the context where the function is being called, and which arguments are being passed. Thus we could just write:

std::for_each(vals.begin(), vals.end(), [&obj](int val) { obj.a(val); });

Exercise5/Question 2:

Consider the following code:

void default_function(int i1, int i2, double d = 5.)
{ std::cout << i1 + i2 - d << std::endl; }
...
//Uninformative error!  bind has no way of knowing that default_function has default arguments.
std::bind(&default_function, _1, 1000);

How may the code be changed to avoid the error in the bind call?

Answer 2:

There are a few different ways to bind this. The most obvious is to pass the default parameter:

std::bind(&default_function, _1, 1000, 5.));

This has the obvious downside that if the default value of default_function is changed, we need to find all places where it's bound, determine if the value was intended to always match the default value, and modify it accordingly.

Another solution is to declare an adaptor function that we can bind:

void no_default_function(int i1, int i2)
{ default_function(i1, i2); }

std::bind(&no_default_function, _1, 1000);

While this works, the point of std::bind is avoiding having to clutter your code with adaptor functions. It's a short hop from the above to:

void no_bind_function(int i)
{ default_function(i, 1000); }

Probably the best method that assumes the use of std::bind is to declare the default value in a named variable which is referenced in both places:

static const double DEFAULT_DEFAULTFUNCTION_VALUE = 5.;
default_function(int i1, int i2, double d = DEFAULT_DEFAULTFUNCTION_VALUE);
...
std::bind(&default_function, _1, 1000, DEFAULT_DEFAULTFUNCTION_VALUE);

Even were the name more descriptive and concise, the usage is obviously more clunky that if we were just calling the function normally:

int i;
default_function(i, 1000);

When called this way, the compiler knows what default arguments are present, and can use that knowledge when determining which function you're calling, and what variable values are. See exercise 6 for a potential solution:

Exercise 6:

Here are C++ lambda-equivalents to the exercises we did earlier with bind:

std::for_each(vals.begin(), vals.end(), [](int val) { print_function(" Value: ", val); });
std::for_each(vals.begin(), vals.end(), [&](int val) { obj1.print(val); });
std::for_each(vals.begin(), vals.end(), [&](int val) { print_difference(val, obj2, was_changed); });
std::for_each(vals.begin(), vals.end(), [&](int val) { obj3.a(val); });
std::for_each(vals.begin(), vals.end(), [](int val) { default_function(val, 1000); });

The key observation is that lambdas makes natural usage possible. I've found that using lambdas in place of std::bind improves readability.


Notes:

[^1] See http://www.drdobbs.com/stl-algorithms-vs-hand-written-loops/184401446 for a nice treatment.

[^2] Stephan Lavavej, Microsoft's STL maintainer seems to agree

[^3] The arguments are copied for a good reason

Further reading:

Effective C++ items 35 & 54, Modern C++ Design chapter 5

Clone this wiki locally