简体   繁体   中英

C generic Parameter into Function pointer

Is it possible in C(not C++) to have a fuction pointer that takes a generic value(not a pointer), with -pedantic and -wall -werror flags set.

Note: I can't change the parameter Type. The code has to support uint8_t, uint16_t, etc... types as the parameters

Goal: to solve the problem with code.

Question

Is there a way to typecast a uint8_t(and/or uint16_t) parameter to a void*(Approach1)? specifically to pass a non-pointer type value to a void* value.

Is there a way to setup a Generic Type that will work with all the different values(Approach 2)?

Last resort Is there a way to set a specific compiler Exception in the code?( this question has been answer )

Approach 1(causes a invalid conversion from uint8_t to void*)

typedef struct
{
    void (*set_func)(void*);
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    uint8_t a = 123;
    uint16_t b = 321;
    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue to appease compiler warning
    pointerFunction.set_func = (void(*)(void*))&setValue;
    pointerFuncionShort.set_func = (void(*)(void*))&setShortValue;


    //use the function pointer with non-pointer parameter
    // Compile ERROR thrown invalid conversion from uint8_t to void*
    pointerFunction.set_func(a);
    pointerFuncionShort.set_func(b);
}

Aprroach 2(causes a Too Many Parameters Compile Error)

 typedef struct
{
    void (*set_func)();//Blank parameter to allow multiple args
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    uint8_t a = 123;
    uint16_t b = 321;

    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue to appease compiler warning
    pointerFunction.set_func = (void(*)())&setValue;
    pointerFuncionShort.set_func = (void(*)())&setShortValue;

    //use the function pointer with non-pointer parameter
    pointerFunction.set_func(a);// Compile ERROR thrown "Too many Args"
    pointerFuncionShort.set_func(b);// Compile ERROR thrown "Too many Args"
}

UPDATE

To add clarity to the problem. I have 100's of functions with 1 parameter. The 1 parameter of the functions are different types. I can't change any of the functions, but I want to have 1 function pointer type(or more based on type) to any of the functions. I can change any of the types associated with the function pointer and the type to the function pointer, but not what it is pointing too.

No, it is not.

Simple answer: The called function does not know how to even fetch the argument.

Details: The function code is already fixed when it is called (executed). So it contains code to access the argument, which depends on the type of the arguemtn (eg for an uint32_t, a 32 bit load/soter is required, for an uint8_t an 8 bit load/store). So it cannot handle even the value fetch properly. Different from C++ and higher languages like Python, C does not have a concept of run-time type identification built-in.

However, you can pass a union to the function and handle each variant in the function seperately. That would generate all possible accesses. However, you have to specify which actual type is being passed. This is normally done by a second argument which specifies the actual type . That union could also be a struct composed of the type-identifier and the actual value. But that is just an envelope, everything is still explicit.

typedef union {
    int i;
    float f;
} MyValue;

typedef enum {
    MY_VALUE_int,
    MY_VALUE_float
} MyValueType;

void func(MyValueType type, MyValue value)
{
    switch ( type ) {
        ...
    }
}

int main(void)
{
    func(MY_VALUE_int, (MyValueType){ .i=1 });
}

The compound literal argument only works for constants, otherwise you have to assign the value to a union first (or just use that union). gcc has an extension to avoid this, so you can have a function which takes such a union, but the caller may use a simple cast, instead of a compound literal. That works for variables, too:

    func(MY_VALUE_float, (MyValueType)1.0);

An alternative would be passing a const void * and internally casting. However, that is even more risky than the union approach.

All approaches require pasing the actual type explicitly (eg using an enum).

C11 allows to create a macro which evaluates different expressions, according to the type of an argument using the new _Generic construct. With that the original approach can be simulated (using gcc extension, normal way is possible, but more complicated):

// use the code of the first block here

#define func_generic(val) _Generic((val), \
    int : func(MY_VALUE_int, (MyValueType)(val)), \
    int : func(MY_VALUE_int, (MyValueType)(val)) )

// and call like:
func_generic(1);
func_generic(1.0);

However, note the restriction of _Generic : No two compatible types are allowed for the type-selector (ie const int and int are not allowed both) for uint16_t and uint32_t this works, however.

Note that gcc (you apparently use) supports C11 using -std=c11 or std=gnu11 . The latter also enables GNU-extensions.

Note that both of your exampels exibit Undefined Behaviour because you call a function through a pointer of another (function) type.

I found a solution that relies on the fact that there are a limited number of function types known in advance. I think however that it is too much hassle. Just call the original function.

enum GFType {
  GF_UINT8,
  GF_UINT16 // etc
};

