views:

603

answers:

5

I'm looking for the rules involving passing C++ templates functions as arguments. This is supported by C++ as shown by an example here:

#include <iostream>

void add1(int &v)
{
  v+=1;
}

void add2(int &v)
{
  v+=2;
}

template <void (*T)(int &)>
void doOperation()
{
  int temp=0;
  T(temp);
  std::cout << "Result is " << temp << std::endl;
}

int main()
{
  doOperation<add1>();
  doOperation<add2>();
}

Learning about this technique is difficult, however. Googling for "function as a template argument" doesn't lead to much. And the classic C++ Templates The Complete Guide surprisingly also doesn't discuss it (at least not from my search).

The questions I have are whether this is valid C++ (or just some widely supported extension).

Also, is there a way to allow a functor with the same signature to be used interchangeably with explicit functions during this kind of template invocation? The following does not work in the above program , at least in Visual C, because the syntax is obviously wrong. It'd be nice to be able to switch out a function for a functor and vice versa, similar to the way you can pass a function pointer or functor to the std::sort algorithm if you want to define a custom comparison operation.

   struct add3 {
      void operator() (int &v) {v+=3;}
    };
...

    doOperation<add3>();

Pointers to a web link or two, or a page in the C++ Templates book would be appreciated!

+6  A: 

Yes, it is valid.

As for making it work with functors as well, the usual solution is something like this instead:

template <typename F>
void doOperation(F f)
{
  int temp=0;
  f(temp);
  std::cout << "Result is " << temp << std::endl;
}

which can now be called as either:

doOperation(add2);
doOperation(add3());

The problem with this is that if it makes it tricky for the compiler to inline the call to add2, since all the compiler knows is that a function pointer type void (*)(int &) is being passed to doOperation. (But add3, being a functor, can be inlined easily. Here, the compiler knows that an object of type add3 is passed to the function, which means that the function to call is add3::operator(), and not just some unknown function pointer.)

jalf
Now here's an interesting question. When passed a function name, it's NOT like there's a function pointer involved. It's an explicit function, given at compile time. So the compiler knows exactly what it's got at compile time.
SPWorley
Could a template overload be used to make the function pointer and the functor cases have identical syntax? Would there be any performance benefit? Especially for stateless functors which effectively have no instances and therefore we want to avoid any case where the compiler may stop inlining and optimization.
SPWorley
There is an advantage to using functors over function pointers. The functor can be instanciated inside the class and thus provides more opertunity to the compiler for optimizations (such as inlining). The compiler would be hard pressed to optimize a call over a function pointer.
Martin York
When the function is used in a template parameter it 'decays' into a pointer to the passed function. It's analagous to how arrays decay into pointers when passed as arguments to parameters. Of course, the pointer value is known at compile time and must point to a function with external linkage so that compiler can use this information for optimization purposes.
Charles Bailey
My comment above refers to template parameters taking a non-type function pointer parameter. In this case the template instantiation can be inlined as the compiler knows the value of the pointer at compile time. In jalf's example the template takes a type parameter and the type of the template parameter and the value of the function argument together determine the function called, a function object can be better optimized.
Charles Bailey
@Arno: For optimization purposes, I'd go with the functor, whether or not it is stateless.
jalf
@Charles, so actually such a template is really passing a function POINTER? But that's really bizarre.. you can't make a template that takes a constant class instance pointer, for example. And if it's a pointer, than it's opaque to the compiler and it could never inline.
SPWorley
The template in your example is specialized for a function pointer, yes. But the value of that pointer is known at compile-time, so in principle it can still be inlined. I don't know if compilers actually do inline it though.
jalf
+1  A: 

The reason your functor example does not work is that you need an instance to invoke the operator().

Nikolai N Fetissov
+2  A: 

In your template

template <void (*T)(int &)>
void doOperation()

The parameter T is a non-type template parameter. This means that the behaviour of the template function changes with the value of the parameter (which must be fixed at compile time, which function pointer constants are).

If you want somthing that works with both function objects and function parameters you need a typed template. When you do this, though, you also need to provide an object instance (either function object instance or a function pointer) to the function at run time.

template <class T>
void doOperation(T t)
{
  int temp=0;
  t(temp);
  std::cout << "Result is " << temp << std::endl;
}

There are some minor performance considerations. This new version may be less efficient with function pointer arguments as the particular function pointer is only derefenced and called at run time whereas your function pointer template can be optimized (possibly the function call inlined) based on the particular function pointer used. Function objects can often be very efficiently expanded with the typed template, though as the particular operator() is completely determined by the type of the function object.

Charles Bailey
A: 

I think you are trying to write something like this.

struct Square
{
    double operator()(double number) { return number * number; }
};

template <class Function>
double integrate(Function& f, double a, double b, unsigned int intervals)
{
    double delta = (b - a) / intervals, sum = 0.0;

    while(a < b)
    {
     sum += f(a) * delta;
     a += delta;
    }

    return sum;
}

. .

std::cout << "interval : " << i << tab << tab << "intgeration = "
 << integrate(Square(), 0.0, 1.0, 10) << std::endl;
AraK
A: 

Template parameters can be either parameterized by type (typename T) or by value (int X).

The "traditional" C++ way of templating a piece of code is to use a functor - that is, the code is in an object, and the object thus gives the code unique type.

When working with traditional functions, this technique doesn't work well, because a change in type doesn't indicate a specific function - rather it specifies only the signature of many possible functions. So:

template<typename OP>
int do_op(int a, int b, OP op)
{
  return op(a,b,);
}
int add(int a, b) { return a + b; }
...

int c = do_op(4,5,add);

Isn't equivalent to the functor case. In this example, do_op is instantiated for all function pointers whose signature is int X (int, int). The compiler would have to be pretty aggressive to fully inline this case. (I wouldn't rule it out though, as compiler optimization has gotten pretty advanced.)

One way to tell that this code doesn't quite do what we want is:

int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);

is still legal, and clearly this is not getting inlined. To get full inlining, we need to template by value, so the function is fully available in the template. typedef int(*binary_int_op)(int, int); // signature for all valid template params template int add(int a, int b) { return op(a,b); } int add(int a, b) { return a + b; } ... int c = do_op(4,5);

In this case, each instantiated version of do_op is instantiated with a specific function already available. Thus we expect the code for do_op to look a lot like "return a + b". (Lisp programmers, stop your smurking!)

We can also confirm that this is closer to what we want because this:

int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);

will fail to compile. GCC says: "error: 'func_ptr' cannot appear in a constant-expression. In other words, I can't fully expand do_op because you haven't given me enough info at compiler time to know what our op is.

So if the second example is really fully inlining our op, and the first is not, what good is the template? What is it doing? The answer is: type coercion. This riff on the first example will work:

template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
float fadd(float a, float b) { return a+b; }
...
int c = do_op(4,5,fadd);

That example will work! (I am not suggesting it is good C++ but...) What has happened is do_op has been templated around the signatures of the various functions, and each separate instantiation will write different type coercion code. So the instantiated code for do_op with fadd looks something like:

convert a and b from int to float.
call the function ptr op with float a and float b.
convert the result back to int and return it.

By comparison, our by-value case requires an exact match on the function arguments.

Ben Supnik