7

I have a class with a multidimensional array:

  • it is possible to create a one, two, ..., n dimensional array with this class

  • if the array has n dimensions, i want to use n operator[] to get an object:

example:

A a({2,2,2,2}]; 
a[0][1][1][0] = 5;

but array is not a vector of pointer which lead to other vectors etc...

so i want the operator[] to return a class object until the last dimension, then return a integer

This is a strongly simplified code, but it shows my problem:

The error i receive: "[Error] cannot convert 'A::B' to 'int' in initialization"

#include <cstddef>     // nullptr_t, ptrdiff_t, size_t
#include <iostream>    // cin, cout...

class A {
    private:
        static int* a;
    public:
        static int dimensions;
        A(int i=0) { 
            dimensions = i;
            a = new int[5];
            for(int j=0; j<5; j++) a[j]=j; 
        };

        class B{
            public:
                B operator[](std::ptrdiff_t);
        };
        class C: public B{
            public:
                int& operator[](std::ptrdiff_t);
        };

        B operator[](std::ptrdiff_t);
};

//int A::count = 0;

A::B A::operator[] (std::ptrdiff_t i) {
    B res;
  if (dimensions <= 1){
    res = C();
}
  else{
    res = B();
  }
  dimensions--;
  return res;
}

A::B A::B::operator[] (std::ptrdiff_t i){
    B res;
    if (dimensions <=1){
        res = B();
    }
    else{
        res = C();
    }
    dimensions--;
    return res;
}

int& A::C::operator[](std::ptrdiff_t i){
    return *(a+i);
}


int main(){
    A* obj = new A(5);
    int res = obj[1][1][1][1][1];
    std::cout<< res << std::endl;
}
2
  • 2
    Are you aware that A* obj = new A(5); int res = obj[1][1][1][1][1]; works on a pointer to A ? obj[1] does not invoke your operator but accesses the second element in an array of A !! Commented Oct 31, 2014 at 0:49
  • @Oncaphillis, totally correct, didn't even realized that in my answer Commented Oct 31, 2014 at 1:07

3 Answers 3

4

The operator[] is evaluated from left to right in obj[1][1]...[1], so obj[1] returns a B object. Suppose now you just have int res = obj[1], then you'll assign to a B object (or C object in the case of multiple invocations of []) an int, but there is no conversion from B or C to int. You probably need to write a conversion operator, like

operator int()
{
   // convert to int here
}

for A, B and C, as overloaded operators are not inherited.

I got rid of your compiling error just by writing such operators for A and B (of course I have linking errors since there are un-defined functions).

Also, note that if you want to write something like obj[1][1]...[1] = 10, you need to overload operator=, as again there is no implicit conversion from int to A or your proxy objects.

Hope this makes sense.

PS: see also @Oncaphillis' comment!

Sign up to request clarification or add additional context in comments.

2 Comments

hey, i added an operator int() to A,B and C and a operator= to A and now i receive a new error: (.text$_ZN1AC1Ei[_ZN1AC1Ei]+0x14): undefined reference to A::dimensions' ||| (.text$_ZN1AC1Ei[_ZN1AC1Ei]+0x25): undefined reference to A::a' | | | C:\Users\Marius K\Documents\collect2.exe [Error] ld returned 1 exit status
this is a linker error, probably you have undefined methods or static variables. in the latter case, you need to instantiate the static variable ouside the class, like int A::mystatic; in yout case the pointer has to be set to a default value, like nullptr
3

vsoftco is totally right, you need to implement an overload operator if you want to actually access your elements. This is necessary if you want it to be dynamic, which is how you describe it. I actually thought this was an interesting problem, so I implemented what you described as a template. I think it works, but a few things might be slightly off. Here's the code:

template<typename T>
class nDimArray {
    using thisT = nDimArray<T>;

    T                    m_value;
    std::vector<thisT*>  m_children;
public:
    nDimArray(std::vector<T> sizes) {
        assert(sizes.size() != 0);
        int thisSize = sizes[sizes.size() - 1];
        sizes.pop_back();

        m_children.resize(thisSize);
        if(sizes.size() == 0) {
            //initialize elements
            for(auto &c : m_children) {
                c = new nDimArray(T(0));
            }
        } else {
            //initialize children
            for(auto &c : m_children) {
                c = new nDimArray(sizes);
            }
        }
    }
    ~nDimArray() {
        for(auto &c : m_children) {
            delete c;
        }
    }
    nDimArray<T> &operator[](const unsigned int index) {
        assert(!isElement());
        assert(index < m_children.size());
        return *m_children[index];
    }

