Estoy buscando las reglas que implican pasar plantillas de C ++ como argumentos.
Esto es compatible con 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>();
}
Sin embargo, aprender sobre esta técnica es difícil. 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 desde mi búsqueda).
Las preguntas que tengo son si se trata de C ++ válido (o solo una extensión ampliamente compatible).
Además, ¿hay alguna manera de permitir que un functor 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 programa anterior, al menos en Visual C ++ , porque la sintaxis es obviamente incorrecta. Sería bueno poder cambiar una función para un functor y viceversa, de forma similar a la forma en que puede pasar un puntero o functor de función al algoritmo std :: sort si desea definir una operación de comparación personalizada.
struct add3 {
void operator() (int &v) {v+=3;}
};
...
doOperation<add3>();
¡Apuntaría a un enlace web o dos, o una página en el libro de Plantillas C ++!
fuente
-std=gnu++17
. ¿Puedo usar el resultado de un operador de conversión lambda constexpr sin captura C ++ 17 como un argumento sin tipo de plantilla de puntero de función? .Respuestas:
Si, es valido.
En cuanto a hacer que funcione también con los functors, la solución habitual es algo como esto:
que ahora se puede llamar como:
Míralo en vivo
El problema con esto es que si hace que sea difícil para el compilador alinear la llamada
add2
, ya que todo lo que sabe el compilador es quevoid (*)(int &)
se está pasando un tipo de puntero de funcióndoOperation
. (Peroadd3
, al ser un functor, se puede alinear fácilmente. Aquí, el compilador sabe que un objeto de tipoadd3
se pasa a la función, lo que significa que la función a llamar esadd3::operator()
, y no solo un puntero de función desconocido).fuente
template <typename F> void doOperation(F&& f) {/**/}
), por lo que bind, por ejemplo, puede pasar una expresión de vinculación en lugar de vincularla?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 una plantilla de código es usar un functor, 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 de tipo no indica una función específica , sino que solo especifica la firma de muchas funciones posibles. Entonces:
No es equivalente al caso de functor. En este ejemplo, do_op se instancia para todos los punteros de función cuya firma es int X (int, int). El compilador tendría que ser bastante agresivo para alinear completamente este caso. (Sin embargo, no lo descartaría, ya que la optimización del compilador ha avanzado bastante).
Una forma de saber que este código no hace lo que queremos es:
sigue siendo legal, y claramente esto no se está alineando. Para obtener una alineación completa, necesitamos una plantilla por valor, para que la función esté completamente disponible en la plantilla.
En este caso, cada versión instanciada de do_op se 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". (Programadores Lisp, ¡deja de sonreír!)
También podemos confirmar que esto está más cerca de lo que queremos porque esto:
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 nuestra operación.
Entonces, si el segundo ejemplo está realmente en línea con nuestra operación, y el primero no, ¿de qué sirve la plantilla? ¿Qué está haciendo? La respuesta es: tipo coerción. Este riff en el primer ejemplo funcionará:
¡Ese ejemplo funcionará! (No estoy sugiriendo que sea bueno en C ++ pero ...) Lo que sucedió es que do_op ha sido creado en torno a las firmas de las diversas funciones, y cada instancia por separado escribirá un código de coerción de tipo diferente. Entonces, el código instanciado para do_op con fadd se parece a:
En comparación, nuestro caso por valor requiere una coincidencia exacta en los argumentos de la función.
fuente
int c = do_op(4,5,func_ptr);
"claramente no se está alineando".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 utilizan como funciones en lugar de puntero a función. En la creación de instancias de plantilla, se pasa la dirección de la función en lugar de solo el nombre.
Por ejemplo:
Si desea pasar un tipo de functor como argumento de plantilla:
Varias respuestas pasan una instancia de functor como argumento:
Lo más cercano que puede llegar a esta apariencia uniforme con un argumento de plantilla es definir
do_op
dos veces, una con un parámetro que no sea de tipo y otra con un parámetro de tipo.Honestamente, realmente esperaba que esto no se compilara, pero funcionó para mí con gcc-4.8 y Visual Studio 2013.
fuente
En su plantilla
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 compilación, cuyas 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, cuando hace esto, también debe proporcionar una instancia de objeto (ya sea una instancia de objeto de función o un puntero de función) a la función en tiempo de ejecución.
Hay algunas consideraciones menores de rendimiento. Esta nueva versión puede ser menos eficiente con argumentos de puntero de función, ya que el puntero de función particular solo se desmarca 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 en línea) 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, ya que lo particular
operator()
está completamente determinado por el tipo de objeto de función.fuente
La razón de su ejemplo funtor no funciona es que se necesita una instancia para invocar al
operator()
.fuente
Editar: Pasar el operador como referencia no funciona. Por simplicidad, entiéndalo como un puntero de función. Simplemente envía el puntero, no una referencia. Creo que estás tratando de escribir algo como esto.
. .
fuente