Pregunta ¿Es posible pasar 2 funciones que tienen una firma diferente como argumento para otra función?


Actualmente, tengo las siguientes 2 funciones:

void write_to_file(FILE *fp)
{
    fprintf(fp, "stuff here: %d", 10);
    /* 1000s of similar lines below */
}

y

void write_to_string(char *str)
{
    sprintf(str, "stuff here: %d", 10);
    /* 1000s of similar lines below */
}

Me gustaría convertirlo en una sola función. Pensé en algo como:

void write_somewhere(void *ptr, int to_file)
{
    if (to_file) {
        typedef fprintf myprintf;
    } else {
        typedef sprintf myprintf;
    }
    myprintf(ptr, "stuff here: %d", 10);
}

Esto no funciona y se ve feo.

Desde la firma de fprintf y sprintf son diferentes y de la siguiente manera

int fprintf(FILE *stream, const char *format, …);
int sprintf(char *buffer, const char *format, …);

¿Es posible hacer algo como,

void write_somewhere(void *ptr, void *func)
{
    func(ptr, "stuff here: %d", 10);
}

EDITAR: Según la respuesta de Alter a continuación, esto es lo que tengo pero no funciona como se esperaba e imprime el valor de basura cuando intento imprimir valores en la función write_somewhere ():

#include <stdio.h>
#include <stdarg.h>

typedef int (*myprintf_t) (void *, const char *, ...);

int myfprintf(void *ptr, const char *format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = vfprintf(ptr, format, args);
    va_end(args);
    return ret;
}

int mysprintf(void *ptr, const char *format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = vsprintf(ptr, format, args);
    va_end(args);
    return ret;
}

void write_somewhere(void *ptr, myprintf_t myprintf, const char *format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = myprintf(ptr, format, args);
    va_end(args);
    return ret;
}

int main(void)
{
    char s[100];
    int i = 100;

    /* This works */
    write_somewhere(stdout, myprintf, "Hello world");

    /* This prints out garbage */
    write_somewhere(stdout, myprintf, "Hello world, I am %d", i);
    write_somewhere(s, mysprintf);
    return 0;
}

11
2017-10-17 06:23


origen


Respuestas:


La respuesta de Jen es la correcta, pero en este caso puedes redirigir ptr a v*printf usando un puntero a la función:

#include <stdio.h>
#include <stdarg.h>

int myfprintf(void *ptr, const char *format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = vfprintf(ptr, format, args);
    va_end(args);
    return ret;
}

int mysprintf(void *ptr, const char *format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = vsprintf(ptr, format, args);
    va_end(args);
    return ret;
}

void write_somewhere(void *ptr, int (*myprintf)(void *, const char *, ...))
{
    myprintf(ptr, "stuff here");
}

int main(void)
{
    char s[100];

    write_somewhere(stdout, myfprintf);
    write_somewhere(s, mysprintf);
    return 0;
}

Para su última edición:

Parece que quiere pasar algunos parámetros adicionales a write_somewhere, en este caso sugiero:

#include <stdio.h>
#include <stdarg.h>

#define TO_FILE 0
#define TO_STRING 1

void write_somewhere(int where, void *ptr, const char *format, ...)
{
    #define myprintf(ptr, ...) \
    (where == TO_FILE ? vfprintf(ptr, __VA_ARGS__) : vsprintf(ptr, __VA_ARGS__))

    va_list args;
    va_start(args, format);
    myprintf(ptr, format, args);
    /* more stuff */
    va_end(args);
    #undef myprintf
}

int main(void)
{
    char s[100];

    write_somewhere(TO_FILE, stdout, "%u\n", 10);
    write_somewhere(TO_STRING, s, "Hello");
    printf("%s\n", s);
    return 0;
}

9
2017-10-17 06:44



El lenguaje C garantiza que todos los punteros de función tengan la misma representación. Sus funciones polimórficas simplemente necesitan ser prototipadas aceptando cualquier puntero de función, por ejemplo, un void (*funcptr)(void). Tenga en cuenta que un ptr-to-void es no un puntero a la función (es un objeto puntero) y es posible que no pueda contener un puntero a la función.

Por supuesto, solo puedes llamar a la función si sabes cuál de los varios tipos es. Entonces necesitas alguna manera de discriminar, al igual que printf al mirar el formato. Si llama a una función con argumentos que no coinciden con su prototipo, el comportamiento no está definido.


7
2017-10-17 06:40



No es una respuesta a su pregunta exacta, pero en lugar de escribir write_something() como lo has hecho, podrías cambiar la estructura ligeramente:


void write_somewhere(void *ptr, int to_file)
{
    if (to_file) {
        fprintf( (FILE*) ptr, "stuff here");
    } else {
        sprintf( (char*) ptr, "stuff here");
    }
}

Sin embargo, para una respuesta estricta a su pregunta ... Como has encontrado, la línea typedef que has intentado no funciona. typedef es una operación en tiempo de compilación, no una operación en tiempo de ejecución.

Lo que podría hacer, sin embargo, es definir un tipo para un puntero de función que coincida tanto con el fprintf() y sprintf() funciones:


typedef int (*someprintf_ptr)(FILE *stream, const char *format, …);

los write_somewhere() entonces se vería así:


void write_somewhere(void *ptr, someprintf_ptr func)
{
    func(ptr, "stuff here");
}

/* with calls looking like... */
write_somewhere( (void *)a_file_ptr, (someprintf_ptr)(fprintf));

3
2017-10-17 06:44



Su función write_something debería ser algo así como:

void write_something(void (*function)(),  int to_file) 
{
   ....
}

2
2017-10-17 06:29



Intenta hacer myprintf una función

void write_somewhere(void *ptr, int to_file)
{
    myprintf(to_file, ptr, "stuff here");
    // do stuff
    /* 1000s of similar lines below */
}

void myprintf( int to_file, void *ptr, char *output )
{
    if (to_file)
        fprintf( ptr, output );
    else
        sprintf( ptr, output );
}

1
2017-10-17 06:39



Déjame intentarlo:

struct target {
    int (*tgtfunction)();
    void* ptr;
}

struct target mktarget_file(FILE * fp) {
    struct target tgt = { .tgtfuntion = vfprintf, .ptr = fp };
    return tgt;
}

struct target mktarget_string(char * str) {
    struct target tgt = { .tgtfuntion = vsprintf; .ptr = str };
    return tgt;
}

void tgtprintf(struct target * target, char * fmt, ...) {
    va_list ap;
    va_start(ap, target);
    int ret = target.tgtfunction(target.ptr, fmt, ap);
    va_end(ap);
    return ret;
}

void write_stuff(struct target * target)
{
    tgtprintf(target, "stuff here");
    /* 1000s of similar lines below */
}

debe hacer lo que quiera: crear un struct target para su objetivo deseado, y simplemente llame a write_stuff para escribir sus cosas allí.

Tenga en cuenta que es posible que sea necesario refinar el sprintf, ya que cada cadena se escribe en el mismo lugar en lugar de agregarse, y no hay espacio libre. Pero el concepto general podría comenzar así.


1
2017-10-17 07:22