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

Esto es apoyado por C++, como se muestra mediante 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>();
}

El aprendizaje de esta técnica es difícil, sin embargo. Buscando en google por «función como un argumento de plantilla» no conduce a mucho. Y el clásico Las Plantillas De C++ La Guía Completa sorprendentemente también de no discutir (al menos no de mi búsqueda).

Las preguntas que tengo son si esto es válido en C++ (o sólo algunos, muy extensión compatible).

También, hay una manera de permitir que un functor con la misma firma para ser usado de manera intercambiable con funciones explícitas durante este tipo de plantilla de invocación?

La siguiente no trabajo en el programa anterior, al menos en Visual C++, porque la sintaxis es obviamente erróneo. Sería agradable ser capaz de cambiar de una función para un functor y viceversa, de manera similar a la forma en que se puede pasar un puntero a función o functor para el std::algoritmo de ordenación por si desea definir una costumbre operación de comparación.

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

    doOperation<add3>();

Punteros que apuntan a una web de enlace o dos, o una página en las Plantillas de C++ libro se agradece!

InformationsquelleAutor SPWorley | 2009-07-23

6 Comentarios

  1. 120

    Sí es válido.

    Como para lo que es trabajar con functors así, la solución habitual es algo como esto en su lugar:

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

    que ahora puede ser llamado como:

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

    El problema con esto es que si se hace difícil para que el compilador en línea de la llamada a add2, ya que todo el compilador sabe es que un puntero a función del tipo de void (*)(int &) se pasa a doOperation. (Pero add3, siendo un functor, pueden ser insertados 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 de llamada es add3::operator(), y no sólo algunas de función desconocida puntero.)

    • Ahora aquí es una pregunta interesante. Cuando se pasa un nombre de función, NO es como que hay un puntero a la función de los involucrados. Es una función explícita, dado en tiempo de compilación. Para que el compilador sepa exactamente lo que tiene en tiempo de compilación.
    • Podría una plantilla de sobrecarga se utiliza para hacer que el puntero de la función y el functor de los casos tienen idéntica sintaxis? ¿Existe alguna ventaja de rendimiento? Especialmente para los apátridas functors que efectivamente no tienen instancias y por lo tanto queremos evitar cualquier caso en el que el compilador puede dejar de alineaciones y optimización.
    • Hay una ventaja para el uso de functors más de punteros a función. El functor puede ser instanciated dentro de la clase y por lo tanto proporciona más opertunity para el compilador de optimizaciones (como inline). El compilador sería difícil para optimizar una llamada a través de un puntero a función.
    • Cuando se utiliza la función de un parámetro de plantilla que ‘se desintegra en un puntero a la función pasada. Es otros análogos a cómo las matrices de caries en los punteros cuando se le pasan como argumentos a los parámetros. Por supuesto, el valor del puntero es conocido en tiempo de compilación y debe apuntar a una función con la vinculación externa, de modo que el compilador puede utilizar esta información para fines de optimización.
    • Mi comentario anterior se refiere a los parámetros de la plantilla tomando algún tipo de función parámetro de puntero. En este caso, la creación de instancias de plantilla puede estar en línea como el compilador sabe el valor del puntero en tiempo de compilación. En jalf el ejemplo de la plantilla tendrá un tipo de parámetro y el tipo de parámetro de plantilla y el valor del argumento de la función y juntas determinan la llamada de la función, una función de objeto puede ser mejor optimizado.
    • Para fines de optimización, me gustaría ir con el functor, si es o no es apátrida.
    • en realidad, tal una plantilla que realmente está pasando un PUNTERO a función? Pero que es realmente extraño.. no se puede hacer una plantilla que necesita una constante instancia de la clase puntero, por ejemplo. Y si se trata de un puntero, que es opaco para el compilador y que nunca podría estar en línea.
    • La plantilla en tu ejemplo está especializado en una función de puntero, sí. Pero el valor de ese puntero es conocido en tiempo de compilación, por lo que en principio puede todavía estar en línea. No sé si los compiladores hacen realmente en línea aunque.
    • El avance rápido de unos años más tarde, la situación con el uso de funciones como argumentos de plantilla mejorado mucho en C++11. Usted ya no está obligada a utilizar Javaisms como functor clases, y se puede utilizar por ejemplo estática en línea funciones como argumentos de plantilla directamente. Todavía lejos de comparar a Lisp macros de la década de 1970, pero C++11, definitivamente, tiene un buen progreso a través de los años.
    • NO es un puntero, excepto en C++, las funciones son equivalentes a sus punteros, y sus punteros son equivalentes a los punteros de sus punteros. Lo que hace que todas las expresiones que involucran funciones son equivalentes. MyFunc == &MyFunc == &&&&MyFunc == ***&MyFunc etc
    • debido a que c++11 ¿no sería mejor tomar la función como r-value de referencia (template <typename F> void doOperation(F&& f) {/**/}), por lo que se unen, por ejemplo, puede pasar un enlace de expresión en lugar de enlazar?
    • Yo seguramente deseo gustaría ampliar un poco en tu comentario de arriba «puede utilizar por ejemplo estática en línea funciones como argumentos de plantilla directamente». Puede usted explicar o señalar un ejemplo o documento? Me gustaría saber más.
    • Aprendí acerca de esto por accidente cuando la lectura de la cppreference artículo sobre nullptr. en.cppreference.com/w/cpp/language/nullptr. Es un breve ejemplo de pasar un puntero a función a una plantilla.
    • Siento que estoy refrescante viejo tema, pero, ¿qué acerca de static functors? Digamos que void operator() (int &v) dentro de struct add3, es un static función. Puedo escribir; doOperation(add3::operator()); y asegúrese de que el compilador va a ser capaz de poner en línea este estático functor sin problemas?
    • No doOperation(add3()); ya llame operator() en el functor? Seguramente, que no es lo que quieres?

  2. 65

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

    La «tradicional» de C++ manera de plantillas de una pieza de código es el uso de un functor, es decir, el código es en un objeto, y el objeto que se da, por tanto, el código de tipo único.

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

    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 a la functor caso. En este ejemplo, do_op se crea una instancia para todos los punteros a función, cuya firma es de tipo int X (int, int). El compilador tendría que ser bastante agresivo completamente en línea este caso. (Yo no excluye a pesar de que, como la optimización del compilador ha conseguido bastante avanzada).

    Una manera de decir que este código no hacer lo que nosotros queremos es:

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

    todavía es legal, y es evidente que esto no es llegar entre líneas. Para obtener toda la alineaciones, necesitamos a la plantilla, por su valor, por lo que la función es totalmente disponibles 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 una de las instancias de la versión de do_op es instanciado con una función específica, ya disponible. Por lo tanto esperamos que el código para do_op a parecerse mucho a «return a + b». Lisp (programadores, detener su sonriendo!)

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

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

    dará un error de compilación. GCC dice: «error: ‘func_ptr’ no puede aparecer en una expresión constante. En otras palabras, no se puede ampliar totalmente do_op porque no me habéis dado suficiente información al compilador de tiempo para saber lo que nuestros op es.

    Así que si el segundo ejemplo es realmente totalmente inline nuestros op, y la primera es no, ¿de qué sirve la plantilla? Qué está haciendo? La respuesta es: tipo de coacción. Este riff en el primer ejemplo se va a trabajar:

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

    Que el ejemplo funcione! (No estoy sugiriendo es bueno C++ pero…) Lo que ha ocurrido es do_op ha sido de plantilla en torno a la firmas de las diferentes funciones, y cada una separada de la creación de instancias será de escritura diferente tipo de coacción código. Así, el código crea una instancia para do_op con fadd se ve algo 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.

    Por comparación, el valor de los casos requiere una coincidencia exacta en los argumentos de la función.

  3. 15

    Punteros de función pueden ser pasados como parámetros de la plantilla, y esto es parte del estándar de C++
    . Sin embargo, en la plantilla en la que se declaran y se utiliza como funciones en lugar de puntero-a-función. En la plantilla de la creación de instancias de se pasa a la dirección de la función en lugar de sólo 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 quieres pasar un functor tipo como un 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 pasar un functor instancia 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 este aspecto uniforme, con un argumento de plantilla es definir do_op dos veces: una vez con un no-tipo de parámetro 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 espera que esto no para compilar, pero a mí me funcionó con gcc-4.8 y Visual Studio 2013.

  4. 9

    En su plantilla

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

    El parámetro T es un no-tipo de parámetro de plantilla. Esto significa que el comportamiento de la plantilla de los cambios de la función con el valor del parámetro (que debe ser corregido en tiempo de compilación, que la función de puntero constantes).

    Si quieres algo que funciona tanto con los objetos de la función y los parámetros de la función que necesita un tipo de plantilla. Al hacer esto, sin embargo, también debe proporcionar una instancia de objeto (ya sea la función de instancia de objeto o de un puntero a 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. Esta nueva versión puede ser menos eficiente con el puntero de la función de argumentos como la particular función de puntero es sólo derefenced y llamar en tiempo de ejecución, mientras que su función puntero de la plantilla puede ser optimizado (posiblemente la llamada a la función en línea), basado en la particular función de puntero utilizado. La función de los objetos a menudo puede ser muy eficiente ampliado con el tipo de plantilla, a pesar de que el particular operator() está totalmente determinado por el tipo de la función objeto.

  5. 0

    Edición: Pasando el operador como una referencia no funciona. Por simplicidad, que lo entiende como un puntero a función. Usted sólo tiene que enviar el puntero, no es una referencia.
    Creo que usted está 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;

Dejar respuesta

Please enter your comment!
Please enter your name here