4

I originally asked using nested std::array to create an multidimensional array without knowing dimensions or extents until runtime but this had The XY Problem of trying to accomplish it with std::array.

The questions One-line initialiser for Boost.MultiArray and How do I make a multidimensional array of undetermined size a member of a class in c++? and their answers give some helpful information how to use Boost::MultiArray to avoid needing to know the extents of the dimensions at runtime, but fail to demonstrate how to have a class member that can store an array (created at runtime) whose dimensions and extents are not known until runtime.

Community
  • 1
  • 1
Key Lay
  • 327
  • 3
  • 11
  • 1
    Do you mean the number of dimensions are unknown at compile time, or that there are N dimensions of unknown size? – Zac Howland Nov 26 '13 at 19:34
  • As far as I can tell (from a quick look at the boost documentation) you can simply specify everything in the constructor as I would have expected – Sebastian Hoffmann Nov 26 '13 at 19:36
  • 1
    If you look at my solution to your 1st try: http://stackoverflow.com/a/20207616/2963099 and then you keep a vector of dimensions and variable args for the constuctor, you should have an idea of how to do this – Glenn Teitelbaum Nov 26 '13 at 19:40
  • @ZacHowland I mean that both the number of dimensions, as well as their extents (sizes) are unkown at runtime. – Key Lay Nov 26 '13 at 19:41
  • Glenn's answer will solve your problem (and is frankly the only want to solve it), but it sounds like you are still suffering from an XY problem. Why do you think you need a dynamic number of dimensions? – Zac Howland Nov 26 '13 at 19:46
  • @GlennTeitelbaum your solution only works for 2 dimensions. I'm unsure what you mean by "a vector of dimensions" and I'm guessing that "variable args for the constructor" is referring to a variadic constructor. It sounds like you are suggesting a secondary object to track number of dimensions, but I don't think this will get around having to declare a multi-dimensional container or pointer to one, which I perceive to be the root of the problem as doing so requires declaring the number of dimensions at least. – Key Lay Nov 26 '13 at 19:47
  • If you keep [10,15,37,48], then you can calculate the offset into a 1 dimensional array. In fact - you can have your constructor and getters, etc. just use vectors and not worry about var args. Another way to look at this is what do you think the compiler does with x[3][5][4][17][13] considering that memory is linear – Glenn Teitelbaum Nov 26 '13 at 19:57
  • @ZacHowland I am dealing with hypothetical data that will have a variable number of dimensions. By hypothetical I mean that I'm making it up. In an effort to brush up on c++ (I'm trying to get dev job after having non-dev job for more than a year) I am doing practice exercises. Right now I'm working on a multi-dimensional version of http://redd.it/1m71k9 . – Key Lay Nov 26 '13 at 20:05
  • That problem does not involve a variable number of dimensions - it uses a 2-dimensional grid. Expanding it to 3 dimensions makes sense, but having it to a variable number of dimensions would result in nonsense. – Zac Howland Nov 26 '13 at 20:10

4 Answers4

4

Just avoid multidimensional arrays:

template<typename T>
class Matrix
{
    public:
    Matrix(unsigned m, unsigned n)
    :   n(n), data(m * n)
    {}

    T& operator ()(unsigned i, unsigned j)  {
        return data[ i * n + j ];
    }

    private:
    unsigned n;
    std::vector<T> data;
};

int main()
{
    Matrix<int> m(3, 5);
    m(0, 0) = 0;
    // ...
    return 0;
}

A 3D access (in a proper 3D matrix) would be:

T& operator ()(unsigned i, unsigned j, unsigned k)  { 
    // Please optimize this (See @Alexandre C) 
    return data[ i*m*n + j*n + k ]; 
}

Getting arbitrary dimensions and extent would follow the scheme and add overloads (and dimensional/extent information) and/or take advantage of variadic templates.

Having a lot of dimensions you may avoid above (even in C++11) and replace the arguments by a std::vector. Eg: T& operator(std::vector indices). Each dimension (besides the last) would have an extend stored in a vector n (as the first dimension in the 2D example above).

  • The question isn't tagged C++11, so I'm guessing the author doesn't have access to variadic templates, and adding an overload for every dim is quite a bad idea... what if the data would be 50D (which hypothetically could happen)? – Paweł Stawarz Nov 26 '13 at 20:16
  • @PawełStawarz My bad. I'll add the c++11 tag, as that's what I'm working with and would prefer to use fancy c++11 syntax/methods if possible. – Key Lay Nov 26 '13 at 20:19
  • Rather do `(i * m + j) * n + k`. This way it is easier to do it recursively if you add dimensions. Moreover, it saves a multiplication (which the compiler may have noticed, btw). – Alexandre C. Nov 26 '13 at 21:02
1

Yes. with a single pointer member.

A n multidimensional array is actually a pointer. so you can alocate a dynamic n array and with casting, and put this array in the member pointer.

In your class should be something like this

int * holder;

void setHolder(int* anyArray){
  holder = anyArray;
}

