12

Is it possible to create one function instead two functions of same purpose but different argument type?

I have two functions written in C, they convert image from RGB to HSV:

void png_rgb2hsv(pPNG_DATA data);
void jpg_rgb2hsv(pJPEG_DATA data);

they do exactly same thing: they take data->row_pointers and cycle it in a loop data->height times. Then it convert the values referred by data->row_pointers. This is how it works. But the only difference is that the data structures use different types. It seems to me senseless to use two functions for same thing. Especially when I would add more functions for more color spaces.

How this problem of program design is solved in practice in C?

Update: Most of readers didn't understand the question. I did not asked about overloading. It was question about design. I ask if is it possible to remove redundant function, "redundant" code. Because both functions are using same code, do the same thing, but the types in the function argument are different because one type is from libjpeg and the second type is from libpng. What I found is that it is not possible because it would mean use one variable for two different types.

John Boe
  • 3,501
  • 10
  • 37
  • 71
  • 4
    Given your description, why do you have two data types here? If all they are is containers for pixel data, they should be the same? Or have an identical "sub-type" that you could pass? – Mat Feb 08 '15 at 11:00
  • There are two different libraries for images like jpeg and png: libjpeg and libpng, they use different types. – John Boe Feb 08 '15 at 11:02
  • They have different types but you can use them **exactly** the same way? Same syntax, member names, etc.? – Mat Feb 08 '15 at 11:03
  • 2
    Void pointers are compatible with pointer to any object. You can use `void*` and add a "type" parameter: `void data2hsv(void *data, int datatype) { if (datatype == 0) /* use png */; else /* use jpg */; }` – pmg Feb 08 '15 at 11:05
  • Yes. Same member names. I can use them exactly same way. – John Boe Feb 08 '15 at 11:05
  • @user1141649 What is the point of having two different structs if they are used exactly the same way? –  Feb 08 '15 at 11:06
  • Should be noted that if you do function overloading, you still have multiple functions (or one function that does a lot of internal check/branches on type), you're just getting the compiler or preprocessor to generate them for you. – jamesqf Feb 08 '15 at 18:33
  • **This question is NOT about overloading** so it is not duplicate. If you understand it as I would ask for overloading, so you misunderstood my question. I asked if **is it possible to remove the redundant (duplicate) code which uses different type?**. I believe that it is not possible. Both functions do the same thing but using different type and I need use one variable name for different types. Using two names means I still have the duplicate code / function. See HolyBlackCat. – John Boe Feb 09 '15 at 11:03

6 Answers6

9

Use a generic macro:

void png_rgb2hsv(pPNG_DATA data);
void jpg_rgb2hsv(pJPEG_DATA data);

#define rgb2hsv(X) _Generic((X), pPNG_DATA: png_rgb2hsv, pJPEG_DATA: jpg_rgb2hsv)(X)

