10

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?

Matt
  • 22,721
  • 17
  • 71
  • 112
zeboidlund
  • 9,731
  • 31
  • 118
  • 180
  • Possible duplicate of [Iterating over same type struct members in C](https://stackoverflow.com/questions/1869776/iterating-over-same-type-struct-members-in-c) – phuclv Aug 21 '18 at 10:23

3 Answers3

29

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.

greydet
  • 5,509
  • 3
  • 31
  • 51
  • This is elegant. Would you mind explaining exactly how this works, though? I've never really found myself using unions before; while I know C++ fairly well, I'm still fairly new to C. – zeboidlund Jan 19 '13 at 21:51
  • 2
    Undefined behavior? "6.2.6.1.7: When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values." – Paul Hankin Jan 20 '13 at 09:04
  • Actually as stated in the above comment this may be compiler dependent. But AFAIK most of the compilers will do a simple mapping between bytes of the different union members. So in the above example vect3_a[0] maps to x, vect3_a[1] maps to y and vect3_a[2] maps to z. This is simply a syntaxic mean of accessing structure data. The overall vec3 instance will still be 3*sizeof(float). Another iteration solution is to simply use pointer incrementation. For instance &x + 1 will correspond to &y. Which is basically the same as using an array. – greydet Jan 20 '13 at 13:36
  • I've tested this on GCC x64 (Linux), if anyone is interested in seeing the code. It's quite simple and I haven't had any problems :). There is really no pragmatic reason for this code to be used in C++, either, since GLM pretty much takes care of all this. – zeboidlund Jan 20 '13 at 20:00
5

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));
 }
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • Would it be advantageous to make it `static`, `inline`, or a combination of the two? – zeboidlund Jan 19 '13 at 20:45
  • `static` would give it static, rather than global scope (so I don't understand why you think that would be good). `inline` would be a potential optimisation, but you would need to profile. – Paul Hankin Jan 20 '13 at 09:08
3

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.

RageD
  • 6,693
  • 4
  • 30
  • 37