Search code examples
c++visual-c++visual-studio-2012referencevariable-length-array

How to get VC++ to access a pointer as a 2D array


I'm doing a little graphics programming and I have a two dimentional array (that varies in size during program execution) that I store using openGL.
So when I go to access it, all I get is a void pointer back.

To make the logic easier, I want the compiler to pretend that it is, and use it as, a 2D array (because arr[i][j] is more concise and less error prone than ptr[i * y + j]).


This clever method of casting I found works fine in GCC (on the linux machines at uni):

Vertex (&vertices)[tess][tess] = *reinterpret_cast<Vertex (*)[tess][tess]>(
    glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)
);

Which basically casts the block of memory pointer openGL gave me to a tess X tess 2D array, and creates a reference of that type to point at it.
This allows me to access the memory like vertices[i][j].
Vertex is just a typedefed struct containing floats

However, at home on my Windows machine, VS'12 has a hissy fit, complaining that it requires the integers where tess is written to be constant (specifically; error C2057: expected constant expression).
I have no idea why.

Now, I understand that VS doesn't support VLA's, but I am not creating an array here, I'm creating a reference to something that I don't know the size of 'till runtime.
So it shouldn't care if the size changes between function calls, right? Why is this not allowed?


Not to be deterred I tried using std::array

std::array<std::array<Vertex, tess>, tess>& vertices;

And apart from the obvious references must be initialized this test didn't help me because it still complained about expression must have a constant value (specifically; error C2975: '_Size' : invalid template argument for 'std::array', expected compile-time constant expression)


I am at a loss at what to try here, I was so proud of the reinterpret_cast and how simple it made things and was sure I wasn't using a method that was contravening the standard.
I don't want to create a std::vector from the pointer then copy the data from that dynamic array back into the pointer location when I'm finished; that just seems so inefficient when the memory block is already just sitting there!
There's no way to create a vector around a pre-existing block of memory, is there? ..no that sounds silly.

I want to see if this can be done without giving up and just using it as Vertex*; Ideas?
Can someone enlighten me as to why it isn't working in VS?
Is there something I can do to get it working (extensions/updates to VS)?
Does VS'13 add support for this?

I am also getting the error C2087: 'vertices' : missing subscript that I can't explain.
As well as these other errors that seem to show VS desperately wants tess to be constant:
error C2466: cannot allocate an array of constant size 0
error C2540: non-constant expression as array bound
error C2440: 'initializing' : cannot convert from 'Vertex [1][1]' to 'Vertex (&)[][1]'


Solution

  • Well that was fun; I implemented a class to handle exactly what I wanted.
    It's not as typesafe as I'd like, but I learned a lot doing it
    Much like how I felt implementing should-be-a-part-of-the-specification, syntactic-sugar-esque functionality for javascript before I discovered jQuery.

    Basically, instead of being able to do this.

    int (&array)[x][y] = *reinterpret_cast<int (*)[x][y]>(pointer);
    

    You will have to do this

    MDAI<int, 2> array = MDAI<int, 2>(pointer, x, y);
    

    But other than that it works flawlessly! :D
    I initially wrote just a specialised TwoDArray class but found I actually had some 3D arrays too.
    So instead of implementing a 3D version (that returned TwoDArray when you drilled down) I made something more generic and can help with arrays of as many dimensions as you'd like.


    #include <Windows.h>
    #include <iostream>
    
    /*MultiDimensional Array Interpretation
    has the compiler use a flat pointer reference as if it were a faceted array
    
    C++11/GCC VLA-supporting equivalent:
    int (&array)[x][y] = *reinterpret_cast<int (*)[x][y]>(pointer);
    
    using MDAI, <C++11 and MSVS compatible:
    MDAI<int, 2> array = MDAI<int, 2>(pointer, x, y);
    */
    template<class Type, unsigned int dimension>
    class MDAI {
    private:
        Type* array;
        //+1 to guard against zero-length-array
        unsigned int bounds[dimension + 1];
    
    public:
        //unfortunately I can't use `unsigned int &(dimensions)[dimension]` to make it safe
        //because of how operator[]() tries to construct its return value
        /*constructor*/
        MDAI(Type* array, unsigned int* bounds)
        : array(array)
        {
            std::copy(bounds, bounds + dimension, this->bounds);
        }
    
        /*programmer usable constructor for typing of the dimensions, instead of having to declare an array*/
        MDAI(Type* array, ...)
        : array(array)
        {
            va_list arguments;
            va_start(arguments, array);
            for (int index = 0; index < dimension; ++index)
                bounds[index] = va_arg(arguments, unsigned int);
            va_end(arguments);
        }
    
        /*drills down one level into the multi dimensional array*/
        MDAI<Type, dimension - 1> operator[](unsigned index) {
            if (dimension < 1) {
                std::cerr << "MDAI is not an array.\n";
                throw 1;
            }
            if (index < 0 || index >= bounds[0]) {
                std::cerr << "Index out of bounds.\n";
                throw 1;
            }
    
            //figure out how many addresses to jump
            for (unsigned int index2 = 1; index2 < dimension; ++index2)
                index *= bounds[index2];
    
            return MDAI<Type, dimension - 1>(array + index, bounds + 1);
        }
    
        /*'dereferences' the array to get a reference to the stored value*/
        Type& operator*() {
            if (dimension > 0) {
                std::cerr << "MDAI is an array.\n";
                throw 1;
            }
    
            return *array;
        }
    
        /*allows the compiler to automagically 'convert' the MDAI into whatever the user thinks it is*/
        operator Type&() {
            return **this;
        }
    
        /*makes assignment work automagically too!*/
        MDAI<Type, dimension>& MDAI<Type, dimension>::operator=(Type value) {
            **this = value;
            return *this;
        }
    };
    

    Testing a three-dimensional array of bounds 2-4-3:

    void main(unsigned int argC, char** argV) {
        using namespace std;
    
        int array[2][4][3] = {
            {
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9},
                {10, 11, 12}
            },
            {
                {13, 14, 15},
                {16, 17, 18},
                {19, 20, 21},
                {22, 23, 24}
            }
        };
    
        //cast array to pointer, then interpret
        MDAI<int, 3> mdai((int*)array, 2, 4, 3);
        //testing correct memory access
        cout << 15 << ' ' << mdai[1][0][2] << endl;
    
        //testing modifcations using mdai are in array
        mdai[0][2][1] = -1;
        cout << array[0][2][1] << ' ' << mdai[0][2][1] << endl;
    
        //testing modifications in array show up in mdai
        array[1][3][2] = -23;
        cout << -23 << ' ' << mdai[1][3][2] << endl;
    
        //testing automatic type casting
        cout << -15.0 << ' ' << mdai[0][0][1] * -7.5 << endl;
    }
    

    It's as seamless as it would have been had I left it as an array reference.

    For compile-time safety I wanted to have redeclare operator*() as, specifically;
    Type& MDAI<Type, 0>::operator*()
    so you could only call it on a <X, 0>
    But I couldn't figure it out.
    Similarly get operator[]() to only appear for dimensions greater than 0
    Oh well, runtime checking will have to be good enough