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

16,736

Solution 1

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'; 
}

Solution 2

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

Solution 3

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

}
Share:
16,736
Martin Ba
Author by

Martin Ba

C++ Link of the day: Stop Software Patents (by http://www.ffii.org/)

Updated on June 06, 2022

Comments

  • Martin Ba
    Martin Ba almost 2 years

    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.

  • Doug
    Doug over 13 years
    +1 ...and they compile to a direct call, without function-pointer calling overhead at runtime.
  • MSalters
    MSalters over 13 years
    @Doug: that's not guaranteed. Highly likely, though, since e.g. std::sort benefits a lot from that optimization.
  • Tony Delroy
    Tony Delroy over 13 years
    Your implementation there will match the typename Fun parameter to the function prototype void(*)(int), and then use the same instantiation for both calls - not inlining: that's why you actually end up passing the function pointer as a run-time argument to do_work(Fun fun).
  • Martin Ba
    Martin Ba over 13 years
    +1 for showing a decent solution with fn-pointers. But I assume this would still incur some runtime overhead to dereference the fn pointer vs. a copy&paste version for do_work_f() and do_work_g() ... ?
  • Tony Delroy
    Tony Delroy over 13 years
    @Martin: it's a crucial question - all up the optimiser. I wouldn't bet on this being inlined, whereas I would bet on a policy template member....
  • Martin Ba
    Martin Ba over 13 years
    @Tony - could you maybe link in some resource that (approx.) describes a template policy thing in the way you would adopt it for this problem?
  • Cheers and hth. - Alf
    Cheers and hth. - Alf over 13 years
    The first example (without std::ptr_fun) gives a much better chance of actual inlining -- and (although of course it depends on a lot) is thus most likely fastest, somewhat counter-intuitive perhaps. Cheers,
  • Eamon Nerbonne
    Eamon Nerbonne over 13 years
    +1 - does not deserve -1: But it could accept a function object just fine; this is the flexible, standard approach to use.
  • Eamon Nerbonne
    Eamon Nerbonne over 13 years
    return Operation(7); should be return Operation()(7);
  • Stephane Rolland
    Stephane Rolland over 13 years
    +1; Just like Martin asked, it would be great to have your implementation of the policy template member. I'm still struggling with much of the "C++ Modern Design" book which I don't really grasp. Any concrete application example is dearly welcome.
  • Doug
    Doug over 13 years
    @MSalters - definitely not guaranteed. I misread the FredOverflow's answer slightly - I thought FredOverflow had do_work accepting a template non-type function pointer argument, as the question indicates, rather than a template type argument. The non-type arguments are even more likely to compile into a direct call.
  • fredoverflow
    fredoverflow over 13 years
    @Martin: I have updated my answer, would you reconsider the downvote?
  • Tony Delroy
    Tony Delroy over 13 years
    @Martin: I changed the template policy version above a little to more directly mirror the question. While it is a little more verbose, I tend to use function names for the things the policy provides - it is common for there to be more than one function in each policy, and it often makes for more comprehensible code. , @Stephane Rolland: for a more great concrete application, consider std::sort's acceptance of a less-than policy... by not hard-coding operator<, it allows you to say compare dereferenced pointers or reverse the sort order. Is that a useful practical example?