13

In C++, there are two ways of passing a function into another function that seem equivalent.

#include <iostream>

int add1(int i){ return i+1; }
int add2(int i){ return i+2; }

template <int (*T)(int) >
void doTemplate(int i){
    std::cout << "Do Template: " << T(i) << "\n";
}

void doParam(int i, int (*f)(int)){
    std::cout << "Do Param: " << f(i) << "\n";
}

int main(){
    doTemplate<add1>(0);
    doTemplate<add2>(0);

    doParam(0, add1);
    doParam(0, add2);
}

doTemplate accepts a function as a template argument, whereas doParam accepts it as a function pointer, and they both seem to give the same result.

What are the trade-offs between using each method?

Peter
  • 1,381
  • 2
  • 13
  • 24

2 Answers2

12

The template-based version allows the compiler to inline the call, because the address of the function is known at compile-time. Obviously, the disadvantage is that the address of the function has to be known at compile-time (since you are using it as a template argument), and sometimes this may not be possible.

That brings us to the second case, where the function pointer may be determined only at run-time, thus making it impossible for the compiler to perform the inlining, but giving you the flexibility of determining at run-time the function to be called:

bool runtimeBooleanExpr = /* ... */;
doParam(0, runtimeBooleanExpr ? add1 : add2);

Notice, however, that there is a third way:

template<typename F>
void doParam(int i, F f){
    std::cout << "Do Param: " << f(i) << "\n";
}

Which gives you more flexibility and still has the advantage of knowing at compile-time what function is going to be called:

doParam(0, add1);
doParam(0, add2);

And it also allows passing any callable object instead of a function pointer:

doParam(0, my_functor());

int fortyTwo = 42;
doParam(0, [=] (int i) { return i + fortyTwo; /* or whatever... */ }

For completeness, there is also a fourth way, using std::function:

void doParam(int x, std::function<int(int)> f);

Which has the same level of generality (in that you can pass any callable object), but also allows you to determine the callable object at run-time - most likely with a performance penalty, since (once again) inlining becomes impossible for the compiler.

For a further discussion of the last two options, also see this Q&A on StackOverflow.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • could you explain the `=` in the lambda? i'm still rubbing my eyes though:-) – Koushik Shetty May 08 '13 at 12:04
  • @Koushik: It captures the `fortyTwo` variable by value. A non-capturing lambda would not have been a good examples, since those can be implicitly converted to function pointers ;) – Andy Prowl May 08 '13 at 12:06
  • the [=] makes it default to capturing value by value (rather than reference). SO it captures the value 42 for fortyTwo rather than a reference to it. Which means if you assigned 111 to it afterwards the lambda would still use the value 42 it captured. – jcoder May 08 '13 at 12:08
  • @Koushik: You mean, as a way of capturing? No, not in C++11. If you mean as the lambda's parameter (`[] (int const&) { ... }`), yes, you can, but for `int` I would not bother accepting by reference to `const` – Andy Prowl May 08 '13 at 12:11
  • @AndyProwl i did mean capturing. now learnt thanks:-) and thanks to jcoder too:-) – Koushik Shetty May 08 '13 at 12:13
  • Using `template void doParam(int i, F f)` doesn't tell teh – ymett May 08 '13 at 13:14
5

Template parameters

  1. have to be known at compile time.
  2. lead to one function instantation for every distinct value of the parameter (so-called template bloat)
  3. the called function is transparant to the compiler (inlining, but could lead to even more bloat, double-edged sword)
  4. the calling function can be overloaded for particular values of the parameter, without modifying existing code

Function pointers

  1. are passed at run time.
  2. only lead to one calling function (smaller object code)
  3. the called function is typically opaque to the compiler (no inlining)
  4. the calling function needs a runtime if/switch to do special stuff for special values of the parameter, this is brittle

When to use which version: if you need speed and a lot of customization, use templates. If you need flexibility at runtime, but not in the implementation, use function pointers.

As @AndyProwl points out: if you have a C++11 compiler, function pointers are generalized to callable objects such as std::function and lambda expressions. This opens up a whole new can of worms (in a good sense).

TemplateRex
  • 69,038
  • 19
  • 164
  • 304