You are not logged in.

#1 2012-05-03 13:47:16

cmtptr
Member
Registered: 2008-09-01
Posts: 135

(C) Preferred method for callback with non-strict parameter type?

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

#2 2012-05-05 19:03:40

zorro
Member
Registered: 2011-11-18
Posts: 47

Re: (C) Preferred method for callback with non-strict parameter type?

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

#3 2012-05-05 19:39:07

cmtptr
Member
Registered: 2008-09-01
Posts: 135

Re: (C) Preferred method for callback with non-strict parameter type?

zorro wrote:

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

#4 2012-05-06 10:06:10

zorro
Member
Registered: 2011-11-18
Posts: 47

Re: (C) Preferred method for callback with non-strict parameter type?

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

#5 2012-05-06 14:21:08

Xyne
Administrator/PM
Registered: 2008-08-03
Posts: 6,965
Website

Re: (C) Preferred method for callback with non-strict parameter type?

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 StuffForum EtiquetteCommunity Ethos - Arch is not for everyone

Offline

#6 2012-05-12 17:26:58

cmtptr
Member
Registered: 2008-09-01
Posts: 135

Re: (C) Preferred method for callback with non-strict parameter type?

Xyne wrote:

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

Board footer

Powered by FluxBB