4

I am working on a project that involves the implementation of the Stack data structure via a singly linked list. More specifically, I am wondering if there is a way to automatically cycle(iterate) through the attributes of a struct which happen to be of different data types -- it would greatly help when reading input, or when adding more attributes, so, I don't have to manually change everything.

Specific struct

typedef struct Student
{
    char regNumber[30];
    char fName[30];
    char lName[30];
    char email[50];
    int phoneNumber;
    short age;
} Student;

For example: attribute[0] would be regNumber, attribute[1] would be fName, attribute[n] would be the n^{th} element

Alexandros Voliotis
  • 321
  • 1
  • 3
  • 14
  • 1
    https://stackoverflow.com/questions/14418595/c-method-for-iterating-through-a-structs-members-like-an-array – Rashik May 13 '20 at 00:11
  • @Rashik I am aware of this solution but it is about attributes of the same data type, which in my case some of my data types are of different type. I know I could make all of them `char` but I am wondering if there is another workaround. – Alexandros Voliotis May 13 '20 at 00:14
  • You could use a more complex data type where you store a pointer to each element of the student struct and walk through that. It would work for any element data size, but requires additional storage for the pointers to the data and would need proper construction. It also would have issues with knowing what type of pointer to cast to and from so it would need some help there. If there were all char * though, it may work (a char * jagged array). In truth though, you are working against the language by doing this. C doesn't reflect. – Michael Dorgan May 13 '20 at 00:16
  • 2
    You cannot do this in C. If whatever you are doing relies on something like this to work then you're most probably thinking it in the wrong way. – Marco Bonelli May 13 '20 at 00:16
  • 1
    What you need are Reflection capabilities. Have a look here: https://github.com/loganek/mkcreflect – Robert Harvey May 13 '20 at 00:19
  • @AlexanderVoliotis You could technically build a `size_t` array of [`offsetof`](https://en.cppreference.com/w/c/types/offsetof) values for each data member, but that would be of little use if you don't know (or otherwise remember) their types, too. – dxiv May 13 '20 at 00:20
  • Create a new `struct` that represents an arbitrary data type that contains a `union` of all the types you might need, an identifier which indicates which is used, and use that to store your data. – tadman May 13 '20 at 01:03
  • Jumping to C++ is not an option? – Déjà vu May 13 '20 at 01:38

1 Answers1

3

I cannot think of any good way to do this that neither uses undefined behavior, nor very weird constructs. And the fact that you want fields of different type does not make it easier.

If I wanted to write code like this (which I don't) I would probably do something like this.

void *get_attr(struct Student *student, int field)
{
    switch(field) {
    case 0 : return (void*)&student->regNumber;
    case 1 : return (void*)&student->fName;
    case 2 : return (void*)&student->lName;
    case 3 : return (void*)&student->email;
    case 4 : return (void*)&student->phoneNumber;
    case 5 : return (void*)&student->age;
    }
    return NULL;
}

and then you can use it like this:

int main()
{
    struct Student s = { "xxx333", "Jonny", "BGood", "my@email.com", 12345, 22 };
    printf ("%s\n%s\n%s\n%s\n%d\n%d\n",
            (char*)get_attr(&s, 0),
            (char*)get_attr(&s, 1),
            (char*)get_attr(&s, 2),
            (char*)get_attr(&s, 3),
            *(int*)get_attr(&s, 4),
            *(short*)get_attr(&s, 5)
        );
}

Spontaneously, I don't see a good way around those casts. One way, but not necessarily a good way, is to do something like this:

union attr_field {
    char *c;
    int *i;
    short *s;
};

enum attr_type { CHAR, INT, SHORT };

struct attr {
    union attr_field attr;
    enum attr_type type;
};

struct attr get_attr2(struct Student *student, int field)
{
    struct attr ret;
    switch(field) {
    case 0 : ret.attr.c = student->regNumber; ret.type = CHAR; break;
    case 1 : ret.attr.c = student->fName; ret.type = CHAR; break;
    case 2 : ret.attr.c = student->lName; ret.type = CHAR; break;
    case 3 : ret.attr.c = student->email; ret.type = CHAR; break;
    case 4 : ret.attr.i = &student->phoneNumber; ret.type = INT; break;
    case 5 : ret.attr.s = &student->age; ret.type = SHORT; break;
    }
    return ret;
}

void print_attr(struct attr a)
{
    switch(a.type) {
    case CHAR: printf("%s\n", a.attr.c); break;
    case INT: printf("%d\n", *a.attr.i); break;
    case SHORT: printf("%d\n", *a.attr.s); break;
    }
}

int main()
{
    struct Student s = { "xxx333", "Jonny", "BGood", "my@email.com", 12345, 22 };

    for(int i=0; i<6; i++) {
        struct attr a = get_attr2(&s, i);
        print_attr(a);
    }
}

Note that I sometimes used struct and sometimes pointer to struct as argument to functions. The choice was not due to any particular reason. It just happened to be that way. You can do it either way, and both have their pros and cons. If performance is an issue, I'd go for pointers. Same thing with the union. I could have chosen a char array and used strncpy instead. And I could have skipped the pointers for int and short. Here my thought was something like that it's more clear if ALL union members are pointers. But you have to make your own decisions about all this. If you go for pointers, it might be wise to use the const qualifier where appropriate.

If you really want to do so, I guess you could do something like this:

void *attribs[6];
attribs[0] = (void*)s.regNumber;
printf("%s", (char*)attribs[0]);

That could be combined with the techniques mentioned above. For instance

struct attr attribs[6];
for(int i=0; i<6; i++)
    attribs[i] = get_attr2(&s, i);
klutt
  • 30,332
  • 17
  • 55
  • 95
  • 3
    There's no way to know what to do with those `void*` pointers and use them correctly without additional reflection details. – tadman May 13 '20 at 01:02