3

I'm writing a matrix 3x3 class in c++.

glm::mat3 provides access to matrix data through the [][] operator syntax.
e.g. myMatrix[0][0] = 1.0f; would set the first row, first column entry to 1.0f.

I'd like to provide similar access. How can I overload the [][] operators?

I've tried the following, but I get errors:

operator name must be declared as a function

const real operator[][](int row, int col) const
{
    // should really throw an exception for out of bounds indices
    return ((row >= 0 && row <= 2) && (col >= 0 && col <= 2)) ? _data[row][col] : 0.0f;
}

What's the correct way to overload this operator?

whoan
  • 8,143
  • 4
  • 39
  • 48
fishfood
  • 4,092
  • 4
  • 28
  • 35
  • If you can deal with matrix(x,y) syntax, go with that. Our codebase couldn't since we wanted to be able to quickly swap legacy code using 2-dimensional arrays to use our matrix classes instead. A way that sacrifices some safety but quickly achieves what you want is to do: const real* operator[](int row) const { return _data[row]; }; This sacrifices the ability to do bounds-checking on the result when fetching a column, however, so a proxy object could be used instead for a safer result. – stinky472 Sep 30 '12 at 06:30
  • I'd really like to be able to interchange my code with the glm code, just by changing the namespace used, so I can't go with the operator(). – fishfood Oct 01 '12 at 09:17

5 Answers5

7

There is no operator [][], so you need to overload the [] operator twice: once on the matrix, returning a surrogate object for the row, and once for the returned surrogate row:

// Matrix's operator[]
const row_proxy operator[](int row) const
{
    return row_proxy(this, row);
}
// Proxy's operator[]
const real operator[](int col) const
{
    // Proxy stores a pointer to matrix and the row passed into the first [] operator
    return ((this->row >= 0 && this->row <= 2) && (col >= 0 && col <= 2)) ? this->matrix->_data[this->row][col] : 0.0f;
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
6

It would be easier to make the method double operator() (int row, int col) const. Instead of matrix[i][j] you just say matrix(i,j).

NovaDenizen
  • 5,089
  • 14
  • 28
5

Generally, for multiple argument you want to use operator(), not operator[].

Hm, in case it wasn't obvious, there is no operator[][] in C++. It's just operator[] applied twice. Which means that if you want that notation, then you have to let the first one return a result (indexable thing or proxy) that the second can be applied to.

The code below sketches some approaches, choose what you like:

#include <iostream>
#include <vector>

template< int n >
int& dummy() { static int elem = n; return elem; }

struct Mat1
{
    int operator() ( int const x, int const y ) const
    { return dummy<1>(); }

    int& operator() ( int const x, int const y )
    { return dummy<1>(); }

    Mat1( int, int ) {}
};

struct Mat2
{
    int at( int const x, int const y ) const
    { return dummy<2>(); }

    int& at( int const x, int const y )
    { return dummy<2>(); }

    Mat2( int, int ) {}
};

struct Mat3
{
    struct At { At( int x, int y ) {} };

    int operator[]( At const i ) const
    { return dummy<3>(); }

    int& operator[]( At const i )
    { return dummy<3>(); }

    Mat3( int, int ) {}
};

class Mat4
{
protected:
    int get( int const x, int const y ) const
    { return dummy<4>(); }

    void set( int const x, int const y, int const v ) {}

    class AssignmentProxy
    {
    private:
        Mat4*   pMat_;
        int     x_;
        int     y_;
    public:
        void operator=( int const v ) const
        { pMat_->set( x_, y_, v ); }

        int value() const { return pMat_->get( x_, y_ ); }
        operator int () const { return value(); }

        AssignmentProxy( Mat4& mat, int const x, int const y )
            : pMat_( &mat ), x_( x ), y_( y )
        {}
    };

public:
    int operator()( int const x, int const y ) const
    { return get( x, y ); }

    AssignmentProxy operator()( int const x, int const y )
    { return AssignmentProxy( *this, x, y ); }

    Mat4( int, int ) {}
};

class Mat5
{
protected:
    int at( int const x, int const y ) const
    { return dummy<4>(); }

    int& at( int const x, int const y )
    { return dummy<5>(); }

    class RowReadAccess
    {
    private:
        Mat5 const* pMat_;
        int         y_;

    public:
        int operator[]( int const x ) const
        {
            return pMat_->at( x, y_ );
        }

        RowReadAccess( Mat5 const& m, int const y )
            : pMat_( &m ), y_( y )
        {}
    };

    class RowRWAccess
    {
    private:
        Mat5*   pMat_;
        int     y_;

    public:
        int operator[]( int const x ) const
        {
            return pMat_->at( x, y_ );
        }

        int& operator[]( int const x )
        {
            return pMat_->at( x, y_ );
        }

        RowRWAccess( Mat5& m, int const y )
            : pMat_( &m ), y_( y )
        {}
    };

public:
    RowReadAccess operator[]( int const y ) const
    { return RowReadAccess( *this, y ); }

    RowRWAccess operator[]( int const y )
    { return RowRWAccess( *this, y ); }

    Mat5( int, int ) {}
};

struct Mat6
{
private:
    std::vector<int>    elems_;
    int                 width_;
    int                 height_;

    int indexFor( int const x, int const y ) const
    {
        return y*width_ + x;
    }

public:
    int const* operator[]( int const y ) const
    {
        return &elems_[indexFor( 0, y )];
    }

    int* operator[]( int const y )
    {
        return &elems_[indexFor( 0, y )];
    }

    Mat6( int const w, int const h )
        : elems_( w*h, 6 ), width_( w ), height_( h )
    {}
};

int main()
{
    using namespace std;
    enum{ w = 1024, h = 1024 };
    typedef Mat3::At At;

    Mat1 m1( w, h );
    Mat2 m2( w, h );
    Mat3 m3( w, h );
    Mat4 m4( w, h );
    Mat5 m5( w, h );
    Mat6 m6( w, h );

    wcout
        << m1( 100, 200 )       // No fuss simple, but exposes element ref.
        << m2.at( 100, 200 )    // For those who don't like operators.
        << m3[At( 100, 200)]    // If you really want square brackets mnemonic.
        << m4( 100, 200 )       // Hides element ref by using assignment proxy.
        << m5[200][100]         // Ditto but with square brackets (more complex).
        << m6[200][100]         // The minimum fuss square brackets, exposes elem ref.
        << endl;
}

Oh well I discovered after posting that code that I haven't fully hidden the internal storage for Mat5: it needs an extra proxy level, as in Mat4. So that approach is really complex. I wouldn't do it (Mat1 is nice and easy I think), but some folks think proxys are cool, and data hiding even more cool…

Summing up, there is no “the” correct way to overload operator[]. There are many ways (as illustrated by the code above), each with some trade-offs. And generally you’re better off using operator(), because as opposed to operator[] it can take any number of arguments.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
3

There is no [][] operator. The way GLM does it is by returning a vec3& from the first []. vec3 has its own [] operator overload. So it's two separate operator[]s on two separate classes.

This is also how GLSL works. The first [] gets the column as a vector. The second takes the vector and gets the value from it.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

The expression foo[1][2] is really interpreted as (foo[1])[2], i.e. the [] operator is applied twice in succession starting with the variable foo. There is no [][] operator to be overloaded.

markgz
  • 6,054
  • 1
  • 19
  • 41