3

I am looking for overloading [] operator for a two dimensional array pointer to access cell element.

Two dimensional array is passed to my function as int *arr.

We can access cell element by *(arr+i*N+j) where N is the column count and i is row index and j is column index.

But can we write this like arr[i,j] or arr(i,j) for better readability using some macro or operator overloading?

Any suggestion?

coincoin
  • 4,595
  • 3
  • 23
  • 47
vicky
  • 885
  • 1
  • 7
  • 23

4 Answers4

5

You cannot do that directly.
I would suggest to write a little class/struct that would wrap your 2D array conveniently. Here I use std::vector instead of int *arr and you do not have to care about memory management. The following code presents 3 possibles methods :

Method 1 (recommended) : accessing via mat(i,j)

The operator()(size_t i, size_t j) is called function call operator.

template<typename T>
struct Matrix
{
    Matrix(const std::vector<T>& d, size_t s) : Matrix(d, s, s) {}
    Matrix(const std::vector<T>& d, size_t c, size_t r) : data_(d), cols_(c), rows_(r) {}
    std::vector<T> data_;
    size_t    cols_;
    size_t    rows_;
    const T& operator()(size_t i, size_t j) const { return data_[i * cols_ + j]; }
          T& operator()(size_t i, size_t j)       { return data_[i * cols_ + j]; }
    // and you can add other convenient methods
};

Use it like this :

   Matrix<int> mat({1, 2, 3, 4, 5, 6, 7, 8, 9}, 3, 3); // a 3x3 matrix
   std::cout << mat(1,2) << std::endl;

Live code

If you know the size at compile time then you can use std::array and change your struct accordingly :

