tags:

views:

115

answers:

5

I have a list in which i want to be able to put different types. I have a function that returns the current value at index:

void *list_index(const List * list, int index) {
    assert(index < list->size);
    return list->data[index];
}

In the array there are multiple types, for example:

typedef struct structA { List *x; char *y; List *z; } structA;
typedef struct structB { List *u; char *w; } structB;

Now in order to get data from the array:

structA *A;
structB *B;
for(j=0... ) {
 A = list_index(list, j);
 B = list_index(list, j);
}

But now how do I find out the type of the return value? Is this possible with typeof (I'm using GCC btw)?

And is this even possible or do i have to make some sort of different construction?

+2  A: 

You'll have to use unions like shown here.

Dario
A: 

You have some choices. Keep in mind that C is basically not a dynamically typed language.

You Make a common base for the structs, and put a simple type indicator of your own in it.

struct base {
  int type_indication:
};

then

struct structA {
   struct base base;
   ...
};

and then you can cast the pointer to (struct base *).

bmargulies
The question was about C, not C++.
Konrad Rudolph
+2  A: 

The best way to solve this would be to use unions.

Another way would be to memcpy() the list item to an actual struct (i.e., not a pointer) of the appropriate type. This would have the advantage of making each List item as small as possible.

A third way would be to just cast the pointer types as in type punning. C allows this as long as the object is dereferenced with its either its correct type or char.

Either way, you will need to put a code in each structure that identifies the type of object. There is no way the compiler can figure out what a pointer points to for you. And even if you could use typeof, you shouldn't. It's not C99.

Technically, if you don't use a union, you will have a problem making a legal C99 access to the type code, because you will need to make a temporary assumption about the type and this will violate the rule that objects must be dereferenced as their actual type, via a union, or via a char *. However, since the type code must by necessity be in the same position in every type (in order to be useful) this common technical violation of the standard will not actually cause an aliasing optimization error in practice.

Actually, if you make the type code a char, make it the first thing in the struct, and access it via a char *, I think you will end up with code that is a bit confusing to read but is perfectly conforming C99.

Here is an example, this passes gcc -Wall -Wextra

#include <stdio.h>
#include <stdlib.h>

struct A {
    char typeCode;
    int something;
};
struct B {
    char typeCode;
    double somethingElse;
};

void *getMysteryList();

int main()
{
    void **list = getMysteryList();
    int i;
    for (i = 0; i < 2; ++i)
        switch (*(char *) list[i]) {
        case 'A':
            printf("%d\n", ((struct A *) list[i])->something);
            break;
        case 'B':
            printf("%7.3f\n", ((struct B *) list[i])->somethingElse);
            break;
        }
    return 0;
}

void *getMysteryList()
{
    void **v = malloc(sizeof(void *) * 2);
    struct A *a = malloc(sizeof(struct A));
    struct B *b = malloc(sizeof(struct B));

    a->typeCode = 'A';
    a->something = 789;
    b->typeCode = 'B';
    b->somethingElse = 123.456;

    v[0] = a;
    v[1] = b;
    return v;
}
DigitalRoss
Its not a violation of the standard as long as you always access the type code field by its type. You do need to ensure that the type code field has the same type and location in all your structs.
Chris Dodd
A: 

Make the elements you want to put into the list all inherit from a common base class. Then you can have your base class contain members that identify the actual type.

class base {
public:
  typedef enum {
    type1,
    type2,
    type3
  } realtype;

  virtual realtype whatAmI()=0;
};

class type_one : public base {
  public:
    virtual base::realtype whatAmI() { return base::type1; };
};

class type_two : public base {
  public:
    virtual base::realtype whatAmI() { return base::type2; };
};

After that, you'd declare your list type like:

std::list<base *> mylist;

and you can stuff pointers to any of the derived types into the list. Then when you take them out, you can just call 'whatAmI()' to find out what to cast it to.

Please note: Trying to do this in C++ means you are doing something in a way that's not a good match for C++. Any time you deliberately evade the C++ type system like this, it means you're giving up most of the usefulness of C++ (static type checking), and generally means you're creating large amounts of work for yourself later on, not only as you debug the first iteration of this app, but especially at maintenance time.

Michael Kohne
The question is tagged C, not C++.
Jonathan Leffler
So it is. I saw his 'List' type and just assumed a container of objects. So my answer only applies if you are willing to go to C++.
Michael Kohne
A: 

C handles types and typing entirely at compile time (no dynamic typing), so once you've cast a pointer to a 'void *' its lost any information about the original type. You can cast it back to the original type, but you need to know what that is through some other method.

The usual way to do this is with some kind of type tag or descriptor in the beginning of all the objects that might be stored in your list type. eg:

typedef struct structA { int tag; List *x; char *y; List *z; } structA;
typedef struct structB { int tag; List *u; char *w; } structB;
enum tags { structAtype, structBtype };

You need to ensure that every time you create a structA or a structB, you set the tag field properly. Then, you can cast the void * you get back from list_index to an int * and use that to read the tag.

void *elem = list_index(list, index)
switch (*(int *)elem) {
case structAtype:
    /* elem is a structA */
              :
case structBtype:
    /* elem is a structB */
Chris Dodd