struct GenericFunction {
  void (*func)(void);
  GFType type;
};

void callGenericFunction(GenericFunction func, uint64_t p) // largest type
{
  switch (func.type) {
  case GF_UINT8:
    ((void (*)(uint8_t))func.func)(p);
    return;
  case GF_UINT16:
    ((void (*)(uint16_t))func.func)(p);
    return;
  default:
    assert(1); // unimplemented function type
  }
}

void setValue(uint8_t byteValue) // Not a pointer parameter
{
  byteValue++;
}

void setShortValue(uint16_t byteValue) // Not a pointer parameter
{
  byteValue++;
}

int main() {
  uint8_t a = 123;
  uint16_t b = 321;

  GenericFunction pointerFunction;
  GenericFunction pointerFunctionShort;

  pointerFunction.func = (void (*)(void))setValue;
  pointerFunction.type = GF_UINT8;

  pointerFunctionShort.func = (void (*)(void))setShortValue;
  pointerFunction.type = GF_UINT16;


  callGenericFunction(pointerFunction, a);
  callGenericFunction(pointerFunctionShort, b);

  return 1;
}

Note that

a function-pointer may be freely converted to any other function-pointer type and back again, and you will get the original pointer.

This is what we use. We can't even use void * (because it is a data pointer, not a function pointer) to store the function pointer. So I used void (*)(void) to store the function pointer. An enum tells us to what kind of function we must convert it when we need to cal it.

If you can use C11, there is a way to do this using _Generic :

#include <stdio.h>
#include <inttypes.h>

#define setvalue_generic(x) _Generic((x), \
    uint8_t: setValue, \
    uint16_t: setShortValue \
    )(x)

void setValue(uint8_t byteValue)
{
    printf("setValue: %" PRIu8 "\n", byteValue);
    byteValue++;
}

void setShortValue(uint16_t byteValue)
{
    printf("setValue: %" PRIu16 "\n", byteValue);
    byteValue++;
}

int main(void)
{
    uint8_t a = 123;
    uint16_t b = 321;
    setvalue_generic(a);
    setvalue_generic(b);

    return 0;
}

Seems to work well with gcc -std=c11 -pedantic -Wextra -Wall .

The short answer is no.

You have several problems:

1) The different functions all have to have the same signature to allow the function pointer to point to them.

2) The functions are taking their args by value which means a copy will be passed in and any actions you take on the value will not appear to have any affect outside the function call. Since you don't allow pointers I cant see any way round this.

If you are not bothered about problem 2 then you could try declaring a variadic function which will accept args of any type.

eg

void doSomethingWithValue(enum MyType type ...)
{
    va_list args;
    va_start( args, type);

    switch( type)
    {
        case Uint8Type:
        {
             uint8_t value = va_arg(args, uint8_t);

             //doSomething to value
        }
        break;
        .
        .
        .
    }

    va_end(args);
}

Where MyType is an enum set up to identify which type is passed in. which is used like so:

 uint8_t value = 7;
 doSomethingWithValue(Uint8Type, value);
 //note that value is still 7

@bolov answer is good for handling the different types, this is just a different way of handling the same issue, but with 1 parameter.

The downside to this approach is that the type in main has to be GENERAL_TYPE. In my application I can change the type of the parameter, but I can change the type of the functions that I'm pointing to.

the (void(*)(GENERAL_TYPE))& handles the function's parameter types, and the Union handles the types of all the different sizes.

Another option is to have function pointers for each type too.

typedef union generalType
{
    uint8_t byteData;
    uint16_t shortData;
    uint32_t intData;
    int     integerData;
    uint64_t longData;
    void *  voidData;
    //Add any type
} GENERAL_TYPE;

typedef struct
{
    void (*set_func)(GENERAL_TYPE);
} SetFunction;

void setValue(uint8_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

void setShortValue(uint16_t byteValue)//Not a pointer parameter
{
   byteValue++;
}

int main()
{
    GENERAL_TYPE a.byteData = 123;//restricted to use GENERAL_TYPE here
    GENERAL_TYPE b.shortData = 321;
    SetFunction pointerFuncion;
    SetFunction pointerFuncionShort;

    //Typecast the setValue parameter to be a general type will 
    //Allow it to send the data of whatever type.
    pointerFunction.set_func = (void(*)(GENERAL_TYPE))&setValue;
    pointerFuncionShort.set_func = (void(*)(GENERAL_TYPE))&setShortValue;

    //use the function pointer with non-pointer parameter
    pointerFunction.set_func(a);
    pointerFuncionShort.set_func(b);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM