Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create generic functions in C?

Tags:

arrays

c

generics

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)?

like image 359
sdasdadas Avatar asked Aug 08 '13 20:08

sdasdadas


People also ask

Can you do generics in C?

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.

What are generic functions in C?

A generic function is a function that is declared with type parameters. When called, actual types are used instead of the type parameters.

Are templates allowed in C?

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.

What is used to create generic functions and classes?

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.


2 Answers

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.

like image 53
Jacob Pollack Avatar answered Sep 21 '22 18:09

Jacob Pollack


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.

like image 29
Kaz Avatar answered Sep 21 '22 18:09

Kaz