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