On using std::function for member functions

Overview

A colleague recently asked me about passing function pointers under different circumstances, so I thought I would write up a few explanatory notes, as it can be a little confusing.

Consider the code snippet

class A {
  int factor;
public:
  A(int factor) : factor{factor} {}
  int invoke(int i) { return factor * i; }
};

int mult(int (*m_func)(int), int i) {
  return m_func(i);
}

If we wanted to pass the member function A::invoke to mult from a specific instance, we cannot just write

int main() {
  int ret;
  A a(3); // set factor to 3

  ret = mult(&a.invoke, 3); // can't create non-const ptr to member function

  ret = mult(&a::invoke, 3); // nope! a is not a class

  ret = mult(&A::invoke, 3); // with a different signature maybe, but
                             // who's instance is it?
  return ret;
}

This will raise all sorts of compiler errors and will not compile. This is because creating references to member variables or functions is not as straight forward as using namespace functions.

A quick fix to the above is to make the member function static, so that it can be cast to the argument type int(*)(int), however this is more often than not, not the ideal.

Pointers to class members

Consider this example:

struct B {
    int x;
};

int main() {
  B b;

  int B::*x_ptr = B::x; // pointer to name x in B

  b.*x_ptr = 3; // point to x in instance and assign

  return b.x; // will return 3
}

Here we create a pointer to the name in the class, i.e. B::x, which we can then use along with an instance to access a memory location containing a value of B::x, in this case b.x. You could consider x_ptr to be an offset pointer from B, which is used to access members from an instance.

So how could we apply this to functions? We now know how we can pass a function from a class, so we need to first adjust our mult function

int mult(int (A::*m_func)(int), A& instance, int i) {
  return (instance.*m_func)(i);
}

and then use it with something like

int main() {
  int ret;
  A a(3); // set factor to 3

  ret = mult(&A::invoke, a, 3); // pass a reference to the member function
  return ret; // returns 9
}

And in doing so, we can now pass any arbitrary function in A as an argument, and allow it to be called. However, this still isn’t exactly ideal, as we’ve now limited the interface to only use member functions. Another approach, as mentioned in this stack overflow answer would be to use a forwarding function and a void* context pointer. This is a very C-esque approach, and in C++ we can make use of either templated functions to account for a context, or, better yet, use the C++11 functional library.

Using std::function

We can write an interface for mult that is now fairly similar to the original attempt, using the template arguments <return_type(arg_type,...)>:

#include <functional>

int mult(std::function<int(int)> m_func, int i) {
  return m_func(i);
}

We can then use the std::bind to create a context-wrapped member function:

int main() {
  int ret;
  A a(3); // set factor to 3

  auto func = std::bind(&A::invoke, a, std::placeholders::_1);
  ret = mult(func, 3);

  return ret;
}

This is still quite messy, but at least our mult function is now generic enough that it can accept non-member functions, or member functions from other classes, provided the signature matches.

With lambda wrappers

Maybe the most elegant and readable way of writing the above main implementation is to use C++11 lambda expressions, allowing us to write something like

int main() {
  int ret;
  A a(3); // set factor to 3

  ret = mult([&](int i) { return a.invoke(i); }, 3);
  return ret;
}

As a note, I would encourage the use of trailing return types when using lambdas in this way, as it is not immediately obvious what or sometimes even if a lambda returns. Thus, would use

ret = mult([&](int i) -> int { return a.invoke(i); }, 3);

An additional benefit of using lambda wrappers is that the compiler can heavily optimise lifetime considerations. For example, the above written with std::bind generates around 1200 lines of assembly with a considerably longer main, whereas the lambda case only generates 500 lines, with a highly reduced main.