In the c programming language, there are no such things known as generics, but you can cheat your way of working with multiple types with macros and void pointers. For example lets say that we want a structure, called wrapper that simply stores a value of any type and any operations done, on the wrapper don't require anything to do with its value.
Using Macros
#define WRAPPER(TYPE) typedef struct {##TYPE value;} wrapper_##TYPE
Then the user can just paste WRAPPER(int);
on top of their code and they will have access to wrapper_int
or even paste it multiple times with different types.
Using Void*
typedef struct {void* value;} wrapper;
Now the user can cast their value to a void*
and lets say they want to use an int
. They need to cast their int to a void*
pointer. This is how functions such as malloc
, realloc
and calloc
work.
Problems
I'm creating a library for many things and one thing that I am currently creating is a dynamic array that works on any type and is also automatically freed.
To automatically free the arrays. I have created static global variables, one is an array storing the array and the other is the length of that array. Then using gnu c __attribute__((constructor))
, I am freeing both the arrays and the array containing the arrays.
This approach simply doesn't allow me to use the macro way of doing things, since all code needs to be defined in the library c file and not even the header.
So stuck to the void*
solution. It works for many different types, even including strings, but float
, double
and complex types(structures, unions and enums) don't work, since you can't cast them to a `void*.
I read serval stackoverflow questions about the same question. In general, I found out that you can't cast double
to void*
, but you can cast double*
to void*
.
Using just an int.
#include "kstring.h"
#include "array.h"
int main(int argc, const string* argv)
{
array a = create(0);
for (usize i = 1; i < argc; i )
{
append(&a, (type)(usize)(int)parse(argv[i]));
}
for (usize i = 0; i < a.length; i )
print("%d\n", (int)(usize)(a.elements[i]));
}
./build/app 1 2 3 2 1
1
2
3
2
1
It even works on strings.
#include "kstring.h"
#include "array.h"
int main(int argc, const string* argv)
{
array a = create(0);
for (usize i = 1; i < argc; i )
{
append(&a, (type)argv[i]);
}
for (usize i = 0; i < a.length; i )
print("%s\n", (string)(a.elements[i]));
}
./build/app X Y Z Y X
X
Y
Z
Y
X
But not on doubles, or any of the types mentioned.
#include "kstring.h"
#include "array.h"
int main(int argc, const string* argv)
{
array a = create(0);
for (usize i = 1; i < argc; i )
{
double x = parse(argv[i]);
append(&a, (type)&x);
}
for (usize i = 0; i < a.length; i )
print("%lf\n", *((double*)(a.elements[i])));
}
./build/app 1.1 2.1 3.3 1.2 1.1
1.100000
1.100000
1.100000
1.100000
1.100000
This is due, to the fact that it's storing the pointer to x and with each iteration the value is changing. So the value is what ever the latest value. How can I make this work?
type
is just an alias to void*
. Most of the code is using stuff from kstring.h
and array.h
which are defined by myself. All functions such as parse
and ... work correctly.
The problem is not from my library, rather how to work with my libraries' generic system. I've seen the source code of multiple other libraries and they were doing the same thing.
CodePudding user response:
You'll need to change your library to store arbitrary types. The main thing is that it needs to know the size of the element so that it can take the address of whatever the type is and use memcpy
to copy the bytes over.
For example:
struct array {
int length;
int esize;
unsigned char *elements;
};
struct array create(int esize, length)
{
struct array a;
a.length = length;
a.esize = esize;
a.elements = malloc(esize * length);
return a;
}
void append(struct array *a, const void *data)
{
a->length ;
a->elements = realloc(a->elements, a->esize * a->length);
memcpy(a->elements (a->esize * (a->length - 1)), data, a->esize);
}
void *get(struct array *a, int idx)
{
return a->elements (a->esize * idx);
}
Don't forget to add error checking for all calls to malloc
and realloc
.