use:

int *** multy = new int[2][1][56];
yourClass.setHolder((int*)multy);
MeNa
  • 1,481
  • 9
  • 22
  • 1
    No, just no. This is all I'm going to say. – Shoe Nov 26 '13 at 20:00
  • 1
    Ok, please explain me. we all only learning... i'll be glad to learn something new... – MeNa Nov 26 '13 at 20:02
  • this is the same as `void * multy = (void *)new vector[25]; yourClass.setHolder((int*)multy);` and probably isn't very typesafe – Glenn Teitelbaum Nov 26 '13 at 20:13
  • The typecasting would have to be dynamic with respect to the number of dimensions and their extents. I'm not sure if this is possible, but I really don't think it's desirable anyway; My instincts tell me a solution like this would involve lots of ugly c-style pointer and memory management. – Key Lay Nov 26 '13 at 20:16
  • First [never use](http://stackoverflow.com/questions/1609163/what-is-the-difference-between-static-cast-and-c-style-casting) C-style casts and second try to avoid both the use of C-style arrays and [naked pointers](http://stackoverflow.com/questions/6500313/why-should-new-be-used-as-little-as-possible). – Shoe Nov 26 '13 at 20:39
  • @GlennTeitelbaum Thank you! it's nice and powerful! and Kylé, the typecasting is no need to be dynamic... but, you probably right about my non-proffesional style... – MeNa Nov 26 '13 at 20:45
1

You can solve the problem in at least two ways, depending on your preferences. First of all - you don't need the Boost library, and you can do it yourself.

class array{
   unsigned int dimNumber;
   vector<unsigned int> dimSizes;

   float *array;

   array(const unsigned int dimNumber, ...){
     va_list arguments;
     va_start(arguments,dimNumber);
     this->dimNumber = dimNumber;
     unsigned int totalSize = 1;
     for(unsigned int i=0;i<dimNumber;i++)
     {
        dimSizes.push_back(va_arg(arguments,double));
        totalSize *= dimSizes[dimSizes.size()-1];
     }
     va_end(arguments);
     array = new float[totalSize]; 
   };

   float getElement(unsigned int dimNumber, ...){
     va_list arguments;
     va_start(arguments,dimNumber);
     unsgned int elementPos = 0, dimAdd = 1;
     for(unsigned int i=0;i<dimNumber;i++)
     {
        unsigned int val = va_arg(arguments,double);
        elementPos += dimAdd * val;
        dimAdd *= dimsizes[i];
     }
     return array[elementPos]
   };
};

Setting an element value would be the same, you will just have to specify the new value. Of course you can use any type you want, not just float... and of course remember to delete[] the array in the destructor.

I haven't tested the code (just wrote it straight down here from memory), so there can be some problems with calculating the position, but I'm sure you'll fix them if you encounter them. This code should give you the general idea.

The second way would be to create a dimension class, which would store a vector<dimension*> which would store sub-dimensions. But that's a bit complicated and too long to write down here.

Paweł Stawarz
  • 3,704
  • 2
  • 14
  • 26
  • -1 for DIY and NIHS. Generally, please test code before posting it as an authoritive anwer. Specifically, if you think you can do things better than Boost, the burden of proof is on you. – TemplateRex Nov 27 '13 at 11:43
0

Instead of a multidimensional array you could use a 1D-array with an equal amount of indices. I could not test this code, but I hope it will work or give you an idea of how to solve your problem. You should remember that arrays, which do not have a constant length from the time of being compiled, should be allocated via malloc() or your code might not run on other computers. (Maybe you should create a class array for the code below)

#include <malloc.h>

int* IndexOffset; //Array which contains how many indices need to be skipped per  dimension
int  DimAmount;   //Amount of dimensions
int  SizeOfArray = 1; //Amount of indices of the array

void AllocateArray(int* output,         //pointer to the array which will be allocated
                   int* dimLengths,     //Amount of indices for each dimension: {1D, 2D, 3D,..., nD}
                   int dimCount){       //Length of the array above

DimAmount = dimCount;
int* IndexOffset = (int*) malloc(sizeof(int) * dimCount);
int temp = 1;

for(int i = 0; i < dimCount; i++){

temp = temp * dimLengths[i];
IndexOffset[i] = temp; 

}


for(int i = 0; i < dimCount; i++){

SizeOfArray = SizeOfArray * dimLengths[i];

}

output = (int*)malloc(sizeof(int) * SizeOfArray);

}

To get an index use this:

int getArrayIndex(int* coordinates //Coordinates of the wished index as an array (like dimLengths)
            ){

int index;
int temp = coordinates[0];

for(int i = 1; i < DimAmount; i++){

     temp = temp + IndexOffset[i-1] * coordinates[i];

}
index = temp;
return index;
}

Remember to free() your array as soon as you do not need it anymore:

for(int i = 0; i < SizeOfArray; i++){

    free(output[i]);

}
free(output);
StirriX
  • 13
  • 4