Función pasada como argumento de plantilla

Estoy buscando las reglas que implican pasar funciones de plantillas de C ++ como argumentos.

Esto es compatible con C ++ como se muestra en un ejemplo aquí:

#include  void add1(int &v) { v+=1; } void add2(int &v) { v+=2; } template  void doOperation() { int temp=0; T(temp); std::cout << "Result is " << temp << std::endl; } int main() { doOperation(); doOperation(); } 

Sin embargo, aprender sobre esta técnica es difícil. Google para “función como argumento de plantilla” no conduce a mucho. Y las plantillas clásicas de C ++ The Complete Guide sorprendentemente tampoco lo discuten (al menos no de mi búsqueda).

Las preguntas que tengo son si esto es válido C ++ (o solo alguna extensión ampliamente soportada).

Además, ¿hay alguna manera de permitir que un funtor con la misma firma se use indistintamente con funciones explícitas durante este tipo de invocación de plantilla?

Lo siguiente no funciona en el progtwig anterior, al menos en Visual C ++ , porque la syntax es obviamente incorrecta. Sería bueno poder cambiar una función para un funtor y viceversa, similar a la forma en que puede pasar un puntero a función o un funtor al algoritmo std :: sort si quiere definir una operación de comparación personalizada.

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

Los punteros a un enlace web o dos, o una página en el libro de Plantillas C ++ sería apreciada!

Si, es valido

En cuanto a hacer que funcione con funtores también, la solución habitual es algo como esto en su lugar:

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

que ahora se puede llamar como:

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

El problema con esto es que si dificulta al comstackdor add2 la llamada a add2 , dado que todo el comstackdor sabe es que un tipo de puntero de función void (*)(int &) se pasa a doOperation . (Pero add3 , como functor, se puede insertar fácilmente. Aquí, el comstackdor sabe que un objeto de tipo add3 se pasa a la función, lo que significa que la función a llamar es add3::operator() , y no solo una desconocida puntero a la función)

Los parámetros de plantilla se pueden parametrizar por tipo (nombre de tipo T) o por valor (int X).

La forma “tradicional” de C ++ de crear plantillas en un código es usar un funtor, es decir, el código está en un objeto y, por lo tanto, el objeto le da al código un tipo único.

Cuando se trabaja con funciones tradicionales, esta técnica no funciona bien, porque un cambio en el tipo no indica una función específica , sino que especifica solo la firma de muchas funciones posibles. Asi que:

 template int do_op(int a, int b, OP op) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5,add); 

No es equivalente al caso del functor. En este ejemplo, do_op se crea una instancia para todos los punteros de función cuya firma es int X (int, int). El comstackdor tendría que ser bastante agresivo para alinear completamente este caso. (No lo descartaría, ya que la optimización del comstackdor se ha vuelto bastante avanzada).

Una forma de decir que este código no hace exactamente lo que queremos es:

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

sigue siendo legal, y es claro que esto no se está incorporando. Para obtener la alineación completa, necesitamos plantilla por valor, por lo que la función está completamente disponible en la plantilla.

 typedef int(*binary_int_op)(int, int); // signature for all valid template params template int do_op(int a, int b) { return op(a,b); } int add(int a, int b) { return a + b; } ... int c = do_op(4,5); 

En este caso, cada versión instanciada de do_op se crea una instancia con una función específica ya disponible. Por lo tanto, esperamos que el código de do_op se parezca mucho a “return a + b”. (Progtwigdores de Lisp, deja de sonreír!)

También podemos confirmar que esto está más cerca de lo que queremos porque esto:

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

no podrá comstackr. GCC dice: “error: ‘func_ptr’ no puede aparecer en una expresión constante. En otras palabras, no puedo expandir completamente do_op porque no me has dado suficiente información en el momento del comstackdor para saber cuál es nuestra operación.

Entonces, si el segundo ejemplo está realmente delimitando por completo nuestra operación, y la primera no, ¿de qué sirve la plantilla? ¿Qué está haciendo? La respuesta es: escriba coerción. Este riff en el primer ejemplo funcionará:

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

¡Ese ejemplo funcionará! (No estoy sugiriendo que sea bueno en C ++, pero …) Lo que sucedió es que do_op ha sido modelado alrededor de las firmas de las diversas funciones, y cada creación de instancias por separado escribirá un código de coerción diferente. Así que el código instanciado para do_op con fadd se ve algo así como:

 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. 

En comparación, nuestro caso de valor por defecto requiere una coincidencia exacta en los argumentos de la función.

Los punteros de función se pueden pasar como parámetros de plantilla, y esto es parte de C ++ estándar . Sin embargo, en la plantilla se declaran y utilizan como funciones en lugar de puntero a función. En la instanciación de la plantilla, uno pasa la dirección de la función en lugar de solo el nombre.

Por ejemplo:

 int i; void add1(int& i) { i += 1; } template void do_op_fn_ptr_tpl(int& i) { op(i); } i = 0; do_op_fn_ptr_tpl<&add1>(i); 

Si desea pasar un tipo de funtor como argumento de plantilla:

 struct add2_t { void operator()(int& i) { i += 2; } }; template void do_op_fntr_tpl(int& i) { op o; o(i); } i = 0; do_op_fntr_tpl(i); 

Varias respuestas pasan una instancia de functor como argumento:

 template void do_op_fntr_arg(int& i, op o) { o(i); } i = 0; add2_t add2; // This has the advantage of looking identical whether // you pass a functor or a free function: do_op_fntr_arg(i, add1); do_op_fntr_arg(i, add2); 

Lo más cerca que puede llegar a este aspecto uniforme con un argumento de plantilla es definir do_op dos veces: una vez con un parámetro que no es de tipo y una vez con un parámetro de tipo.

 // non-type (function pointer) template parameter template void do_op(int& i) { op(i); } // type (functor class) template parameter template void do_op(int& i) { op o; o(i); } i = 0; do_op<&add1>(i); // still need address-of operator in the function pointer case. do_op(i); 

Honestamente, realmente esperaba que esto no se comstackra, pero funcionó para mí con gcc-4.8 y Visual Studio 2013.

En tu plantilla

 template  void doOperation() 

El parámetro T es un parámetro de plantilla sin tipo. Esto significa que el comportamiento de la función de plantilla cambia con el valor del parámetro (que debe corregirse en tiempo de comstackción, que son las constantes del puntero a la función).

Si desea algo que funcione tanto con objetos de funciones como con parámetros de funciones, necesita una plantilla tipada. Sin embargo, cuando hace esto, también necesita proporcionar una instancia de objeto (instancia de objeto de función o un puntero de función) a la función en tiempo de ejecución.

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

Hay algunas consideraciones de rendimiento menores. Esta nueva versión puede ser menos eficiente con argumentos de puntero a función ya que el puntero de función particular solo se desactiva y se llama en tiempo de ejecución mientras que su plantilla de puntero de función se puede optimizar (posiblemente la llamada de función incorporada) en función del puntero de función particular utilizado. Los objetos de función a menudo se pueden expandir de manera muy eficiente con la plantilla tipada, aunque el operator() particular operator() está completamente determinado por el tipo de objeto de función.

El motivo por el que su ejemplo de functor no funciona es que necesita una instancia para invocar al operator() .

Editar: pasar el operador como referencia no funciona. Para simplificar, entiéndelo como un puntero a la función. Usted solo envía el puntero, no una referencia. Creo que estás tratando de escribir algo como esto.

 struct Square { double operator()(double number) { return number * number; } }; template  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;