arrayscstruct

(C) Accessing Arrays with Indices and Components


I'm writing a library for matrix and vector mathematics and I find it would be convenient to access vector components as if the vertex were both an array vector[2] and a struct vector.y, all while being able to initialize the variable with curly braces (type) vector = {0, 1, 3};.

I found something close with the use of unions, namely

typedef union {
    struct {
        float x, y, z;
    };

    float data[3];
} v3;

which is great for the latter two criteria, but indexing the data with ints can only be done with vector.data[2].


Solution

  • I find it would be convenient to access vector components as if the vertex were [...] an array

    Through its definition in terms of pointer arithmetic, the C indexing operator [] requires one operand to be an integer and the other a pointer (possibly the result of automatic array-to-pointer conversion). Structures and unions are not acceptable operands. Pointers to structures and unions are acceptable, but they would not produce the semantics you want. C does not have operator overloading, so that's pretty much the end of the story. There is no way to define a C data type that is not an array type yet provides for array-like element access via the indexing operator.*

    I found something close with the use of unions

    Yes, and variations on your union approach are the best you can do for a data type that provides access to the vector components both via index and via name. When I write something along those general lines, I tend to choose the union member names in a way that expresses what is going on as clearly as possible. In this case, I might name the array member as_array, so that you might write vector.as_array[1]. YMMV.

    As an alternative, you could consider macro-based accessors for at least the access-by-name case. For example, given

    typedef float vector3[3];
    
    #define vec_element_x 0
    #define vec_element_y 1
    #define vec_element_z 2
    #define vec_element_0 0
    #define vec_element_1 1
    #define vec_element_2 2
    
    #define element(v,name) (v[vec_element_ ## name])
    

    , you could write

        vector3 dir = { 1, 2, 3 };
    
        // access by name
        float y = element(dir, y);
        element(dir, z) = 4;
    
        // access by index is supported this way too
        float x = element(dir, 0);
        element(dir, 1) = -0.5;
        // ... but only for integer constants 0, 1, 2, not expressions or variables
    

    * Where by saying element access, I mean to exclude pointer types such as float *, on the basis that the pointed-to object and others around it are not elements of the pointer.