Nir Friedman
Nir Friedman

Reputation: 17714

Function to Lambda

It's often useful in C++ to work with lambdas and function objects, rather than function pointers. The reason being that a lambda's or function object's type fully encodes what is getting called (and thus allows inlining), whereas a function pointer's type merely encodes the signature. I had the interesting thought that it might be useful when using generic code to create a lambda that calls a given function. In other words, a higher order function that given a specified function, returns a lambda that calls it. To avoid any indirection, function pointer non-type template parameters have to be used. The problem is that to specify such a non-type template parameter, you have to know the signature of the function. I'm not able to get around needing to pass the function in question twice: once to deduce the types, and once to actually specialize on the function. My best attempt looks like this:

template <class T>
struct makeLambdaHelper;

template <class R, class ... Args>
struct makeLambdaHelper<R(*)(Args...)>
{
  template <void(*F)(Args...)>
  static auto blah() {
    return [] (Args && ... args) {
      return F(std::forward<Args>(args)...);
    };
  }
};

To use this, you would do:

void f(int, int) { std::cerr << "f\n"; };
...
auto lam = makeLambdaHelper<decltype(&f)>::blah<f>();
lam(0,0);

When f's type is passed as the template argument to makeLambdaHelper, first its signature is deduced. Then the struct has a static function that declares a non-type template parameter; so we pass f itself, and get out a lambda. Having to pass f twice like this is pretty ugly; is it possible to do better, maybe by doing something clever with default template parameters?

Upvotes: 2

Views: 630

Answers (2)

Niall
Niall

Reputation: 30624

Given the sample usage of your code;

makeLambdaHelper<decltype(&f)>::blah<f>();

You declare a class, with the signature of the function f (using a decltype), then you again reference the function f in the call to the function blah - you reference f twice, once for the value (the pointer) and once for the type (the signature). I think you could minimise the syntax here and combine them (using template type deduction).

void f(int, int) { std::cout << "f\n"; }

template <class R, class ... Args>
auto make_lambda(R(*F)(Args...))
{
    return [F] (Args && ... args) {// F pointer captured by value
      return F(std::forward<Args>(args)...);
    };
}

int main()
{
    auto lam = make_lambda(&f);
    lam(0,0);
}

Demo code.

The function make_lambda takes the pointer to the function f once and use template deduction to get the required signature.


As noted in the comments, a reasonable portion of the question is the issue of not having an indirection (including the tail call) in the generated code; the indirection is dependent on a number of factors, including the compiler used, level of optimisations applied and the inline-ability of the function f to begin with. See these samples for a change in compiler, a change in the complexity of the function, and a change to the "inline-ability" of f; all produce varying results of indirection, tail calls and inlined code. As you noted in the comments, it is important that f can be (and is) inlined (this is often up to the compilers optimisations) to begin with; else, there is little difference between all the options.

On the syntax, short of the macro, there seems to be little that can be done with class templates when the template arguments require both the type and a value (the pointer here); function templates can deduce the type based on the value. Bear in mind that if the performance of the indirection is measurable and significant in the application, then you may need to bite the bullet and use the longer form of the syntax, else favour a more maintainable shorter and obvious syntax (anyone one of them).


Given the original syntax, it could be cleaned up a little; not that it improves any of the fundamental issues, only that to place the type and value "near" each other in the template syntax would aid in the maintenance of the code (your results may vary of course);

template <class F, F>
struct lambda_maker;

template <class R, class ... Args, R(*F)(Args...)>
struct lambda_maker<R(Args...), F>
{
  static auto make() {
    return [] (Args && ... args) {
      return F(std::forward<Args>(args)...);
    };
  }
};
// ...
auto lam = lambda_maker<decltype(f), f>::make();
lam(2, 3);

Upvotes: 1

Barry
Barry

Reputation: 303890

If you just want to wrap a function pointer in a function object, I'd recommend a macro:

#define WRAP_FN(f) [](auto&&... args) -> decltype(auto) { \
    return f(std::forward<decltype(args)>(args)...); };

To be used like:

auto lam = WRAP_FN(foo);

The advantage of the macro is that it'll additionally handle overloaded names - so that you can pass overloaded functions into std algorithms and have that do what you want it to.

Upvotes: 3

Related Questions