Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 978
Peter Avatar asked May 08 '13 11:05

Peter


1 Answers

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.

like image 53
Andy Prowl Avatar answered Oct 26 '22 17:10

Andy Prowl