    //icky dynamic cast operators
    operator T() {
        assert(isElement());
        return m_value;
    }
    T &operator=(T value) {
        assert(isElement());
        m_value = value;
        return m_value;
    }

private:
    nDimArray(T value) {
        m_value = value;
    }

    bool isElement() const {
        return m_children.size() == 0;
    }

    //no implementation yet
    nDimArray(const nDimArray&);
    nDimArray&operator=(const nDimArray&);
};

The basic idea is that this class can either act as an array of arrays, or an element. That means that in fact an array of arrays COULD be an array of elements! When you want to get a value, it tries to cast it to an element, and if that doesn't work, it just throws an assertion error.

Hopefully it makes sense, and of course if you have any questions ask away! In fact, I hope you do ask because the scope of the problem you describe is greater than you probably think it is.

Comments

2

It could be fun to use a Russian-doll style template class for this.

// general template where 'd' indicates the number of dimensions of the container
// and 'n' indicates the length of each dimension
// with a bit more template magic, we could probably support each
// dimension being able to have it's own size
template<size_t d, size_t n>
class foo
{
private:
    foo<d-1, n> data[n];
public:
    foo<d-1, n>& operator[](std::ptrdiff_t x)
    {
        return data[x];
    }
};

// a specialization for one dimension. n can still specify the length
template<size_t n>
class foo<1, n>
{
private:
    int data[n];
public:
    int& operator[](std::ptrdiff_t x)
    {
        return data[x];
    }
};

int main(int argc, char** argv)
{
    foo<3, 10> myFoo;
    for(int i=0; i<10; ++i)
        for(int j=0; j<10; ++j)
            for(int k=0; k<10; ++k)
                myFoo[i][j][k] = i*10000 + j*100 + k;

    return myFoo[9][9][9]; // would be 090909 in this case
}

Each dimension keeps an array of previous-dimension elements. Dimension 1 uses the base specialization that tracks a 1D int array. Dimension 2 would then keep an array of one-dimentional arrays, D3 would have an array of two-dimensional arrays, etc. Then access looks the same as native multi-dimensional arrays. I'm using arrays inside the class in my example. This makes all the memory contiguous for the n-dimensional arrays, and doesn't require dynamic allocations inside the class. However, you could provide the same functionality with dynamic allocation as well.

7 Comments

I think a template is the only way to go since specifying the dimensions dynamically in the constructor doesn't make any sense at all. One still has to provide and therefore know the number of dimesnions at compile time when writing obj[1][1][1]... etc.
@Oncaphillis, are you sure about this? Cannot it work with some clever proxy objects? The OP code makes sense, and seems that it can be fixed. In particular, decrementing the dimension whenever you invoke operator[] seems a neat idea, and when dimension==1 you just return a reference to the element. I'm too lazy to fix the code, will try probably tomorrow as this is quite a nice problem.
It's not a question if it can work or not. I think one may actually get it to work. But does it make sense ? Lets say you have an multi-array for which you can define the number of dimensions dynamically during runtime like array(5). Later you are able to access elements via array[a][b][c][d][e]. At this point in the code you must have been aware that you are dealing with five dimensions during compile time. What did you win ? It would be better to provide the dimensions as a template arg.
@Oncaphillis I disagree, sometime you may need to read out let's say a tensor from a file, so you cannot know at compile time the number of dimensions. I would very much like actually to be able to specify the number of dimension at runtime, unlike boost::multi_array, at least in my kind of work I find it quite useful. And you can check if you accessed more than N dimensions also at runtime (of course there is a speed penalty for this).
@vsoftco Of course it is usefull, but then it doesn't make much sense to access the concrete cell in your multi-array with proxy objects and cascades of [] operators, since you need to know how many of these operators you actually have to cascade to get to your concrete cell. It would be much better to hide the concrete number of dimensions to the functions which have to access a concrete cell.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.