0

I'm using a library that callbacks to my functions. Those functions do basically the same thing; the only difference is that they have a different type of structure passed to the function.

static void func_a(void * a, TypeA * b) {
    ...
    printf("%d\n", b -> code);
}

static void func_b(void * a, TypeB * b) {
    ...
    printf("%s\n", b -> string);
}

Is there a way in C to avoid using duplicate code in this case?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
shiftas
  • 268
  • 2
  • 8
  • 1
    The `func(void *a, void *b, int type)` approach loses type checking and risks a mis-match of argument and `type`. There is a `_Generic` alternative, yet OP seems happy with the current solutions. To better answer, the library callbacks calls and requirements are needed. – chux - Reinstate Monica Dec 21 '20 at 09:40

3 Answers3

2

You could write a single function like this:

enum struct_type {
  A, B, // ...
};

static void func(void *a, void *b, enum struct_type st)
{
  // ...

  char const *str;

  switch(st) {
  case A:
    str = ((TypeA *)b)->string;
  case B:
    str = ((TypeB *)b)->string;
  // ...
  }

  printf("%s\n", str);
}

Is that what you are looking for?

Peter
  • 2,919
  • 1
  • 16
  • 35
1

One simple way is to

  1. Make use of an extra parameter type which tells you what type of struct you want to use
  2. use void* to store your required struct data.

A simple prototype would be like this:

static void func_common( void *a, void* data , int type )
{
    /* data contains your required data structure */
    /* type tells what type you want to use */
}

Inside func_common use can operate on your required data based on type

if (type == STRCUT1)
    printf("%d\n", *(int *)data->code);
else if (type == STRCUT2)
    printf("%s\n", (char*) data->string);

Note: You will have to take care of converting void* to required types based on what you are passing.

IrAM
  • 1,720
  • 5
  • 18
0

I upvoted Peter's answer, but wanted to point out the possibility of using struct "inheritance" (also see here) as an alternative solution.

The reason I would suggest this approach is that it provides better type safety during compile time, as we avoid using the void * pointer and the compiler can check for type errors.

i.e.,:

typedef struct {
  int type_id;
  int i;
  float f;
} common_s;

typedef struct {
  common_s parent;
  int code;
} type_A_s;

typedef struct {
  common_s parent;
  char *string;
} type_B_s;

static void func_common(void *a, common_s *b) { /* ... */ }

static void func_a(void *a, type_A_s *b) {
  func_common(a, &b->parent);
  printf("%d\n", b->code);
}

static void func_b(void *a, type_B_s *b) {
  func_common(a, &b->parent);
  printf("%s\n", b->string);
}

This is similar to how the kernel handles network socket addresses, where IP4, IP6 and Unix socket address types all have a common header field (which includes the address type identifier), and some functions are common to all types while others use the type identifier to route the address object to the correct function.

Myst
  • 18,516
  • 2
  • 45
  • 67