0

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]'

Hashbrown
  • 12,091
  • 8
  • 72
  • 95
  • 1
    It *should* care if the language standard prohibits it. – Oliver Charlesworth Sep 13 '14 at 08:41
  • 1
    As for a solution, you could consider a small helper class that abstracts the array indexing maths. – Oliver Charlesworth Sep 13 '14 at 08:43
  • @OliCharlesworth, I'm trying that now. It just makes it less sexy I guess. Can you elaborate on what, precisely, is prohibited though? – Hashbrown Sep 13 '14 at 08:44
  • "Prohibit" is perhaps too strong. The C++ language does not define VLAs, so a compiler is under no obligation to implement them. – Oliver Charlesworth Sep 13 '14 at 08:46
  • but that's my point, I don't see any VLA's here, these are all references and pointer casts. No new memory is allocated; I have some data and I have it interpreted (and thus accessed) differently using syntactic sugar – Hashbrown Sep 13 '14 at 08:48
  • But the compiler would still need to implement (amongst other things) code to access the memory as a VLA (which is different to the code required for a fixed array). – Oliver Charlesworth Sep 13 '14 at 08:50
  • Hey @OliCharlesworth, thanks for confirming my fears early that it just wouldn't be do-able :) It made me go and fix it. I'd like to invite you to try out the `class` I wrote (see; my answer - second code snippet) – Hashbrown Sep 13 '14 at 13:38
  • @Hashbrown C++ doesn't support variably modified type. People sometimes say *VLA* when they mean *variably modified type*, e.g. `int (&array)[x][y]`. This is not a VLA but it requires the VLA's type system. – M.M Nov 03 '14 at 07:16
  • yeah that's what Oliver was getting at. I recognised that it wasn't a VLA from the very beginning, which was why I believed it should be accepted by the compiler in the first place. But Oliver pointed out that it ("VMT"s) would still require, what you call the VLA type system, which the Microsoft compiler implementers neglected to include even though it would be useful in this scenario – Hashbrown Nov 03 '14 at 08:12

1 Answers1

0

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

Hashbrown
  • 12,091
  • 8
  • 72
  • 95
  • you can have compile time checking by replacing those `if` statements with [`static_assert`](http://stackoverflow.com/questions/3385515/static-assert-in-c/25874285#25874285) calls – Hashbrown Sep 16 '14 at 16:55