template<typename T, size_t Cols, size_t Rows> struct Matrix 
{ 
  std::array<T, Cols * Rows> data_; 
  // etc...

Method 2 : accessing via mat[{i,j}]

Using the array subscript operator only takes one argument so you can change/add the following operators to your class :

const T& operator[](const std::array<size_t,2>& a) const { return data_[a[0] * cols_ + a[1]]; }
      T& operator[](const std::array<size_t,2>& a)       { return data_[a[0] * cols_ + a[1]]; }

which can be called like this :

std::cout << mat[{1,2}] << std::endl;

Note that this method is useful when you work on several dimensions (you do not have to write several operator()(size_t i, size_t j, size_t k, etc...)

Method 3 (not recommended) : accessing via mat[Idx(i),Idx(j)]

You can take two arguments using the array subscript operator but you have to overload the comma operator which is not possible between two built in types... So accessing directly via mat[i,j] is not possible (Thanks leemes' comment for pointing that out).

However you can create your own type and overload it. Here an example (put it before your Matrix class definition) :

struct Idx 
{
    Idx(size_t ii) : i(ii) {}
    size_t i;
    operator size_t() const { return i; } // implicit user-defined conversion
};

std::array<size_t, 2> operator , (Idx i1, Idx i2)
{
    return { i1, i2 };
}

// etc...
// and we do not have to add Matrix operators since we reused the one from Method 2

Use it like this :

std::cout << mat[Idx(1),Idx(2)] << std::endl;

which is not that elegant...

Complete and final live code

coincoin
  • 4,595
  • 3
  • 23
  • 47
  • What kind of operator is this? (And I guess you wanted the second to be non-const) – leemes Nov 05 '15 at 10:15
  • This is the operator : `mat(i,j)`. – coincoin Nov 05 '15 at 10:18
  • Yes sorry it is what happened when you write code without testing it ;p – coincoin Nov 05 '15 at 10:23
  • 1
    :) Regarding the array subscript operator with two arguments: That would indeed only be possible when overloading the comma operator, but this again is not possible for both arguments being built in types. The usual "workaround" is to use syntax `[i][j]` instead (like in my answer). It also resembles the classical syntax of raw 2D arrays in a better way. But this is of course a subjective preference. – leemes Nov 05 '15 at 10:25
  • Oh right thanks for the point that comma operator overload is also not possible. I ll take a more precise look at your answer ! – coincoin Nov 05 '15 at 10:33
  • @coincoin, can't we overload [] operator with two arguments ? – vicky Nov 05 '15 at 11:51
  • @vicky unfortunately not directly, you need to overload the comma opeartor also. And as told by leemes, it it not possible with built in types (here size_t). – coincoin Nov 05 '15 at 12:13
  • @vicky I have added an example which uses the operator[] with two arguments but I do not recommend it at all ! – coincoin Nov 05 '15 at 13:23
  • @coincoin hmmm not recommended but a nice try. I didn't know if we can overload comma operator. but yes its a useful technique. – vicky Nov 07 '15 at 03:40
  • @coincoin ... can we merge your technique with leemes technique.... include implicit conversion to convert int* into Matrix* automatically which is not visible to outside user. – vicky Nov 07 '15 at 03:42
  • @vicky what do you need ? What specific technique would interest you ? You can return an int* with this method `T* data() {return data_.data(); }` by using [vector::data](http://en.cppreference.com/w/cpp/container/vector/data). If you can explain you goal it might be easier. – coincoin Nov 07 '15 at 09:14
2
  • You can write an index function to hide the formula.

    If your N is defined globally, write

    int index(int i, int j) {
        return i * N + j;
    }
    

    and use it with

    arr[index(i, j)]
    
  • Alternatively, write a wrapper class around your pointer. This class can be written with absolutely no runtime overhead. It can have an operator to allow the syntax

    arr[i][j]
    

    where arr is an instance of the wrapper class. Such a wrapper can be defined like this:

    class Array2DWrapper {
        int *ptr;
    public:
        Array2DWrapper(int *ptr) : ptr(ptr) {}
    
        int * operator[](int i) {
            return ptr + i*N;
        }
    };
    
    // USAGE:
    void exampleFunction(int *arrPtr) {
        Array2DWrapper arr { arrPtr };
        ...
        arr[i][j];
        ...
    }
    

    As you can see, the idea is to overload operator[] for the outer dimension, which returns a pointer to the inner dimension. When the user of this class writes arr[i], it calls the custom operator overload, which returns an int*, then the next [j] accesses the element using the builtin operator[] for pointers.

    Note that the above class can be used as a function parameter, but the caller can call it with a raw pointer to some 2D array. This will call the constructor of this class automatically ("implicit conversion").

    // USAGE with implicit conversion on the call site:
    void exampleFunction(Array2DWrapper arr) {
        ...
        arr[i][j];
        ...
    }
    
    // Note how the caller doesn't need to wrap it explicitly:
    int * myArrPtr = ...;
    exampleFunction(myArrPtr);
    

    If N is not defined globally, you should add it as a member to the class, as well as to the constructor.

    class Array2DWrapperDynamicN {
        int *ptr;
        int N;
    public:
        Array2DWrapper(int *ptr, int N) : ptr(ptr), N(N) {}
    
        int * operator[](int i) {
            return ptr + i*N;
        }
    };
    

    But now, the implicit conversion doesn't work anymore.

    // USAGE:
    void exampleFunction(int *arrPtr, int N) {
        Array2DWrapperDynamicN arr { arrPtr, N };
        ...
        arr[i][j];
        ...
    }
    
leemes
  • 44,967
  • 21
  • 135
  • 183
  • your second option is good. But we have to add N also somewhere in the class ? – vicky Nov 05 '15 at 10:02
  • @vicky I extended the answer a bit. I don't know if in your case `N` is defined globally or if it needs to be put inside the wrapper. It depends on the rest of your code... Where does `N` come from? Is it a function parameter? Or outside the function? – leemes Nov 05 '15 at 10:07
  • Your implicit conversion is really good. But it would be great if N can be involved in implicit conversion because N is not defined globally. It would be great to see plain pointer converted into smart pointer with 2d indexing feature. – vicky Nov 05 '15 at 10:09
  • @vicky But for that, it needs to know the size of the inner dimension in *some* way... Well, in C++11 you can use initializer lists to use implicit conversion for multiple argument constructors. It is a bit complicated. Basically you call the function like this: `exampleFunction({arr,N})`, see how `{arr,N}` is a single argument to `exampleFunction` and is converted implicitly to a `Array2DWrapperDynamicN` if the function has the signature `exampleFunction(Array2DWrapperDynamicN arr)` – leemes Nov 05 '15 at 10:11
  • normally row and column count are passed as an argument list after 2D array like exampleFunction(arr,m,n) .... can we do something in this case ? – vicky Nov 05 '15 at 10:16
  • @vicky Okay. For minimal changes in your code, I recommend to use the above wrapper (with dynamic `N`) only *inside the function* which accesses the array. However, if you're willing to improve your complete code base (also on the caller side) then use a class for your arrays. Have a look at the answer by coincoin for example. He uses a vector which also does the correct memory management *and* provides convenient access functions. He uses a different syntax `(i,j)`, but if you want to use my syntax `[i][j]` this can be done with vector as the backing store too. – leemes Nov 05 '15 at 10:21
  • I just remembered something I did : since `operator[]` only takes one argument you could also do something like this : `int& operator[](const T (&arr)[N])` – coincoin Nov 05 '15 at 10:48
1

No.

Operator overloading requires at least one of the argument types to be a class/struct/union. And macros can't do that.

The closest you can get is to pass the array by reference, if possible. E.g.:

template<std::size_t width, std::size_t height>
void doThings(int(& array)[width][height])
{
    // access array[i][j];
}

Or if that's not possible, a helper non-operator function, to hide the ugly part:

int& access(int* array, std::size_t i, std::size_t j)
{
    return *(arr + i*N + j);
}

Or maybe you need to tackle the underlying problem here. Why is a 2D array passed by int* in the first place?

The important thing to remember here is to not over-complicate things.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • I think writing arr[i,j] is more readable than writing *(arr+i*N+j) which is more error prone and unreadable. – vicky Nov 05 '15 at 09:53
0

One cannot change the behavior of operators for built types. If you want to overload an operator, at least one of the operands must a user-defined type.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182