Question

Let's say I have a vector class:

typedef struct vec3_s
{
    float x, y, z;
}
vec3;

But, I would like to be able to iterate through it without converting it to an array of floats. While a cast is acceptable in this case, I'm curious to see if anything along the lines of C++ like functionality is doable in straight C. For example, in C++, since std::vector< T > has the subscript [] operator overloaded, I can pass the address of its first index to a function taking a void*.

i.e.,

void do_something_with_pointer_to_mem( void* mem )
{
    // do stuff
}

int main( void )
{
    std::vector< float > v;

    // fill v with values here

    // pass to do_something_with_pointer_to_mem

    do_some_with_pointer_to_mem( &v[ 0 ] );

    return;
}

Another, more concrete example is when calls to glBufferData(...) are made in OpenGL (when using C++):

glBufferData( GL_ARRAY_BUFFER, sizeof( somevector ), &somevector[ 0 ], GL_STREAM_DRAW );

So, is it possible to accomplish something similar in C using the subscript operator? If not, and I had to write a function (e.g., float vec3_value_at( unsigned int i )), would it make sense to just static inline it in the header file it's defined in?

Was it helpful?

Solution

If all of your structure fields are of the same type, you could use a union as following:

typedef union vec3_u
{
    struct vec3_s {
        float x, y, z;
    };
    float vect3_a[3];
}
vec3;

This way you could access to each x, y or z field independently or iterate over them using the vect3_a array. This solution cost nothing in term of memory or computation but we may be a bit far from a C++ like solution.

OTHER TIPS

You don't get the syntactic sugar of C++, but it's easy to write the function you'd write in C++ as operator[].

float get_vec3(v *vec3, int i) {
   switch(i) {
   case 0: return v->x;
   case 1: return v->y;
   case 2: return v->z;
   }
   assert(0);
 }

Now you can iterate through any vec3.

 for (int i = 0; i < 3; i++) {
     printf("%f\n", get_vec3(v, i));
 }

The problem in C with what you are trying to do is that you need to know how to move through a structure (i.e. you need to know the types). The reason std::vector<T> works the way it does is because it is using templates (a C++ concept). Now, that said, you could try something slightly different than what you suggested. If you do not want to use any arrays, you can store generic types. However, when retrieving the data and using it, the user will have to know what kind of data he or she is expecting. Below avoids arrays (although, a potentially cleaner solution exists in using them) and has a linked list implementation of something which gives you the nearly same flexibility of std::vector<T> (performance benefits aside since this is a linked list with O(n) operations for everything (you can be clever and reverse the list to achieve, perhaps, O(1) insert, but this is merely for example)

#include <stdio.h>
#include <stdlib.h>
typedef struct _item3_t
{
  void *x, *y, *z;
  struct _item3_t* next;
} item3_t;

typedef struct
{
  item3_t* head;
} vec3_t;

void insert_vec3(vec3_t* vec, void* x, void* y, void* z)
{
  item3_t* item = NULL;
  item3_t* tmp  = NULL;
  int i = 0;
  if(vec == NULL)
    return;

  item = malloc(sizeof(item3_t));
  item->x = x;
  item->y = y;
  item->z = z;
  item->next = NULL;

  tmp = vec->head;
  if(tmp == NULL) { // First element
    vec->head = item;
  } else {
    while(tmp->next != NULL)
      tmp = item->next;
    tmp->next = item;
  }
}

// This is one method which simply relies on the generic method above
void insert_vec3_float(vec3_t* vec, float x, float y, float z)
{
  float* xv, *yv, *zv;
  if(vec == NULL)
    return;
  xv = malloc(sizeof(float));
  yv = malloc(sizeof(float));
  zv = malloc(sizeof(float));

  *xv = x;
  *yv = y;
  *zv = z;

  insert_vec3(vec, xv, yv, zv);
}

void init_vec3(vec3_t* vec)
{
  if(vec == NULL)
    return;
  vec->head = NULL;
}

void destroy_vec3(vec3_t* vec)
{
  item3_t* item = NULL, *next = NULL;
  if(vec == NULL)
    return;

  item = vec->head;
  while(item != NULL) {
    next = item->next;
    free(item->x);
    free(item->y);
    free(item->z);
    free(item);
    item = next;
  }
}

item3_t* vec3_get(vec3_t* vec, int idx)
{
  int i = 0;
  item3_t* item = NULL;
  if(vec == NULL)
    return NULL;
  item = vec->head;
  for(i = 0 ; i < idx && item != NULL ; ++i)
    item = item->next;
  return item;
}

void do_something(item3_t* item)
{
  if(item == NULL)
    return;
  float x = *((float*)item->x);
  float y = *((float*)item->y);
  float z = *((float*)item->z);

  // To do - something? Note, to manipulate the actual
  // values in the vector, you need to modify their values
  // at their mem addresses
}

int main()
{
  vec3_t vector;

  init_vec3(&vector);

  insert_vec3_float(&vector, 1.2, 2.3, 3.4);

  printf("%f %f %f\n", *((float*)vec3_get(&vector, 0)->x), *((float*)vec3_get(&vector, 0)->y), *((float*)vec3_get(&vector, 0)->z));

  do_something(vec3_get(&vector, 0));

  destroy_vec3(&vector);

  return 0;
}

This code should compile right out of box. What you have here is a linked list which is your "vector" (particularly, a vec3 structure). Each node in the list (i.e. every element in the std::vector<T> sense) has 3 elements which are all void pointers. Thus, you can store any data type you wish here. The only catch is that you need to allocate memory for those pointers to point to and when removing an element, you will need to free that memory (refer to the vec3_destroy method for an example). Hope this helps a little more for understanding how these void pointers can work in your case.

To retrieve data, you won't be able to use the [] notation, but you can use the vec3_get method in the same way. The do_something method is an example stub of some way you might be able to accomplish something similar to what you mentioned in the OP.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top