0

How can I overload multi-dimensional brackets?

Let's say that I have a class which enables me to access points in an n-vector space. For example:

class NSpaceVector {
    private:
        int vectorSpace[8][8][8][8][8][8][8];
    public:        
        const NSpaceVector operator[][][][][][][](int i, int j, int k, int l, int m, int n, int p)const {return vectorSpace[i][j][k][l][m][n][p]; }
        NSpaceVector operator[][][][][][][](int i, int j, int k, int l, int m, int n, int p) {return vectorSpace[i][j][k][l][m][n][p]; }
}
unsigned long & operator [](int i) {return registers[i];}

What I would like to do is overload the bracket operator so that I can index into this location like so:

int main() {
    NSpaceVector nsv; // Assume initializes all to 0
    nsv[2][4][7][4][0][7][6] = 2;
    cout << nsv[2][4][7][4][0][7][6] << endl; //-> 2
    cout << nsv[1][4][7][4][0][7][6] << endl; //-> 0
    return 0;
}

I can't get this to compile. Any ideas? Thx, Keith :^)

kmiklas
  • 13,085
  • 22
  • 67
  • 103
  • 4
    You can't overload a multi-dimensional bracket since there is no such operator in C++. Instead, you have to overload a single bracket to return a sort of proxy object, for each overload of a single-dimensional bracket would mean a second level of indexing, which would return another proxy... and so forth. – SergeyA Mar 23 '16 at 17:17
  • Give this a read for a better way to perform this with `operator()`: https://isocpp.org/wiki/faq/operator-overloading#matrix-array-of-array – user4581301 Mar 23 '16 at 17:18
  • 2
    Or, alternatively, find a different syntax. Like `nsv(1,2,3,4,5,6,7)` or `nsv[{1,2,3,4,5,6,7}]`. – T.C. Mar 23 '16 at 17:18
  • Not strictly a duplicate as this is a special case with an easy answer: `auto& operator[](int i) {return vectorSpace[i]; }` - that should work as expected. – Galik Mar 23 '16 at 17:30

2 Answers2

2

The standard answer is to have each [] (from left-to-right) return a proxy for the correct nested subset, the last of which actually returns a reference to the data. In your scheme, it would be easiest by far to template the nested types to automate production.

As the mysterious user4581301 mentions, it's much simpler to provide

nsv(2,4,7,4,0,7,6)

Sample template production of your desired class is probably simpler than using a temporary proxy object:

template <size_t Width, size_t Depth, typename T=int>
struct SubSpace;

template <size_t Width, typename T>
struct SubSpace<Width,0,T> {
    std::array<T, Width> array;

    T& operator[] (size_t i) { return array[i]; }
    T const& operator[] (size_t i) const { return array[i]; }
};

template <size_t Width, size_t Depth, typename T>
struct SubSpace {
    using Nested = SubSpace<Width, Depth-1, T>;
    std::array<Nested, Width> array;

    Nested& operator[] (size_t i) { return array[i]; }
    Nested const& operator[] (size_t i) const { return array[i]; }
};


using NSpaceVector = SubSpace<8,6>;

int main()
{
    NSpaceVector nsv;
    nsv[2][4][7][4][0][7][6] = 2;
    cout << nsv[2][4][7][4][0][7][6] << endl; //-> 2
}

Note this doesn't currently default-initialize the array members, but we can use aggregate initialization anyway:

    NSpaceVector nsv {}; // value-initialize every element
    nsv[2][4][7][4][0][7][6] = 2;
    cout << nsv[2][4][7][4][0][7][6] << endl; //-> 2
    cout << nsv[2][4][7][4][0][7][5] << endl; //-> 0

Also note that SubSpace<8,6> gives the 8x7 result you wanted, since Depth terminates at 0. This could be cleaned up with a toplevel wrapper, but I'm reluctant to terminate on Depth==1 instead and have everything go wrong when someone instantiates SubSpace<0,0> by accident.


Simpler still, depending on whether you want any behaviour in your class, is this:

struct OtherSpaceVector {
    int s[8][8][8][8][8][8][8];

    auto operator[] (size_t i) -> decltype(s[i]) { return s[i]; }
};

int main()
{
    OtherSpaceVector osv{};
    osv[2][4][7][4][0][7][6] = 2;
    std::cout << osv[2][4][7][4][0][7][6] << '\n'; 
    std::cout << osv[2][4][7][4][0][7][5] << '\n';
}

It's harder to alter the dimensions and feels more brittle than the template version, but it still works for the use case you provided.

Useless
  • 64,155
  • 6
  • 88
  • 132
1

Any ideas?

There's only the option to overload the operator[]() for every level, and let the return types have another operator[]() overload.


As mentioned in comments, such is usually solved with overloading the call operator():

int operator()(size_t dim1, size_t dim2, ... size_t dimn);  
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • This answer displeases me. Any chance that we could add this to the C++17 spec? – kmiklas Mar 23 '16 at 17:34
  • @kmiklas Sorry to displease you by telling the facts. No there won't be changes in operator overloading syntax with c++17 spec AFAIK, and I doubt you will convince the c++ standard committee to change it for that concern. – πάντα ῥεῖ Mar 23 '16 at 17:37
  • @kmiklas zilch. Unless you have a time machine, then infinitesimal. – T.C. Mar 23 '16 at 18:45
  • @πάνταῥεῖ: There may very well be changes in operator overloading with C++17 (namely smart references via `operator.`) Just no changes to bracket subscripting operators. – Ben Voigt Dec 09 '16 at 01:27