2

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?

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • umm... you mean like `std::bind`? – Richard Hodges Mar 03 '16 at 22:18
  • Does bind somewhere have a special case for a function pointer template parameter? If it does not, then there will be a layer of indirection inside the lambda, which doesn't help me. My understanding from this talk (https://www.youtube.com/watch?v=zt7ThwVfap0) is to generally avoid bind. – Nir Friedman Mar 03 '16 at 22:21
  • bind is specialised for function pointers, member function pointers, member functions bound to shared_pointers etc. Why would you avoid bind? It's part of the standard library and therefore able to use magic tricks unavailable to you standards-compliant code. – Richard Hodges Mar 03 '16 at 22:26
  • As you can see, bind is not successful at removing the indirection, unlike my version: https://goo.gl/hDk4mZ. Also, with bind you have to specify the clunky _1, _2, etc. – Nir Friedman Mar 03 '16 at 23:04
  • And you should explicitly state that you are doing this just to work around the issue that some compilers optimize code using lambdas slightly better than code using functors. – jotik Mar 03 '16 at 23:44
  • No, a functor in C++ parlance is a function object. A lambda is an anonymous functor. And this is not a workaround, this has to do with fundamental differences in how types are handled between function pointers and functors (lambdas and otherwise). All compilers will optimize this approximately the same way in non-toy examples. And I write about this exact topic, and the fact that it enables inlining, in the intro to the question. – Nir Friedman Mar 03 '16 at 23:49

2 Answers2

3

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Won't work in C++11 which does not support generic lambdas. – jotik Mar 03 '16 at 23:07
  • Well, if we're using macros, my solution can also be wrapped in a macro if it comes to that. Your solution allows overloading which is nice, but mine keeps the proper signature, and is not a polymorphic lambda which has advantages. Still, this is at least a nice alternative. – Nir Friedman Mar 03 '16 at 23:08
  • @NirFriedman The function pointer already keeps the proper signature. If not to aid in overloaded names, why do you need a function object? – Barry Mar 03 '16 at 23:28
  • @Barry Performance reasons generally. If you pass a function pointer to e.g. std::sort as the comparator, it will not get inlined (unless the entire sort gets inlined, which is unlikely). If you convert it to a lambda first using my or your technique, it will be. – Nir Friedman Mar 03 '16 at 23:50
  • @NirFriedman That seems... questionable. Youre claiming the compiler won't inline the body of `f` as-is, but will inline the body of `f` in `[](auto x){ return f (x);}`? – Barry Mar 04 '16 at 00:09
  • 2
    @Barry Not sure why it seems that way to you, I suspect it's just a miscommunication. When you call a typical templated higher order function like std::sort with a function like f, the type it deduces for the Comparator will be bool(*)(double, double) or something similar. So if sort itself does not get inlined, it does not have enough information to inline f, it has to rely on the passed address at runtime. However, if you pass sort a lambda that calls f, then f gets inlined into the lambda at the current scope. Sort deduces a unique type for the lambda, giving it enough info to inline – Nir Friedman Mar 04 '16 at 00:23
  • f without needing to be inlined itself. It's a bit long but you can see it here: https://goo.gl/TrD4Ug. Notice that my_sort has a mov ecx, comp(double, double) instruction that my_sort2 lacks. – Nir Friedman Mar 04 '16 at 00:31
  • @Barry It does not, for clang 3.7.1, that instruction is still there. – Nir Friedman Mar 04 '16 at 01:22
1

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);
Niall
  • 30,036
  • 10
  • 99
  • 142
  • Doesn't work to remove indirection, compare assembly of your method to mine: https://goo.gl/IoxcW2. – Nir Friedman Mar 04 '16 at 14:46
  • 1
    I see that, but a change in compiler changes that as well. https://goo.gl/37zsIl. Some of this will depend on the compiler and it's optimizations. The runtime parameter in the answer does change somewhat. I'll consider some alternatives and come back to this. – Niall Mar 04 '16 at 18:28
  • Cool, thanks. The code generated by various versions of gcc was quite different from clang (whose results I found more intuitive), so that was interesting. – Nir Friedman Mar 04 '16 at 19:57
  • Short of the macro, there seems to be little that can be done with templates when the template arguments require both the type and a value (the pointer here). 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). – Niall Mar 05 '16 at 13:20
  • 1
    Either way, it is interesting to note that, if you modify the function being called to be more complex, https://goo.gl/KBJd5v, then all variations produce a mix of indirection and inline code. If I think of or find something else, I'll let you know... – Niall Mar 05 '16 at 13:20
  • As you noted in other comments, it is important that `f` can be (and is) inlined (this is often up to the compilers optimisations) to begin with; else, as here, https://goo.gl/ocbJq4 (some syntax changes as well), there is again little difference between all the options. – Niall Mar 05 '16 at 14:52
  • Yes, if f is large enough to not be inlined it's not likely to be relevant. Interestingly though your code still shows a key difference: with your approach, at some point a function address is pushed into a register, and then later on that register is used for a call instruction. With mine, all of the jumps/calls are hardcoded. – Nir Friedman Mar 05 '16 at 20:38