(If your compiler is too old and does not suport this trick (or you don't want to use it for some reason), then looks like there is no way to have 2 functions with same name and different argument type. Then, you would need to choose another solution.)

Update:

I misunderstood OP's question. If you want to create a single function for both types, you should do something like this:

void rgb2hsv_(Something *row_pointers, int height) // any member that you need goes here
{
    /* your code */
}

#define rgb2hsv(obj) \
do { \
_Generic((obj), pPNG_DATA: pPNG_DATA, pJPEG_DATA: pJPEG_DATA) tmp = (obj); \
    rgb2hsv_(tmp->row_pointers, tmp->height /*again, every member that you need should be stated here*/) \
} while (0)




Also, if pJPEG_DATA and pPNG_DATA have absolutely same internal layout (it means their members have same type and they are listed in the same order), you can try this: (it's not as safe as previous one, but at least it does not look like a bad hack)

void rgb2hsv(void *ptr)
{
    pPNG_DATA data = (pPNG_DATA *) ptr; // it does not matter which of 2 types you use here
    /* put your code that uses `data` here */
}

But keep in mind that if you swap 2 members in any of these structs or change their internal structure in any way, then this thing may no longer work.

( Also, you should know: these 2 techniques are just tricky workarounds to get a desired result. Best way is just to pass each needed member as separate argument and don't do this macro kung fu )

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Not everyone has access to a C11 compiler (+1 BTW). – pmg Feb 08 '15 at 11:13
  • @pmg Oops. I never used generic before. Looks like my code (and exmaple from cppreference.com) is wrong or even gcc does not support generics. – HolyBlackCat Feb 08 '15 at 11:23
  • How should the implementation look? – John Boe Feb 08 '15 at 11:24
  • 1
    @user1141649 You should create separate functions for png and jpg and then use `#define rgb2hsv(X) ...` as in my example to create a macro that will call one of these functions depending on argument type. – HolyBlackCat Feb 08 '15 at 11:27
  • I am not sure if I understand you correctly but this would mean to define the code implementation for both functions. Hence I don't see any advantage of using it. Unless what I should define is the rgb2hsv (but this is not possible because there is not any declaration of rgb2hsv in your code). – John Boe Feb 08 '15 at 11:39
  • 3
    @user1141649 Look at last line closer. It defines a macro `rgb2hsv`. You can use it as a function: compiler will replace `rgb2hsv(something);` with `png_rgb2hsv(something);` or `jpg_rgb2hsv(something);` depending on a type of `something`. – HolyBlackCat Feb 08 '15 at 11:43
  • *"You should create separate functions for png and jpg and then use"* What is sense of creating duplicate codes? I have two functions total: png_rgb2hsv, png_hsv2rgb and the code is cca 160 lines including some comments. I can see that the macro calls first or second function but that is not what I asked for. If I use your approach I must duplicate those 180 lines for jpeg functions. I asked **what I must to do to _create_ one function instead two functions**. What you do is that you create two functions and then you say which one you want to use. – John Boe Feb 08 '15 at 13:48
  • @user1141649 Oops, i misunderstood your question. I updated my answer. – HolyBlackCat Feb 08 '15 at 14:42
  • 1
    @HolyBlackCat both gcc and clang support generics; you can see that in action by clicking Run This Code [on cppreference](http://en.cppreference.com/w/c/language/generic) – Cubbi Feb 08 '15 at 15:47
  • @Cubbi Yes, you're right. GCC changelog says that generics support was added in 4.9 and I have 4.8.1. – HolyBlackCat Feb 08 '15 at 18:10
  • Aid you're gonna use a macro to fake a function, be prepared for bugs such as `rgb2hsv(getUserInput())`. I'm sure a user would be upset if they had to input something twice just to get your crappy programming back to work. – Cole Tobin Feb 08 '15 at 19:37
  • @ColeJohnson Thanks for noticing it. Can you suggest any less crappy technique? :D Becase I have only one solution: to create a variable inside `do {} while (0)` to store the argument. – HolyBlackCat Feb 08 '15 at 20:55
  • @HolyBlackCat I was thinking, just don't do this and just use two separate functions (like OP is already doing). A temp variable inside a `do {...} while (0)` would work though. – Cole Tobin Feb 08 '15 at 20:57
5

Void pointers are compatible with pointer to any object. You can use void* and add a "type" parameter.

void data2hsv(void *data, int datatype) {
    if (datatype == 0) {
        /* use png */
    } else {
        /* use jpg */
    }
}

Edit: lying to the compiler

void data2hsv(void *data) {
    pPNG_DATA source = data; // if data is of pJPG_DATA type 
                             // compiler will not catch the error
    /* use source as if it was pPNG_DATA */
}
pmg
  • 106,608
  • 13
  • 126
  • 198
  • `void rgb2hsv(void *data, int datatype) { int c = data->channels;}` warning: dereferencing 'void *' pointer error: request for member 'channels' in something not a structure or union.. how do you get the needed type there? – John Boe Feb 08 '15 at 11:31
  • You have to convert the `void*` to a pointer to the correct datatype: `pPNG_DATA pPNG = data; int c = pPNG->channels;` – pmg Feb 08 '15 at 11:37
  • 1
    If you can lie to the compiler you can use just the void pointer and convert inside the function to either PNG or JPG. Just remember: **if you lie to the compiler it will get its revenge sooner or later** – pmg Feb 08 '15 at 11:40
  • Can I do something like this? `void rgb2hsv(void *data, enum EXT datatype) { if (datatype==PNG) pPNG_DATA data=data; }` Because I got error: *expected expression before 'pPNG_DATA'* ... I am trying to use data for pPNG_DATA **or** for pJPEG_DATA. I need one name for two types, not two names for two types. – John Boe Feb 08 '15 at 12:04
  • Yes you can; but you cannot use `data` with two meanings. *See my edit* – pmg Feb 08 '15 at 12:15
  • But I cannot compile it, I have gcc (tdm-1) 4.7.1 and the errors stays there. What version of C do you use if you can compile it? If I declare source within if block, will not this declaration be limited to the scope of if block? – John Boe Feb 08 '15 at 12:59
  • I didn't try and compile it. I don't know what `pPNG_DATA` is, but it must be defined in a header file you need to include! – pmg Feb 08 '15 at 13:32
  • So **I cannot do it like that**. It simply does not work. I cannot redeclare the data and I cannot use one variable name for two different types. I cannot use any condition for this because any variable declared within the if block will be visible only in the scope of if. **If I use two names, so it is useless**. I would need to duplicate whole function code. Nonsense – John Boe Feb 08 '15 at 13:39
5

There are mainly two different ways to handle this. You create a struct which contains the image and also a tag what the data is, something like this:

struct img{
    char *filetype;
    void *data;
};

And then you create a function which checks filetype and call what ever function you want.

Or you create a struct with function pointers to what ever functions you want to use, something like this:

struct imgfuns{
    void (*rgb2hsv)(void *);
};

struct imgfuns *init_struct(void)
{
    struct imgfuns *funs = malloc(sizeof(*funs));
    if(funs == NULL)
        return NULL;
    if(png...)
        funs->rgb2hsv = png_rgb2hsv;
    else if(jpg...)
        funs->rgb2hsv = jpg_rgb2hsv;
    return funs;
}

int main(int argc, char *argv[])
{
    struct imgfuns *funs;
    funs = init_struct();
    if(funs == NULL)
      exit(1);
    /* get data from somewhere */
    funs->rgb2hsv(data);
    free(funs);
    return 0;
}

But it can be a pain if you need to use many different functions as you need to map them for each function. But you will get cleaner code and you can handle all formats in one place instead of creating wrapper functions for each file format.

More info can be found here: How do function pointers in C work?

Community
  • 1
  • 1
emil
  • 1,642
  • 13
  • 12
4

That's not possible in C, but in many other programming languages, such as C++, it is possible.

However, there are some tricks around that limitation in C, see this question for more information.

One common practice in C is to put the type in the function name, as in your example.

Community
  • 1
  • 1
Emil Laine
  • 41,598
  • 9
  • 101
  • 157
3

The followoing uses an additional parameter to inform the function what type of image data is passed:

void rgb2hsv(void *data, int data_type)
{
    char *row, *pix;
    switch (data_type) {
        case JPG: row= (pJPEG_DATA data)->row; break;
        case PNG: row= (pPNG_DATA  data)->row; break;
    }
    ....
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • Do I must to do it for every member which I need to work with? It is not possible to set the data to (pJPEG_DATA data) or to (pPNG_DATA data) directly? – John Boe Feb 08 '15 at 11:35
  • Yes, that is possible too. Just play with the example, as you are going to be a programmer! – Paul Ogilvie Feb 08 '15 at 12:30
  • But I don't use type char*. I use these types inside the struct: `png_bytep * row_pointers; png_uint_32 height; png_uint_32 channels;` And for the jpeg there would be something similar: `jpg_bytep * row_pointers; jpg_uint_32 height; jpg_uint_32 channels;`. So I cannot use your solution because you expect that I use same type, but they are different types. They are used in same way, but they are from different libraries – John Boe Feb 08 '15 at 13:59
  • Ifyou check those types, they will boil down to the same base type. png_bytep and jpg_bytep both seem to be pointers to byte, so are the same base type. If you want to avoid multiple functions, the whole trick is to unify the types so you can process them in one function or loop. – Paul Ogilvie Feb 08 '15 at 14:16
0

You cannot overload functions in C, but you can organize your code to make it optimal. I would create a function for rgb2hsv and that would get a void * type of parameter, would determine the type and use it, handling all possible cases. And you should use only this function. On lower levels you still duplicate your function, on higher level you will not have to call two separate functions. This would simplify usage.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • Would you use one variable name to perform all calculations or would you use two names? If the second, then you did not understand my question as most of ppl here. – John Boe Feb 08 '15 at 17:04
  • I would use one name for the operations. That would be a proxy function which handles the type and calls png_rgb2hsv if it is a png and calls jpg_rgb2hsv if it is a jpg. Effectively, you will have to functions, but instead of calling them all the time, you will have a proxy function which will handle the function choosing. You could implement this into a single function as well, but it would not be ideal from a management point of view. – Lajos Arpad Feb 09 '15 at 04:00
  • **Unfortunately all readers misunderstood the question**. I asked how **not to duplicate** the code or **how to remove the duplicate function**... It looks it is not possible because it is not possible to use one variable name for two different types. – John Boe Feb 09 '15 at 11:34