I am getting back into using C, but I've been spoiled by generics in other languages. I have made it to the following piece of code in my implementation of a resizable array:
typdef struct {
void** array;
int length;
int capacity;
size_t type_size;
} Vector;
void vector_add(Vector* v, void* entry) {
// ... code for adding to the array and resizing
}
int main() {
Vector* vector = vector_create(5, sizeof(int));
vector_add(vector, 4); // This is erroneous...
// ...
}
In my attempt to make this generic, I'm now unable to add an integer to the vector without storing it in memory somewhere else.
Is there any way to make this work (either as is, or possibly a better approach to generics)?
Unlike C++ and Java, C doesn't support generics. How to create a linked list in C that can be used for any data type? In C, we can use a void pointer and a function pointer to implement the same functionality. The great thing about void pointer is it can be used to point to any data type.
A generic function is a function that is declared with type parameters. When called, actual types are used instead of the type parameters.
The main type of templates that can be implemented in C are static templates. Static templates are created at compile time and do not perform runtime checks on sizes, because they shift that responsibility to the compiler.
Generics can be implemented in C++ using Templates. Template is a simple and yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don't need to write the same code for different data types.
For my answer I am assuming that you are not familiar with the sections of memory (ie the use of the memory pool).
In my attempt to make this generic, I'm now unable to add an integer to the vector without storing it in memory somewhere else.
If you want to create a generic structure (as you did) then you will need to use void pointers. Consequently, from the use of void pointers you will need to store the values for each field on the memory pool, or uncommonly on the stack. Note, the structure is composed of void pointers and hence only memory addresses are contained within the structure, pointing to other locations in memory where the values are.
Be careful if you declare them on the stack as once your stack frame is popped from the call stack those memory addresses are not considered to be valid and hence may be used by another stack frame (overwriting your existing values within that collection of memory addresses).
Aside: If you migrate to C++ then you can consider the use of C++ templates.
Yes; you can embrace Greenspun's Tenth Rule and develop a full blown dynamic language in C, and in the process, develop a relatively clean C run time that can be used from within C.
In this project I did just that, as have others before me.
In the C run time of this project, a generic number would be created from a C number like this:
val n = num(42);
because of the way val
is represented, it takes up only a machine word. A few bits of type tag are used to distinguish a number from a pointer, from a character, etc.
There is also this:
val n = num_fast(42);
which is much faster (a bit manipulation macro) because it doesn't do any special checks that the number 42 fits into the "fixnum" range; it's used for small integers.
A function that adds its argument to every element of a vector could be written (very inefficiently) like this:
val vector_add(val vec, val delta)
{
val iter;
for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
val *pelem = vecref_l(vec, iter);
*pelem = plus(*pelem, delta);
}
return nil;
}
Since plus
is generic, this will work with fixnums, bignums and reals, as well as with characters, since it is possible to add integer displacements to characters via plus
.
Type mismatch errors will be caught by the lower level functions and turned into exceptions. For instance if vec
isn't something to which length
can be applied, length
will throw.
Functions with a _l
suffix return a location. Wherease vecref(v, i)
returns the value at offset i
in vector v
, vecref_l(v, i)
returns a pointer to the val
typed location in the vector which stores that value.
It's all C, just with the ISO C rules bent a little bit: you can't make a type like val
efficiently in strictly conforming C, but you can do it quite portably to architectures and compilers you care about supporting.
Our vector_add
isn't generic enough. It's possible to do better:
val sequence_add(val vec, val delta)
{
val iter;
for (iter = zero; lt(iter, length(vec)); iter = plus(iter, one)) {
val elem = ref(vec, iter);
refset(vec, iter, plus(elem, delta));
}
return nil;
}
By using the generic ref
and refset
, this now works with lists and strings also, not only vectors. We can do something like:
val str = string(L"abcd");
sequence_add(str, num(2));
The contents of str
will change to cdef
since a displacement of 2
is added to each character, in place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With