简体   繁体   中英

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

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 .

Your idea's ok, but you're not passing in a type but a value (specifically, a function pointer>. Alternatively, pass a template policy providing functions - it's a good idea to read Modern C++ Design by Andrei Alexandrescu.

#include <iostream>

int f(int x) { return 2 * x; }
int g(int x) { return -3 * x; }

typedef int (*F)(int);

template<F f> 
int do_work() 
{ 
    return f(7);
} 

int main()
{
    std::cout << do_work<f>() << '\n'
              << do_work<g>() << '\n'; 
}

OR

int calculate() { return 4; }

struct F { int do_something_with(int x) { return 2 * x; } };
struct G { int do_something_with(int x) { return -3 * x; } };
// or, make these functions static and use Operator::do_something_with() below...

template<typename Operation> 
int do_work() 
{ 
    int v = calculate(7);
    return Operation().do_something_with(v);
} 

int main()
{
    std::cout << do_work<F>() << '\n'
              << do_work<G>() << '\n'; 
}

You can have pointers to functions as template parameters, but function objects are more "C++ish". However, you can write your function template in a way that accepts both variants:

#include <iostream>

void f(int x)
{
    std::cout << "inside function f\n";
}

struct g
{
    void operator()(int x)
    {
        std::cout << "inside function object g\n";
    }
};

template <typename Functor>
void do_work(Functor fun)
{
    fun(42);
}

int main()
{
    // template argument is automatically deduced
    do_work(&f);
    // but we could also specify it explicitly
    do_work<void(*)(int)>(&f);

    // template argument is automatically deduced
    do_work(g());
    // but we could also specify it explicitly
    do_work<g>(g());
}

Here, the name Functor hints at any type that is callable via f(x) syntax. Functions support this syntax naturally, and in the case of function objects, f(x) is syntactic sugar for f.operator()(x) .

One approach that is highly likely to generate the direct function call, because it gives the compiler no option, is to use a static member function:

struct F { static void func(int x) { /*whatever*/ } };
struct G { static void func(int x) { /*whatever*/ } };

template<class T>
void do_work() {
    T::func(calculate());
}

No function pointers, no temporaries, and no unnecessary this . I guarantee nothing, of course, but generated code should be reasonable even with optimization disabled.

No, you need to wrap the functions in a wrapper class with operator() . Here is an example:

class Functor_f
{
public:
    void operator()(int x)
    {
    }
};

class Functor_g
{
    public:
    void operator()(int x)
    {
    }
};



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


int main()
{
    do_work<Functor_f>();
    do_work<Functor_g>();

}

You can use std::ptr_fun to do this wrapping automatically for you. For example:

void f(int x)
{
}

void g(int x)
{
}

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


int main()
{
    do_work(std::ptr_fun(f));
    do_work(std::ptr_fun(g));

}

With any modern C++ compiler you don't pay the function pointer overhead when the function pointer value is known at compile this, 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, ie trabajar() is the non-template version that only accepts function pointers
  • templating do_work() like this has the advantage that you can also supply a function pointer or some compatible function object (functor)
  • 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) , ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM