3

I have a pair of functions that both accept multiple parameters. Both function signatures are the same type except for one enum parameter:

typedef enum { a_1, a_2, a_3 } enum_a_t;
typedef enum_a_t * enum_a_p;

typedef enum { b_1, b_2      } enum_b_t;
typedef enum_b_t * enum_b_p;

void func_a(int ai, float af, enum_a_p ae_ptr) { /* blah */ }
void func_b(int ai, float af, enum_b_p ae_ptr) { /* blah */ }

Now because in my head I think of enums being implemented as integers, I thought maybe I could typedef a function that is compatible with both of these signatures, something like:

typedef void (* func_t)(int, float, int *);

But of course when assigning either of these functions to a variable of that type (e.g. func_t f_a = func_a), llvm-gcc complains:

warning: incompatible pointer types initializing 'func_t' (aka 'void (*)(int, float, int *)') with an expression of type 'void (int, float, enum_a_p)' (aka 'void (int, float, enum_a_t *)') [-Wincompatible-pointer-types]

I realize it's only a warning and I can probably get away with it if I'm careful, but I was curious if there is a strict / type-safe way to do something like this in C?

I would like to not have a warning on assignment, and I would like to be able to treat these functions as a common type without having to change their declared parameter lists.

edit

This is a very small example of the problem I'm experiencing in a much larger code base. Changing the declared parameter lists would require typecasting all direct invocations of those functions elsewhere in the code - a major change that would require a lot of integration testing (days of testing then involving others (this is on a weird embedded platform)) in areas of the code I really don't need to be modifying.

ardnew
  • 2,028
  • 20
  • 29
  • 1
    How about a union of the two enumerations? – Jeff Holt Jul 19 '17 at 22:20
  • 1
    See [Is the sizeof(enum) == sizeof(int), always?](https://stackoverflow.com/questions/1113855/is-the-sizeofenum-sizeofint-always) – BLUEPIXY Jul 19 '17 at 22:38
  • Regardless of the return type.... I don't see how you can make this work since `a_1 == b_1, a_2 == b_2`, how can you interpret the result? – Michaël Roy Jul 20 '17 at 08:40
  • @jeff6times7 if you mean using `typedef union { enum_a_t a; enum_b_t b; } enum_u_t;` and defining the function pointer as `typedef void (* func_t)(int, float, enum_u_t *)`, then i get the exact same warning but with `enum_u_t *` instead of `int *` as the incompatible actual parameter – ardnew Jul 20 '17 at 20:26
  • @MichaëlRoy those assignment statements are merely a demonstration -- they aren't meant to reflect any real functional behavior.. unless i'm misunderstanding your point? – ardnew Jul 20 '17 at 20:41
  • It is a very concrete implementation issue. If the function returns 0, what is the meaning of the return value? a_1, or b_1 (.. or c_1) ? This could be a show-stopper. – Michaël Roy Jul 20 '17 at 20:44
  • @MichaëlRoy see my edit, maybe that helps – ardnew Jul 20 '17 at 20:47
  • That doesn't make the issue of making sense of the return values go away. With this system, you give away the capacity for intelligent error handling and even simple centralized error reporting. This could lead to spaghetti creeping into the code in the not so far future. I don't know your application, but that's something that deserves a bit of thought. – Michaël Roy Jul 20 '17 at 20:54
  • 1
    @MichaëlRoy "With this system, you give away the capacity for intelligent error handling" -- I realize without more context this design might seem confusing to you, but showing its correctness and safety is an entirely different (yet _provable_) discussion from the question at hand – ardnew Jul 20 '17 at 21:14
  • Sure. Your app, my doubts. – Michaël Roy Jul 20 '17 at 21:16
  • "*exact same warning*" just drop this `int*` approach but use the same function signatures for both your functions by replacing `enum_a_p` *and* `enum_b_p` by `enum_u_t*`. BTW, `typedef`ing pointer types is often considered not the cleanest style. – alk Jul 21 '17 at 15:49
  • @alk changing the declared parameter lists of those functions is going to be a nightmare for integration testing i'm afraid. without that restriction, your solution certainly answers the question. but as-is, what i'm asking may be silly and impossible to do in a type-safe way. it looks like i will have to pony up and ditch the function pointer. thanks for your input – ardnew Jul 21 '17 at 16:23

2 Answers2

0

Assuming that the two functions can be modified, I would change the two functions to the format:

void func_a(int ai, float af, void * ae_ptr) { /* blah */ }
void func_b(int ai, float af, void * ae_ptr) { /* blah */ }

Then, when you enter each of the two functions, recast to the appropriate type enum_a_p or enum_b_p when referencing the data.

Jonathon S.
  • 1,928
  • 1
  • 12
  • 18
0

To call a function by function pointer with described arguments, the arguments must be compatibile. The pointers to different types are not compatible. The one solution is to declare functions without prototype and then declare the function pointer without arguments (or more precisely with undefined number of arguments):

void func_a();
void func_b();

void func_a(int ai, double af, enum_a_p ae_ptr) { /* blah */ }
void func_b(int ai, double af, enum_b_p ae_ptr) { /* blah */ }

typedef void (* func_t)();

Alternatively, you may use ellipsis:

void func_a(int ai, double af, ...) { /* blah with varargs */ }
void func_b(int ai, double af, ...) { /* blah with varargs */ }

typedef void (* func_t)(int ai, double af, ...);

However, please note that in both cases default type promotios apply - that's why the argument af need to be changed from float to double type.