3
int* push_back_int(intVector* target, int push)
{
    target->length++;
    target->val = (int *)realloc(target->val, target->length * sizeof(int));
    target->val[target->length - 1] = push;
    return &target->val[target->length - 1];
}
float* push_back_float(floatVector* target, float push)
{
    target->length++;
    target->val = (float *)realloc(target->val, target->length * sizeof(float));
    target->val[target->length - 1] = push;
    return &target->val[target->length - 1];
}

Is there any way that I can hold a variable to replace the cast to int* or float* so that i can reuse the same code for multiple variable types using void*

  • 3
    No, types have to be specified statically. But read [this blog](https://levelup.gitconnected.com/using-templates-and-generics-in-c-968da223154d) for how to emulate generic functions in C. – Barmar Sep 09 '22 at 02:35
  • Does this answer your question? [Pseudo-generics in C](https://stackoverflow.com/questions/16522341/pseudo-generics-in-c) – abdo Salm Sep 09 '22 at 03:10
  • 1
    If you use macros to eliminate duplicate code that is generally a good idea. It's even better if you can use real functions. Macros work via text substitution and not code so you hit limits quickly. You can't pass a text arguments with a comma, you can't generate macros etc). If you manage to wrap everything in suitable macros, you end up with your own language (foreign to others). If something breaks you have to reverse engineer your macro to figure out what really went wrong. – Allan Wind Sep 09 '22 at 03:34
  • If you store different types in the same data structure, you also need to keep track of the type for each element so you can cast it when you need to read it again (from `void *`). You need to suitable alignment so it works for all types you plan on storing in it. – Allan Wind Sep 09 '22 at 03:36
  • If realloc() fails you blow away your original pointer. – Allan Wind Sep 09 '22 at 04:10
  • Without wishing starting a conflict, would a union serve somehow? – Fe2O3 Sep 09 '22 at 04:16
  • You can do polymorphism in C, you just have to manage the VTABLE concept yourself :-) See https://stackoverflow.com/questions/351733/how-would-one-write-object-oriented-code-in-c/351745#351745 – paxdiablo Sep 09 '22 at 04:44
  • @Barmar not all types need to be specified statically. VLAs are a common counter-example. Doubtful that a VLA will solve OP's fuzzy issue though. – chux - Reinstate Monica Sep 09 '22 at 13:17
  • @chux-ReinstateMonica If you consider the size part of the type, true. But you can't choose between types like `int` and `float` dynamically. – Barmar Sep 09 '22 at 15:14

3 Answers3

1

No. In C the type is only available at compile-time.

You can use void * to pass data back and forth but you need to retain the element size. This approach is referred to as non-type safe (compiler will not catch the wrong "type", say, switching iq and fq below, which will then blow up most impressively at run-time when you get it wrong). Note how calling code handles the cast.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct queue {
    size_t push_length;
    void *val;
    size_t length;
};

void *push_back(struct queue *target, void *push) {
    size_t offset = target->length * target->push_length;
    target->length++;
    void *tmp = realloc(target->val, target->length * target->push_length);
    if(!tmp) {
       // error handling
       return NULL;
    }
    target->val = tmp;
    return memcpy((char *) target->val + offset, push, target->push_length);
}

int main() {
    struct queue fq = { sizeof(float), NULL, 0 };
    push_back(&fq, &(float) { 2.718 });
    push_back(&fq, &(float) { 3.142 });
    for(unsigned i = 0; i < fq.length; i++) {
        printf("%u: %f\n", i, ((float *) fq.val)[i]);
    }

    struct queue iq = { sizeof(int), NULL, 0 };
    push_back(&iq, &(int) { 1 });
    push_back(&iq, &(int) { 2 });
    for(unsigned i = 0; i < iq.length; i++) {
        printf("%u: %d\n", i, ((int *) iq.val)[i]);
    }
}

and the output:

0: 2.718000
1: 3.142000
0: 1
1: 2

Your platform may require specific alignment for each element of val (i.e. for type T push_length = sizeof(T) % alignof(T) ? (sizeof(T) / alignof(T) + 1) * alignof(T) : sizeof(T)).

Allan Wind
  • 23,068
  • 5
  • 28
  • 38
1

Can I cast a variable to a type decided during execution (?)

Yes, in some cases that support variable length array.

(double (*)[r]) is a cast to a type determined at run time. Demonstrative code follows:

  int r = rand();
  double a[r];
  double (*b)[r] = &a;
  unsigned char *data = malloc(sizeof a);
  b = data;  // warning: assignment to 'double (*)[r]' from incompatible pointer type 'unsigned char *' [-Wincompatible-pointer-types]
  b = (double (*)[r]) data;  // OK
  (void) b;

In OP's code, the cast nor the sizeof(type) are not needed.
Use target->val = realloc(target->val, target->length * sizeof(target->val[0])); for both.

Only difference remaining in push_back_...() is the function name and signature.


Is there any way that I can hold a variable to replace the cast to int* or float* so that i can reuse the same code for multiple variable types using void*.

Any object pointer can be held in a void *. Yet that void * does not certainly retain anything to denote the type from which it is assigned. Auxiliary data needed. In OP's case, the size of the type would be enough if the size was consistent per vector.

typedef struct {
  size_t object_size;  
  size_t length;
  void *val;
} gVector;

// Pass in the address of the object to push
void* push_back_g(gVector* target, const void *push) {
  void *p = realloc(target->val, target->object_size * (target->length + 1u));
  if (p) {
    target->val = p;
    return memcpy((unsigned char *)val + target->object_size *  target->length++, 
        push, target->object_size);
  }
  // Handle error with TBD code
  return NULL;
}

Alternative we could pass in the size per push call and store that too.

Yet in both cases, code loses type checking.


With _Generic, code could handle various pre-determined select types with common code. Yet it seems OP wants any type.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

C does not have runtime type information (RTTI) so you would have to hack that out manually by introducing an enum or similar. However, I would not encourage type generic programming with enums + void pointers in modern C programming since it is type unsafe and clunky.


We could use macros to generate type-generic code based on a number of supported types, kind of like a poor man's version of C++ templates. But that creates code which is very hard to read and maintain, so it isn't advisable either. Just for the record, it might look like this:

// NOT RECOMMENDED PRACTICE

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

#define vector_define(type)  \ 
  typedef struct             \
  {                          \
    type* val;               \
    size_t length;           \
  }type##Vector;
SUPPORTED_TYPES(vector_define)

#define push_back_define(type)                                           \
  type* push_back_##type (type##Vector* target, type push)               \
  {                                                                      \
    target->length++;                                                    \
    target->val = realloc(target->val, target->length * sizeof(type));   \
    target->val[target->length - 1] = push;                              \
    return &target->val[target->length - 1];                             \
  }
SUPPORTED_TYPES(push_back_define)

As you can see it starts to look like a different language and the code is pretty alien to read for anyone who isn't used at seeing "X-macros".


In this case the best solution is perhaps to write a version using void* but to wrap it in a type safe macros utilizing _Generic. Your vector could then be implemented as a single type-generic ADT. Not a separate one for int, float and so on. As for how to do that proper, it's too long an answer for SO - a place to start would be to read up on How to do private encapsulation in C?.

A simple example of how to use _Generic without creating a proper ADT:

#include <stdio.h>

void push_back_int (int* target, int push)
{ puts(__func__); }

void push_back_float (float* target, float push)
{ puts(__func__); }

#define push_back(arr,item)            \
  _Generic((arr),                      \
            int*:   push_back_int,     \
            float*: push_back_float)   \
  ((arr), (item) )

int main (void)
{
  int* i_arr;
  int i_item = 5;
  push_back(i_arr, i_item);

  float* f_arr;
  float f_item = 123.0f;
  push_back(f_arr, f_item);
}
Lundin
  • 195,001
  • 40
  • 254
  • 396