You are not logged in.
example first:
bash-4.0$ cat test.c
#include <stdio.h>
void doit(size_t (*reader)(void *, size_t, size_t, void *), void *data)
{
size_t bytes;
char buffer[1024];
do {
bytes = reader(buffer, 1, 1024, data);
printf("%.*s", bytes, buffer);
} while (bytes == 1024);
}
int main(void)
{
FILE *f;
f = fopen(__FILE__, "r");
doit(fread, f);
fclose(f);
return 0;
}
bash-4.0$ cc test.c
test.c: In function ‘main’:
test.c:19: warning: passing argument 1 of ‘doit’ from incompatible pointer type
test.c:5: note: expected ‘(*)(void *, size_t, size_t, void *)’ but argument is of type ‘size_t (*)(void * __restrict__, size_t, size_t, struct FILE * __restrict__)’
So I want a function where the user passes a callback pointer with some arbitrary userdata pointer. The problem is that I want this to also be friendly enough that lazy users can just pass fread and a file to it (as I've done here). I was hoping that gcc would notice (*)(void *, size_t, size_t, void *) has a (void *) type for its last argument and would allow me to assign to it a (*)(void *, size_t, size_t, FILE *), but as you can see it throws incompatible pointer type warnings because the function prototypes don't match exactly. I mean, they really are the same thing - I could easily write my own fread wrapper, but it would be wasteful and seems like it should be unnecessary:
size_t _fread(void *ptr, size_t size, size_t nmemb, void *file)
{
return fread(ptr, size, nmemb, file);
}
What's the preferred method for what I'm trying to achieve here? I don't mind casting in my code, but I would like to write this in such a way that the users of the API don't have to cast for this to work (and still have some type checking, so they can't pass in a completely incompatible function).
Thanks.
[edit: fix a bug - that'll teach me to edit sample code after I've tested and pasted it]
Last edited by cmtptr (2012-05-04 11:35:14)
Offline
Declaring a function pointer type would improve the readability but requires a small cast when an exact match is not presented. As you have stated, multiple declarations provide the user with typesafe interfaces. If you were using C++ you could use overloading, templates or typeid. C++ allows polymorphism for clean designs.
Offline
Declaring a function pointer type would improve the readability but requires a small cast when an exact match is not presented. As you have stated, multiple declarations provide the user with typesafe interfaces. If you were using C++ you could use overloading, templates or typeid. C++ allows polymorphism for clean designs.
My original example code actually did have the function typedef'd (in fact that's the change I was referencing in my [edit] comment). I removed it from the example because I felt it didn't add anything to what I was trying to illustrate and would probably only draw comments to the style of the code instead of to the question - d'oh!
Either way, whether I typedef it or not the issue is still there. The user still has to perform a cast to supress this warning. Once they do that, type checking is gone. Somebody dumb could easily pass, for example, a (*)(size_t, float, struct pants **) to doit() and, because they casted it, gcc doesn't complain...
Even if I were willing to use C++ here (I'm not), I don't think that C++ has a direct solution to this problem. It may have features that present alternatives (as you mentioned, employing a polymorphic reader class in place of my callback pointer), but if I were trying to implement the same code here in C++ I would be faced with the same incompatible pointer type warning. So in other words, offering C++ is not an answer, it's a workaround.
Offline
The compiler is generating a warning when the types are incompatible and allows the programmer to override this by explicit type-casting. The programmer needs to be responsible for their code.
Existing code/libraries can be incorporated into C++ but these should be encapsulated within classes. New code can be designed to minimise these issues.
Offline
This works, but it feels a bit kludgy, circumvents compile-time type checking, and changes the order of the arguments:
#include <stdio.h>
#include <stdarg.h>
void doit(void * data, ...)
{
va_list args;
va_start(args, data);
size_t (* reader)(void *, size_t, size_t, void *);
reader = va_arg(args, size_t (*)(void *, size_t, size_t, void *));
va_end(args);
size_t bytes;
char buffer[1024];
do {
bytes = reader(buffer, 1, 1024, data);
printf("%.*s", bytes, buffer);
} while (bytes == 1024);
}
int main(void)
{
FILE *f;
printf("%s\n", __FILE__);
f = fopen(__FILE__, "r");
doit(f, fread);
fclose(f);
return 0;
}
My Arch Linux Stuff • Forum Etiquette • Community Ethos - Arch is not for everyone
Offline
This works, but it feels a bit kludgy, circumvents compile-time type checking, and changes the order of the arguments:
#include <stdio.h> #include <stdarg.h> void doit(void * data, ...) { va_list args; va_start(args, data); size_t (* reader)(void *, size_t, size_t, void *); reader = va_arg(args, size_t (*)(void *, size_t, size_t, void *)); va_end(args); size_t bytes; char buffer[1024]; do { bytes = reader(buffer, 1, 1024, data); printf("%.*s", bytes, buffer); } while (bytes == 1024); } int main(void) { FILE *f; printf("%s\n", __FILE__); f = fopen(__FILE__, "r"); doit(f, fread); fclose(f); return 0; }
Thanks for the reply, and sorry I didn't notice it sooner.
True, I noticed that this is how curl does it with their *_setopt() functions; however, when it comes down to it I think I'd rather make the user cast than to suffer the overhead and interface ambiguity that comes with va_args.
Thanks again for the responses. Looks like I should just keep moving forward with my first method. At least it's simple and explicit!
Offline