Можно ли передать 2 функции, которые имеют различную сигнатуру в качестве аргумента другой функции?

В настоящее время у меня есть следующие 2 функции:

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

и

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

Я бы хотел, чтобы он превратил его в одну функцию. Я подумал о чем-то вроде:

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

Это не работает и выглядит уродливо.

Поскольку сигнатура fprintf и sprintf различна и следующим образом:

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

Можно ли сделать что-то вроде

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

EDIT: Основываясь на ответе Alter ниже, это то, что у меня есть, но оно работает не так, как ожидалось, и выводит значение мусора при попытке распечатать значения в функции 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;
}

Ответ 1

Ответ Jen правильный, но в этом случае вы можете перенаправить ptr в v*printf с помощью указателя на функцию:

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

Для вашего последнего редактирования:

Кажется, что вы хотите передать некоторые параметры дополнительных параметров write_somewhere, в этом случае я предлагаю:

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

Ответ 2

Язык C гарантирует, что все указатели функций имеют одинаковое представление. Ваши полиморфные функции просто должны быть прототипированы как принятие любого указателя функции, скажем, void (*funcptr)(void). Обратите внимание, что ptr-to-void не является указателем на функцию (это указатель объекта) и может не иметь возможности удерживать указатель на функцию.

Конечно, вы можете только вызвать функцию, если знаете, какой из нескольких типов она есть. Таким образом, вам нужно каким-то образом различать, как и printf, глядя на формат. Если вы вызываете функцию с аргументами, не соответствующими ее прототипу, поведение undefined.

Ответ 3

Не ответ на ваш точный вопрос, но вместо того, чтобы писать write_something(), как вы, вы можете слегка изменить структуру:


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

Однако для строжайшего ответа на ваш вопрос... Как вы нашли, строка typedef, которую вы пытались, не работает. typedef - это операция времени компиляции, а не операция выполнения.

Однако вы можете определить тип для указателя функции, который соответствует функциям fprintf() и sprintf():


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

write_somewhere() будет выглядеть следующим образом:


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

Ответ 4

Ваша функция write_something должна выглядеть примерно так:

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

Ответ 5

Попробуйте сделать myprintf функцией

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

Ответ 6

Позвольте мне попробовать:

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 */
}

должен делать то, что вы хотите: создайте struct target для вашей целевой цели и просто вызовите write_stuff, чтобы написать там свои материалы.

Имейте в виду, что материал sprintf может понадобиться уточнить, так как каждая строка записывается в одно и то же место вместо добавленного, и нет проверки свободного места. Но общая концепция может начаться именно так.