Tuesday, April 23, 2013

Making functions: How to provide a simple interface when working with std::function

While working on the lundi project, a minor, but not negligible, issue arose: How can we spare the user from producing manually all those std::functions from their callable objects?

The hypothetical expose function template takes std::function<R(P...)> and exposes them to the lua VM. A typical use case would be:

std::function<void(int, int)> lamarck = [](int a, int b){ 
  std::cout << a << b; 
};
expose(lamarck);

That's nice and all, but why do you need to write the signature of the function you just typed? That's redundant. The thing is, lambdas are actually function objects. The preceding lambda declaration is roughly equivalent to

struct blanche {
  void operator()(int a, int b) const { 
    std::cout << a << b; 
  }
};
std::function<void(int, int)> lamarck = blanche();

Note the braces; we're not assigning a type here.

Now, we could put pretty much anything in function objects; the only thing that makes them function objects are the presence of operator(). That would be insane, of course, to provide an implicit conversion to std::function.

Nobody is keeping you from doing it, though:

template<typename R, typename... P>
std::function<R(P...)> make_function(R(func const &)(P...)) {
  return std::function<R(P...)>(func);
}

Well, that's the easy part, but what about function objects? You can't just match a function object's call signature because it's not easily accessible. You can, though, look at his operator() to work out the parameter and return types. Note that it's not possible if operator() is a template or if it's overloaded.

This is where decltype is useful:

typedef decltype(&T::operator()) ptmf_type;
ptmf_type should look (in the case of void(int, int)) like void (mytype::*)(int, int) const, which can be matched in a template to extract all the needed types. We might as well work out directly the function type:
template<typename T>
struct ptmf_traits {
  typedef void type;
};

template<typename O, typename R, typename... P>
struct ptmf_traits<R(O::*)(P...) const> {
  typedef std::function<R(P...)> type;
};

Now, you can just use ptmf_traits<decltype(&LambdaType::operator())>::type to get a function type:

template<typename Func>
ptmf_traits<decltype(&Func::operator())>::type 
make_function_from_lambda(Func const &func) {
  return func;
}

With this function, the type of the std::function produced is fully deduced from the argument type:

auto my_func = make_function_from_lambda([](int a, int b){
  std::cout << a << b;
});
// my_func's type is std::function<void(int, int)>

Of course, you need a bit of boilerplate to accept free functions and maybe some tweaking to make it do what you want, but the core idea is there. You can find a more complete version here.

You may want to follow me on twitter.