Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to pass 2 functions which have different signature as an argument to another function?

Currently, I have the following 2 functions:

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

and

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

I'd like to poly morph it into a single function. I'd thought about something like:

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

This doesn't work and looks ugly.

Since the signature of fprintf and sprintf are different and as follows,

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

Is it possible to do something like,

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

EDIT: Based on Alter's answer below, this is what I have but it doesn't quite work as expected and prints out garbage value when trying to print out values in write_somewhere() function:

#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;
}
like image 935
Nikhil Avatar asked Oct 17 '14 06:10

Nikhil


3 Answers

Jen‘s answer is the correct one, but in this case you can redirect ptr to v*printf using a pointer to function:

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

For your last edit:

It seems that you want to pass some extras parameters to write_somewhere, in this case I suggest:

#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;
}
like image 138
David Ranieri Avatar answered Nov 10 '22 05:11

David Ranieri


The C language guarantees that all function pointers have the same representation. Your polymorphic functions simply needs to be prototyped as accepting any function pointer, say, a void (*funcptr)(void). Note that a ptr-to-void is not a function pointer (it's an object pointer) and may not be able to hold a function pointer.

Of course you can only call the function if you know which of the several types it is. So you need some way to discriminate, much like printf does by looking at the format. If you call a function with arguments not matching its prototype, the behavior is undefined.

like image 42
Jens Avatar answered Nov 10 '22 03:11

Jens


Not an answer to your exact question, but instead of writing write_something() as you have, you could change the structure slightly:


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

However, for a strict answer to your question... As you've found, the typedef line that you've attempted doesn't work. typedef is a compile time operation, not a runtime operation.

What you could do, though, is to define a type for a function pointer that matches both the fprintf() and sprintf() functions:


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

The write_somewhere() would then look like:


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));
like image 40
Andrew Edgecombe Avatar answered Nov 10 '22 05:11

Andrew Edgecombe