Función pasada como argumento de plantilla


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

Esto es soportado por C++ como se muestra en un ejemplo aquí:

#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>();
}

Aprender sobre esta técnica es difícil, sin embargo. Buscar en Google "función como argumento de plantilla" no conduce a mucho. Y las plantillas clásicas de C++ La Guía Completa sorprendentemente tampoco lo discute (al menos no de mi búsqueda).

Las preguntas que tengo son si esto es válido C++ (o solo algunas extensiones ampliamente soportadas).

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

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

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

    doOperation<add3>();

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

Author: Peter Mortensen, 2009-07-24

6 answers

Sí es válido.

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

template <typename F>
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 hace que sea difícil para el compilador insertar la llamada a add2, ya que todo lo que el compilador sabe es que un tipo de puntero de función void (*)(int &) se está pasando a doOperation. (Pero add3, siendo un funtor, se puede insertar fácilmente. Aquí, el compilador 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 un puntero de función desconocido.)

 108
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-23 20:12:14

Los parámetros de la plantilla pueden ser parametrizados por tipo (typename T) o por valor (int X).

La forma "tradicional" de C++ de templar un fragmento de código es usar un funtor, es decir, el código está en un objeto, y 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. Entonces:

template<typename OP>
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 funtor. En este ejemplo, se crea una instancia de do_op para todos los punteros de función cuya firma es int X (int, int). El compilador tendría que ser bastante agresivo para insertar completamente este caso. (Sin embargo, no lo descartaría, ya que la optimización del compilador se ha vuelto bastante avanzada.)

Una forma de saber 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 claramente esto no se está insertando. Para llenarse en línea, 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<binary_int_op op>
int do_op(int a, int b)
{
 return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op<add>(4,5);

En este caso, cada versión instanciada de do_op es instanciada con una función específica ya disponible. Por lo tanto, esperamos que el código para do_op se parezca mucho a "return a + b". (Programadores Lisp, dejen 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<func_ptr>(4,5);

Fallará al compilar. 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 compilador para saber cuál es nuestro op.

Así que si el segundo ejemplo está realmente completamente alineado en nuestro op, y el primero no lo está, ¿de qué sirve la plantilla? Qué está haciendo? La respuesta es: tipo coerción. Este riff del primer ejemplo funcionará:

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

¡Ese ejemplo funcionará! (No estoy sugiriendo que es buen C++ pero...) Lo que ha sucedido es que do_op ha sido templada alrededor de las firmas de las diversas funciones, y cada instanciación separada escribirá código de coerción de tipo 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 por valor requiere una coincidencia exacta en los argumentos de la función.

 58
Author: Ben Supnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-22 15:46:04

Los punteros de función se pueden pasar como parámetros de plantilla, y esto es parte del estándar C++ . Sin embargo, en la plantilla se declaran y se utilizan como funciones en lugar de puntero a función. En template instanciation se 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 op(int&)>
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<typename op>
void do_op_fntr_tpl(int& i) {
  op o;
  o(i);
}

i = 0;
do_op_fntr_tpl<add2_t>(i);

Varias respuestas pasan una instancia de funtor como un argumento:

template<typename op>
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 se puede llegar a esta apariencia 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 op(int&)>
void do_op(int& i) { op(i); }

// type (functor class) template parameter
template<typename op>
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<add2_t>(i);

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

 11
Author: Kietz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-16 14:33:19

En su plantilla

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

El parámetro T es un parámetro de plantilla que no es de tipo. Esto significa que el comportamiento de la función de plantilla cambia con el valor del parámetro (que debe fijarse en tiempo de compilación, qué constantes de puntero de función son).

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

template <class T>
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 de función, ya que el puntero de función en particular solo se desrefence y se llama en tiempo de ejecución, mientras que su plantilla de puntero de función se puede optimizar (posiblemente la llamada a función inlineada) 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 escrita, aunque como el particular operator() está completamente determinado por el tipo del objeto de la función.

 8
Author: CB Bailey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-23 20:18:34

La razón por la que su ejemplo de funtor no funciona es que necesita una instancia para invocar el operator().

 1
Author: Nikolai Fetissov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-23 20:12:35

Editar: Pasar el operador como referencia no funciona. Para simplificar, entiéndalo como un puntero de función. 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 <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;
 0
Author: AraK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-15 12:59:19