emulando aplicación de función parcial en c

Estoy escribiendo un progtwig de C que, si hubiera sido parte del lenguaje, se beneficiaría de una aplicación de función parcial. Necesito proporcionar un puntero de función con una firma fija, pero en algún momento debo llamar a una función ya escrita que necesita argumentos.

Puedo escribir muchas funciones pequeñas, llamándome como:

// example, not actual code void (*atJoystickLeft)(void); // fixed signature .. code .. atJoystickLeft = &func1; .. code .. void func1(void) { func2(10, "a string"); } / 

Me gustaría en su lugar:

 atJoystickLeft = &func2(10, "a string"); 

así que puedo disponer de la función func1 que es solo para establecer el parámetro para la llamada a func2.

Eso haría que el código al menos, 20% – 30% más pequeño. Mi pregunta simple es: ¿Alguien tiene un truco para emular una aplicación de función parcial en C? (C99)

La respuesta corta es que no, C no lo admite.

Podrías juntar una interfaz de usuario variable que tomó la dirección de una función y creó un marco de stack para invocar esa función con esos argumentos, pero no sería portátil 1 . Si quisiera que fuera portátil, y era libre de modificar las otras funciones que iba a invocar a través de este extremo frontal (en una forma que no es realmente adecuada para la invocación directa), podría reescribirlas todas para recibir una va_alist como única. parámetro, y recupere el número / tipo correcto de parámetros usando va_arg :

 // pointer to function taking a va_alist and return an int: typedef int (*func)(va_alist); void invoke(func, ...) { va_alist args; va_start(args, func); func(args); va_end(args); } 

Edit: Lo siento, como @missingno señaló, no hice que esto funcionara de la forma en que estaba previsto. Esto debería ser realmente dos funciones: una que toma las entradas y las envuelve en una estructura, y la otra que toma la estructura e invoca la función deseada.

 struct saved_invocation { func f; argument_list args; }; saved_invocation save(func f, ...) { saved_invocation s; va_alist args; sf = f; va_start(args, f); va_make_copy(s.args, args); va_end(args); } int invoke(saved_invocation const *s) { s->f(s->args); } 

Para va_make_copy , obtendrías más cosas no estándar. No sería lo mismo que va_copyva_copy normalmente solo almacena un puntero al principio de los argumentos, que solo permanece válido hasta que se devuelve la función actual. Para va_make_copy , tendría que almacenar todos los argumentos reales para poder recuperarlos más tarde. Suponiendo que usó la estructura de argument que describo a continuación, recorrería los argumentos usando va_arg y usaría malloc (o lo que sea) para asignar un nodo a cada argumento y crearía algún tipo de estructura de datos dinámica (por ejemplo, lista enlazada, dinámica array) para mantener los argumentos hasta que estuviera listo para usarlos.

También tendrías que agregar algún código para tratar de liberar esa memoria una vez que hayas terminado con una función de enlace particular. En realidad, también cambiaría la firma de su función de tomar una lista_vista directamente, a tomar cualquier tipo de estructura de datos que haya diseñado para mantener su lista de argumentos.

[edición final]

Esto significaría que la firma de todas las demás funciones que iba a invocar debería ser:

 int function(va_alist args); 

… y luego cada una de estas funciones tendría que recuperar sus argumentos a través de va_arg , por lo que (por ejemplo) una función que tomaría dos puntos como sus argumentos, y devolvería su sum se vería así:

 int adder(va_alist args) { int arg1 = va_arg(args, int); int arg2 = va_arg(args, int); return arg1 + arg2; } 

Esto tiene dos problemas obvios: primero, aunque ya no necesitamos un envoltorio separado para cada función, todavía terminamos agregando código extra a cada función para permitir que se invoque a través del envoltorio único. En términos de tamaño de código, es poco probable que sea mucho mejor que el punto de equilibrio, y podría ser fácilmente una pérdida neta.

Mucho peor, sin embargo, dado que ahora estamos recuperando todos los argumentos de todas las funciones como una lista de argumentos variables, ya no obtenemos ningún tipo de comprobación de los argumentos. Si quisiéramos lo suficiente, sería posible (por supuesto) agregar un pequeño código y un tipo de envoltura para manejar eso también:

 struct argument { enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ }; union data { char char_data; short short_data; int int_data; long long_data; /* ... */ } } 

Luego, por supuesto, escribiría aún más código para verificar que la enumeración de cada argumento indicara que era el tipo esperado, y recuperaría los datos correctos de la unión, de ser así. Esto, sin embargo, agregaría cierta fealdad seria a la invocación de las funciones, en lugar de:

 invoke(func, arg1, arg2); 

… obtendrías algo como:

 invoke(func, make_int_arg(arg1), make_long_arg(arg2)); 

Obviamente, esto se puede hacer. Desafortunadamente, todavía no sirve para nada: el objective original de reducir el código casi seguro se ha perdido. Esto elimina la función de envoltorio, pero en lugar de una función simple con un envoltorio simple y una invocación simple, terminamos con una función compleja e invocación compleja, y una pequeña montaña de código adicional para convertir un valor en nuestro tipo de argumento especial, y otro para recuperar un valor de un argumento.

Si bien hay casos que pueden justificar un código como este (por ejemplo, escribir un intérprete para algo como Lisp), creo que en este caso funciona como una pérdida neta. Incluso si ignoramos el código adicional para agregar / usar la verificación de tipo, el código de cada función para recuperar argumentos en lugar de recibirlos directamente funciona más que el contenedor que estamos tratando de reemplazar.


  1. “No sería portátil” es casi un eufemismo (realmente no puedes hacer esto en C en absoluto), solo tienes que usar lenguaje ensamblador para comenzar.

No conozco ninguna manera directa de hacer lo que quiere, ya que C no admite cierres ni ninguna otra forma de pasar “parámetros ocultos” a sus funciones (sin usar variables globales o estáticas, estilo strtok)

Pareces venir de un fondo de FP. En C, todo tiene que hacerse a mano y me arriesgaría a decir que, en este caso, sería mejor tratar de modelar las cosas con un estilo OO: convierta sus funciones en “métodos” haciendo que reciban un argumento adicional, un puntero a una estructura de “atributos internos”, el this o el self . (Si conoces Python, esto debería ser muy claro :))

En este caso, por ejemplo, func1 podría convertirse en algo como esto:

 typedef struct { int code; char * name; } Joystick; Joystick* makeJoystick(int code, char* name){ //The constructor Joystick* j = malloc(sizeof Joystick); j->code = code; j->name = name; return j; } void freeJoystick(Joystick* self){ //Ever noticed how most languages with closures/objects // have garbage collection? :) } void func1(Joystick* self){ func2(self->code, self->name); } int main(){ Joystick* joyleft = make_Joystick(10, "a string"); Joystick* joyright = make_Joystick(17, "b string"); func1(joyleft); func1(joyright); freeJoystick(joyleft); freeJoystick(joyright); return 0; }