Peter
Peter

Reputation: 1471

C++ Function passed as Template Argument vs Parameter

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?

Upvotes: 15

Views: 4793

Answers (2)

Andy Prowl
Andy Prowl

Reputation: 126562

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.

Upvotes: 13

TemplateRex
TemplateRex

Reputation: 70556

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).

Upvotes: 5

Related Questions