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 typedef
ed struct
containing float
s
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]'
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