10

I noticed that lambdas both work using function pointers as well as the dedicated function type using g++.

#include <functional>

typedef int(*fptr)(int, int);

int apply_p(fptr f, int a, int b) {
  return f(a, b);
}

int apply_f(std::function<int(int, int)> f, int a, int b) {
  return f(a, b);
}

void example() {
  auto add = [](int a, int b) -> int {
    return a + b;
  };

  apply_p(add, 2, 3); // doesn't give an error like I'd expect it to
  apply_f(add, 2, 3); 
}

My question is: Which of these are most idiomatic to use? And what are the dangers and/or benefits of using one over the other?

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
  • Related to [this question](http://stackoverflow.com/q/26748736/1708801) asked today. The conversion to function pointer only works if the lambda has no capture. As my answer there is another alternative using a template and treating the lambda as a Callable. `std::function` incurs overhead and it can not be optimized away like a lambda can. – Shafik Yaghmour Nov 05 '14 at 10:28
  • 1
    The second version may incur an unnecessary overhead. I would make `apply_f` a function template instead. – juanchopanza Nov 05 '14 at 10:28
  • @ShafikYaghmour wow.. random timing – Electric Coffee Nov 05 '14 at 10:28
  • what @juanchopanza says is imo the best advice: not having to care about function pointer syntax, no performance overhead of std::function and it works with everything you throw at it which matches the call - including 'raw' functions, std::functions, lambdas, your own operator classes – stijn Nov 05 '14 at 10:38

3 Answers3

10

I noticed that lambdas both work using function pointers as well as the dedicated function type

If the lambda doesn't capture anything, then it can decay to a function pointer.

Which of these are most idiomatic to use?

Neither. Use function if you want to store an arbitrary callable object. If you just want to create and use one, keep it generic:

template <typename Function>
int apply(Function && f, int a, int b) {
     return f(a,b);
}

You can go further and make the return and argument type(s) generic; I'll leave that as an exercise.

And what are the dangers and/or benefits of using one over the other?

The function pointer version only works with (non-member or static) functions and non-capturing lambdas, and doesn't allow any state to be passed; only the function itself and its arguments. The function type can wrap any callable object type, with or without state, so is more generally useful. However, this has a run-time cost: to hide the wrapped type, calling it will involve a virtual function call (or similar); and it may need dynamic memory allocation to hold a large type.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • isn't it dangerous to keep it _that_ generic? doesn't it completely lose all type-safety doing this? – Electric Coffee Nov 05 '14 at 11:00
  • 3
    @ElectricCoffee: I don't see any danger, and it certainly doesn't lose *all* type safety. It allows any type for which `f(a,b)` is a well-formed expression convertible to `int` to be used. Other types will be rejected. – Mike Seymour Nov 05 '14 at 11:03
4

I would say (C++14)

template <class Functor, class... Args>
decltype(auto) apply_functor(Functor&& f, Args&&... args) {
    return std::forward<Functor>(f)(std::forward<Args>(args)...);
}

or C++11:

template <class Functor, class... Args>
auto apply_functor(Functor&& f, Args&&... args) ->decltype(std::forward<Functor>(f)(std::forward<Args>(args)...)) {
    return std::forward<Functor>(f)(std::forward<Args>(args)...);
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • You could consider making it `std::forward(f)(std::forward(args)...);` to preserve the value category of the `f` argument as well. – Niall Nov 05 '14 at 12:16
  • @Niall thanks, fixed. Also changed `auto` to `decltype(auto)` for C++14 version to support returning by reference. – Anton Savin Nov 05 '14 at 12:23
2

If in doubt, prefer std::function:

  1. The syntax is much more straightforward and consistent with other types, consider e.g.

    typedef std::function<int (int, int)> MyAddFunction;
    

    and

    typedef int (*MyAddFunction)( int, int );
    
  2. It's more generic (it actually can take a lambda or a plain C function or a functor)

  3. It's more type safe

    apply_p(0, 0, 0); // probably crashes at runtime, dereferences null pointer
    apply_f(0, 0, 0); // doesn't even compile
    
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207