-2

I'm passing a pointer to my function, but the type of this pointer may change. I don't want to write several versions of this function such as func_float() and func_int(). So I'm passing a pointer of void* to my function and then type casting to int* or float* which depends on an argument type. Here's my code:

void func(void* pSrc, float* pDst, int len, int type)
{
    if (type == 0)
        float* pIn = (float*)pSrc;
    else
        int* pIn = (int*)pSrc;
    float* pOut = pDst;
    while (len--)
        *pOut++ = (float)*pIn++ * 2.0;  // cast to float and then do some math
}

int main()
{
    int input[3] = { 1,2,3 };
    float output[3] = { 0 };
    func(input, output, 3, 1);
    return 0;
}

But this seems not work. VS told me that a dependent statement may not be a declaration. I tried to declare pIn as void* before if, but I got another error expression must be a pointer to a complete object type when I dereference the pointer pIn.

Any help would be appreciated.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
ZR Han
  • 238
  • 1
  • 3
  • 10
  • 2
    The declarations for `pIn` are local to the `if` branch and to the `else` branch, they do not exist after the if/else. You will have to duplicate the algorithm in each of the two branches. – prog-fh Aug 26 '21 at 10:43
  • One way is to make a macro that defines your function, and takes the type as macro parameter (and token pastes so the function has unique names – M.M Aug 26 '21 at 10:44
  • 1
    In this particular case it would be easier to just write 2 functions – M.M Aug 26 '21 at 10:45
  • 1
    I get so many other compiler errors. Notably, your function parameters are of the wrong types and you pass an incorrect number of them. – Lundin Aug 26 '21 at 11:00
  • Consider that `(float)` does something completely different depending on whether \*pIn++ is a float or an int. How would the machine code look like? The compiler would have to write two different functions, one for float and one for int! – user253751 Aug 26 '21 at 11:22

3 Answers3

3

There's lots of problems here. Wrong number of parameters, wrong types used, wrong scope for variables - very fundamental stuff.

In addition, you cannot do arithmetic on void pointers. gcc provides that as a non-standard extension, but then my_void_pointer++ doesn't mean "increase 1 item" but "increase 1 byte", similar to character type pointers.

Since the void pointer doesn't contain any type information, the compiler can't know many bytes the pointed at item contains. I'd advise to simply forget about void pointer arithmetic and gcc extensions in general.

To answer the question of how to do this in C, there are 3 different ways I can think of:


The KISS principle/common sense solution is to simply avoid type-generic programming in C - its usefulness is overall overrated - and write two separate functions:

#include <stdio.h>

void func_int (int* dst, const int* src, size_t size)
{
  for(size_t i=0; i<size; i++)
  {
    dst[i] = src[i] * 2;
  }
}

void func_float (float* dst, const float* src, size_t size)
{
  for(size_t i=0; i<size; i++)
  {
    dst[i] = src[i] * 2;
  }
}

int main (void)
{
    int input_i[3] = { 1,2,3 };
    int output_i[3];
    func_int(output_i, input_i, 3);
    for(size_t i=0; i<3; i++)
    {
      printf("%d ", output_i[i]);
    }
    
    puts("");

    float input_f[3] = { 1.0f, 2.0f, 3.0f };
    float output_f[3];
    func_float(output_f, input_f, 3);
    for(size_t i=0; i<3; i++)
    {
      printf("%f ", output_f[i]);
    }
}

If you still insist on type-generic programming ("Keep it complicated for the sake of complexity"), then you can use standard C _Generic:

#define func(dst,src,size) _Generic((dst), int*: func_int, float*: func_float) (dst,src,size)

func(output_i, input_i, 3);
func(output_f, input_f, 3);

And finally you can do "template meta programming" in the same manner, by using a combination of _Generic and X-macros to generate the function itself ("Keep it stupidly complicated")

#include <stdio.h>

#define SUPPORTED_TYPES(X) \
  X(int)                   \
  X(float)                 \

#define FUNC_DEFINITIONS(type)                              \
void func_##type (type* dst, const type* src, size_t size)  \
{                                                           \
  for(size_t i=0; i<size; i++)                              \
  {                                                         \
    dst[i] = src[i] * 2;                                    \
  }                                                         \
}
SUPPORTED_TYPES(FUNC_DEFINITIONS)

#define GENERIC_LIST(type) ,type*: func_##type
#define func(dst,src,size) _Generic((dst) SUPPORTED_TYPES(GENERIC_LIST)) (dst,src,size)

int main (void)
{
    int input_i[3] = { 1,2,3 };
    int output_i[3];
    func(output_i, input_i, 3);
    for(size_t i=0; i<3; i++)
    {
      printf("%d ", output_i[i]);
    }
    
    puts("");

    float input_f[3] = { 1.0f, 2.0f, 3.0f };
    float output_f[3];
    func(output_f, input_f, 3);
    for(size_t i=0; i<3; i++)
    {
      printf("%f ", output_f[i]);
    }
}

Overall, good programmers keep their code simple and bad programmers try to write it as complex as possible. That's why you should think twice before deciding which of these 3 versions to use.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Initially I wrote two functions but I’m wondering if there is a better way. All of you strongly recommend two separate functions, so I’ll do it in that way. Thank you! – ZR Han Aug 26 '21 at 12:21
  • [KISS](https://en.wikipedia.org/wiki/KISS_principle) is a good principle — but then, so is [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). Sometimes it's tricky to know which principle to favor. – Steve Summit Aug 26 '21 at 12:40
  • @SteveSummit DRY makes sense in many scenarios, sure, but it is also dangerous and often does more harm than good. Like in my X macro example when someone decides to obfuscate their program to oblivion just to save a bit of repetition. Or for countless other examples, just look at the on-going destruction of the C++ language. The chance of introducing bugs because of increased complexity is often bigger than the change of bugs caused by repetition. – Lundin Aug 26 '21 at 12:56
  • @Lundin That's what I meant by "Sometimes it's tricky to know which principle to favor." – Steve Summit Aug 26 '21 at 12:58
2

The variables cease to exist at the end of their scope. You would have to do something like this:

void func(void* pSrc, float* pDst, int len, int type)
{
    if (type == 0)
        func_float(pSrc, pDst, len);
    else
        func_int(pSrc, pDst, len);
}

and then implement func_float and func_int

If you want to support many types, this could be good:

void foo(void* pSrc, float* pDst, int len, int type) 
{
    switch(type) {
        case 0: func_float(pSrc, pDst, len); break;
        case 1: func_int(pSrc, pDst, len); break;
        // More cases:
        default: perror("Invalid type"); exit(EXIT_FAILURE);
    }
}
klutt
  • 30,332
  • 17
  • 55
  • 95
2

You are, in effect, trying to make the type of your pIn variable differ, depending on the value of the passed-in type flag. But there's no way to do that. Each variable must have exactly one type, known at compile time.

The closest you could achieve to what you're trying to do would probably be something like this:

void func(void *pSrc, float *pDst, int len, int type)
{
    char *pIn = pSrc;        /* char * so can do pointer arithmetic */
    float *pOut = pDst;
    while (len--) {
        *pOut++ = (type == 0 ? *(float *)pIn : *(int *)pIn) * 2.0;

        if (type == 0)
             pIn += sizeof(float);
        else pIn += sizeof(int);
    }
}

This is kind of an ugly kludge, however. If your actual func is more complicated, it might be worth it. But if all you're doing is multiplying by 2, it would probably cleaner to just bite the bullet and use two separate functions. (Sorry, I know you said that's not what you wanted to do.)

The subexpression (type == 0 ? *(float *)pIn : *(int *)pIn) is somewhat of a jawbreaker as I've written for. (C is both celebrated, and notorious, for allowing this kind of pithiness.) You might prefer to write things out in a more "longhand" way:

void func(void *pSrc, float *pDst, int len, int type)
{
    char *pIn = pSrc;        /* char * so can do pointer arithmetic */
    float *pOut = pDst;
    while (len--) {
        double inVal;

        if (type == 0) {
            inVal = *(float *)pIn;
            pIn += sizeof(float);
        } else {
            inVal = *(int *)pIn;
            pIn += sizeof(int);
        }

        *pOut++ = inVal * 2.0;
    }
}

This second formulation would also make things easier and cleaner if you have more different values for type, or if you're doing more involved things with inVal.


Addendum: Here's a completely different approach. In your original problem statement, you had either an array of int, or an array of float, and you wanted to multiply every element by 2, without repeating too much code. I assume that in your actual situation, you have functions to apply that are more involved than just "multiply by 2".

If you're comfortable using function pointers, you could literally write a pair of "apply function to int" and "apply function to float" functions, with the actual do-the-work function specified separately, and exactly once. Here are the two "apply function" functions:

void apply_func_to_int(double (*func)(double), int *pSrc, float *pDst, int len)
{
    for(int i = 0; i < len; i++)
        pDst[i] = func(pSrc[i]);
}

void apply_func_to_float(double (*func)(double), float *pSrc, float *pDst, int len)
{
    for(int i = 0; i < len; i++)
        pDst[i] = func(pSrc[i]);
}

As you can see, they are very, very similar to each other — basically a copy-and-paste job — but they're both so short, the repetition isn't too objectionable.

Here is the function to apply:

double func(double x)
{
    return x * 2;
}

Notice that you only have to write this function once. It accepts and returns a double, for reasonably full generality.

Putting it all together:

int main()
{
    int i;
    int input[3] = { 1, 2, 3 };
    float output[3] = { 0 };
    apply_func_to_int(func, input, output, 3);
    for(i = 0; i < 3; i++) printf("%f ", output[i]); printf("\n");

    float input2[3] = { 1.1, 2.2, 3.3 };
    apply_func_to_float(func, input2, output, 3);
    for(i = 0; i < 3; i++) printf("%f ", output[i]); printf("\n");
}

And of course you can apply other functions:

#include <math.h>

/* ... */

    float input3[4] = { 2, 10, 25, 1.44 };
    apply_func_to_float(sqrt, input3, output, 4);
    for(i = 0; i < 4; i++) printf("%f ", output[i]); printf("\n");
Steve Summit
  • 45,437
  • 7
  • 70
  • 103