Is it possible to have a function(-name) as a template parameter in C++?

Viewed 17941

I don't want function pointer overhead, I just want the same code for two different functions with the same signature:

void f(int x);
void g(int x);

...

template<typename F>
void do_work()
{
  int v = calculate();
  F(v);
}

...

do_work<f>();
do_work<g>();

Is this possible?


To clear up possible confusion: With "template parameter" I mean the parameter/argument to the template and not a function parameter whose type is templated.

5 Answers

With any modern C++ compiler you don't pay the function pointer overhead when the function pointer value is known at compile time, like in your example, because the compiler then replaces the indirection with a direct function call.

Consider your example code, slightly modified:

void f(int x);
void g(int x);
int calculate();

template<typename F>
void do_work(F f)
{
    int v = calculate();
    f(v);
}

static void trabajar(void (f)(int))
{
    int v = calculate();
    f(v);
}

void foo()
{
    do_work(f);
    do_work(g);
}

void bar()
{
    trabajar(f);
    trabajar(g);
}

void baz()
{
    f(calculate());
    g(calculate());
}

For all the foo(), bar() and baz() variants GCC 12.1 generates the same direct function invoking code:

        sub     rsp, 8
        call    calculate()
        mov     edi, eax
        call    f(int)
        call    calculate()
        add     rsp, 8
        mov     edi, eax
        jmp     g(int)

See also: compiler explorer

Notes:

  • foo() and bar() invoke f() and g() via function pointers
  • baz() invokes f() and g() directly
  • for you use case it isn't necessary to templatize your do_work() function, i.e. trabajar() is the non-template version that only accepts function pointers
  • templating do_work() like this has the advantage that you can also supply some compatible function object (functor) instead of a function pointer
  • when templating do_work() like this and supplying a function address as its argument (like in foo()), the template parameter F is type-inferred to void (*)(int), i.e. a function pointer type

It's perhaps worth mentioning that even when using a C++ function object there isn't really a guarantee that your C++ compiler inlines the classes function call operator. However, any serious C++ will do it.

Related