0

I'm looking to use an array with multiple types -- strings and numbers (int). Here is what I have so far:

int main(int argc, char *argv[]) {

    int number = 2;
    void * array[] = {"helo", &number, "watkins"};
    int array_len = sizeof(array)/sizeof(array[0])

    for (int i=0; i<array_len; i++) {
        void *value = array[i];

        // how to do this in C?
        if (type(value) == char)
            printf("The string is: %s", value);
        else
            printf("The number is: %d", value);

    }

}

How would I test the type or do something equivalent in C?

  • 2
    You could make an array of unions. – stark Sep 22 '19 at 01:42
  • I think you want to make array like in Python or JavaScript? – Abdul Rehman Sep 22 '19 at 01:43
  • @AbdulRehman yes, similar to that. –  Sep 22 '19 at 01:44
  • @stark could you please show an example? –  Sep 22 '19 at 01:47
  • 1
    You could make an array of `struct variant { char type; void* data; }` and when reading it you check the `type` of each element to know what to do with it's respective `data`. Doing it in a more straightforward way is not possible because C isn't designed to support that, so the C syntax doesn't either. – Havenard Sep 22 '19 at 01:47
  • https://stackoverflow.com/questions/5127797/could-i-retrieve-the-datatype-from-a-variable-in-c , C does not support type introspection. – Saurabh P Bhandari Sep 22 '19 at 01:49
  • 1
    In C an array is made up of a single type of object. Now the array can be an array of struct. You cannot however declare an array of type `void *` as the type does not provide complete information about what you are declaring. – David C. Rankin Sep 22 '19 at 01:51
  • @DavidC.Rankin could you please show a better example then of what I'm trying to do by using the struct? –  Sep 22 '19 at 01:52
  • It is not possible, generally speaking, to "test the type" of an element of an array of `void` pointers. Your code will need to store type information (e.g. as an `enum` with distinct values corresponding to actual types) in another array. And your code will need to ensure consistency (e.g. not recording that an element of `array` is a `char *` when it is actually an `int *`) or face undefined behaviour. – Peter Sep 22 '19 at 03:02
  • @David, sorry for the delay, but [NateEldredge](https://stackoverflow.com/users/634919/nate-eldredge) did exactly what I was referring to, and formally used an `enum` to create integer constants for ease of readability. You can actually remove the `'u'` union name and just use an anonymous union which avoids having to put `.u.` in each reference. – David C. Rankin Sep 22 '19 at 03:59

2 Answers2

1

I like Nate's answer, so don't change your choice, but there is a subtle variation on it using an anonymous union that will allow you to refer to the members of the union directly without the need to use a union tag to reference the member.

This was introduced with the C11 Standard in 6.7.2.1 Structure and union specifiers(p13). It simply provides a bit of syntactical short-hand. For example using an anonymous union within the struct, e.g.

typedef struct {    /* struct containing tag and union between int/char[] */
    int tag;
    union {                 /* anonymous union allow you to refer  */ 
        char str[MAXC];     /* to members as members of the struct */
        int number;
    };
} mytype;
...
    mytype array[] = {{ISSTR, {"helo"}}, {ISINT, {{2}}}, {ISSTR, {"watkins"}}};

Your access to the union members would be:

            printf ("The string is: %s\n", array[i].str);

or

            printf ("The number is: %d\n", array[i].number);

(with no union tag between the array[i] and the str or number)

Putting it altogether, your example would be:

#include <stdio.h>

#define MAXC 16     /* if you need a constant, #define one (or more) */

enum { ISSTR, ISINT };  /* enum can provide global constants as well */

typedef struct {    /* struct containing tag and union between int/char[] */
    int tag;
    union {                 /* anonymous union allow you to refer  */ 
        char str[MAXC];     /* to members as members of the struct */
        int number;
    };
} mytype;

int main (void) {

    mytype array[] = {{ISSTR, {"helo"}}, {ISINT, {{2}}}, {ISSTR, {"watkins"}}};
    int array_len = sizeof array/sizeof *array;

    for (int i=0; i<array_len; i++) {
        // how to do this in C?
        if (array[i].tag == ISSTR)      /* if tag ISSTR element is string */
            printf ("The string is: %s\n", array[i].str);
        else if (array[i].tag == ISINT) /* if ISINT, element holds an int */
            printf ("The number is: %d\n", array[i].number);
        else
            fputs ("As Nate put it, things are horribly wrong...\n", stderr);
    }
}

Example Use/Output

$ ./bin/structunion
The string is: helo
The number is: 2
The string is: watkins

Keep in mind this was introduced with C11, in C99 you will not find an anonymous struct or anonymous union (though some compilers did provide by as an extension)

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

This feature doesn't exist in C at the level of the language. If you want something like this, you have to create it yourself. So your array elements need to be of a type that can contain either type of object, such as a struct or union. Furthermore, your code has to keep track of which type is which - the language will not do it for you.

So one approach would be something like the following:

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

enum my_type { IS_STRING, IS_INTEGER };

struct my_multitype {
    enum my_type t;
    union {
        char *s;
        int n;
    } u;
};

int main(int argc, char *argv[]) {

    struct my_multitype array[] =
        {
         { IS_STRING, { .s = "helo" } },
         { IS_INTEGER, { .n = 2 } },
         { IS_STRING, { .s = "watkins" } }
        };

    int array_len = sizeof(array)/sizeof(array[0]);

    for (int i=0; i<array_len; i++) {
        switch (array[i].t) {
        case IS_STRING:
            printf("The string is: %s\n", array[i].u.s);
            break;
        case IS_INTEGER:
            printf("The number is: %d\n", array[i].u.n);
            break;
        default:
            printf("Something is horribly wrong\n");
            abort();
            break;
        }
    }
}

Note that we are using designated initializers to initialize the correct member of the union in each case.

You could also use void * as you tried, but then you have to keep the type information in a separate array or something, and it's awkward. It's also awkward to initialize an array of such things, again as you saw with the integer, where you had to introduce an auxiliary variable.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • 1
    Yep, that's exactly how I would approach it, but you did one better with an `enum` for the tag, I just used a single `int tag;` for a flag. Without the named initializer you could do `{ {1, {"hello"}}, {0, {{2}}}, ...`. Also you can remove the `'u'` naming the union and just refer to, e.g. `array[i].s` and `array[i].n`. – David C. Rankin Sep 22 '19 at 03:55