Index-Operator doppelt überladen?



  • Guten Abend ihr Nachtaktiven,

    ich stehe vor folgendem Problem. Ich habe eine Klasse matrix geschrieben, welche eine (mathematische) Matrix mit m*n Elementen darstellt. Intern werden die Werte in einem dynamisch alloziierten, zweidimensionalen Array vom Typ double gespeichert. Nun würde ich Elemente der Matrix aber gerne über

    objekt[x][y]
    

    ansteuern. Den einzelnen Index-Operator kann ich problemlos überladen, aber wie kann ich den doppelten [][]-Operator überladen? Geht das überhaupt?

    Grüße und eine noch eine schöne Nacht,

    Ens



  • Ich würde behaupten das geht indirekt, in dem du ein Objekt zurückgibst, welches einen Wrapper für die eigentlichen Daten bereitstellt und den operator[] neu definiert.





  • In der Welt der BLAS-Bibliotheken hat es sich eingebürgert, den operator () für die indizierung zu verwenden. Vektoren haben dann meistens beide Operatoren v[i] und v(i), während matrizen nur noch (i,j) haben. Dann spart man sich das Zwischenobjekt welches ein wenig Problematisch ist - es kann recht schnell verwechselt werden ob bei der matrix mat[i] nun eine Zeile oder Spalte ist.



  • otze schrieb:

    In der Welt der BLAS-Bibliotheken hat es sich eingebürgert, den operator () für die indizierung zu verwenden. Vektoren haben dann meistens beide Operatoren v[i] und v(i), während matrizen nur noch (i,j) haben.
    Dann spart man sich das Zwischenobjekt welches ein wenig Problematisch ist - es kann recht schnell verwechselt werden ob bei der matrix mat[i] nun eine Zeile oder Spalte ist.

    Verstehe ich nicht. Der kostenlose Proxy leitet, wenn er was taugt, die Anfrage einfach nur weiter. Ob nun bei mat[a][5] oder bei mat(a,5) das a die Spalte oder Zeile bedeutet, ist ihm vollkommen egal.

    #include <iostream>
    using namespace std;
    
    template<typename T,size_t SIZEY,size_t SIZEX>
    class Matrix
    {
    private:
        T data[SIZEY][SIZEX];//oder vector<vector> oder
        //hashtable für dünnbesetzt oder gigantisch per mmap auf
        //der platte…
    public:
        T& at(size_t y,size_t x) //einheitliche zugriffsfunktion
        {
            assert(tod und teufel);//das reicht doch schon als grund
            return data[y][x];
        }
        struct AccessProxy//und diese klasse fangen halt alle 2d-container
        {
            Matrix* m;
            size_t y;
            AccessProxy(Matrix *m,size_t y)
                :m(m),y(y)
            {
            }
            T& operator[](size_t x)
            {
                return m->at(y,x);
            }
        };
        AccessProxy operator[](size_t y)//und den auch
        {
            return AccessProxy(this,y);
        }
        //auf const gerade mal verzichtet
    };
    
    int main()
    {
        Matrix<int,3,4> m;
        for(int y=0; y!=3; ++y)
            for(int x=0; x!=4; ++x)
                m[y][x]=x*y;
        for(int y=0; y!=3; ++y)
        {
            for(int x=0; x!=4; ++x)
                cout<<m[y][x]<<'\t';
            cout<<'\n';
        }
        return 0;
    }
    

    Das muss andere Gründe haben, warum dort mat(i,j) üblich ist. Denke eher, es lag daran, daß man sich im vorigen Jahrhundert den ehrlichen Proxy nicht traute, er könnte ja Rechenzeit kosten, man schmierte Wissen über die Implementierung hinein.

    #include <iostream>
    using namespace std;
    
    template<typename T,size_t SIZEY,size_t SIZEX>
    class Matrix
    {
    private:
        T data[SIZEY][SIZEX];//oder vector<vector> aber nicht viel mehr
    public:
        T* operator[](size_t y)//freiwillig in ketten gelegt.
        {
            return &data[y][0];
        }
        //auf const gerade mal verzichtet
    };
    
    int main()
    {
        Matrix<int,3,4> m;
        for(int y=0; y!=3; ++y)
            for(int x=0; x!=4; ++x)
                m[y][x]=x*y;
        for(int y=0; y!=3; ++y)
        {
            for(int x=0; x!=4; ++x)
                cout<<m[y][x]<<'\t';
            cout<<'\n';
        }
        return 0;
    }
    


  • volkard schrieb:

    Verstehe ich nicht. Der kostenlose Proxy leitet, wenn er was taugt, die Anfrage einfach nur weiter. Ob nun bei mat[a][5] oder bei mat(a,5) das a die Spalte oder Zeile bedeutet, ist ihm vollkommen egal.

    Du hast leider nicht verstanden worauf ich hinaus will. Um den kompletten Ausdruck m[i][j] geht es nicht, sondern um m[i]

    foo(m[i]);
    


  • otze schrieb:

    volkard schrieb:

    Verstehe ich nicht. Der kostenlose Proxy leitet, wenn er was taugt, die Anfrage einfach nur weiter. Ob nun bei mat[a][5] oder bei mat(a,5) das a die Spalte oder Zeile bedeutet, ist ihm vollkommen egal.

    Du hast leider nicht verstanden worauf ich hinaus will. Um den kompletten Ausdruck m[i][j] geht es nicht, sondern um m[i]

    foo(m[i]);
    

    Ja, und?
    Wenn man das nicht will, macht man den Proxy halt privat.


  • Mod

    Wenn man das nicht will, macht man den Proxy halt privat.

    Da reicht schon decltype , und so privat ist er dann gar nicht mehr.



  • Wenn man das nicht will, macht man den Proxy halt privat.

    template<class MatrixRow>
    void foo(MatrixRow& row);
    


  • otze schrieb:

    Wenn man das nicht will, macht man den Proxy halt privat.

    template<class MatrixRow>
    void foo(MatrixRow& row);
    

    Ok, gutes Argument.
    Wieso sollte man das denn nicht wollen?
    Man spendiert dem Proxy noch size und iterators und schon hat man ein vollständige row.



  • Nathan schrieb:

    Ok, gutes Argument.
    Wieso sollte man das denn nicht wollen?
    Man spendiert dem Proxy noch size und iterators und schon hat man ein vollständige row.

    könnte auch ne column sein.

    //edit vgl:

    foo(m.transpose()[i]);
    


  • otze schrieb:

    volkard schrieb:

    Verstehe ich nicht. Der kostenlose Proxy leitet, wenn er was taugt, die Anfrage einfach nur weiter. Ob nun bei mat[a][5] oder bei mat(a,5) das a die Spalte oder Zeile bedeutet, ist ihm vollkommen egal.

    Du hast leider nicht verstanden worauf ich hinaus will. Um den kompletten Ausdruck m[i][j] geht es nicht, sondern um m[i]

    foo(m[i]);
    

    Uups! Da erwarte ich aber eher, daß es getRow(size_t) und getColumn(size_t) oder sowas geben muss.
    foo(m[i]); halte ich für einen dunklen Hack, gemein und hinterhältig. Aber ich sehe ein, daß man Arcoth und Nathan wohl nicht davon abhalten kann, sowas zu machen, wenn man op[][] anbietet.



  • Muss man auch niemanden abhalten.
    Nur ehrliche Unfälle vermeiden.
    Wenn das Ding AccessProxy heisst sollte das aber schon reichen.
    Wer meint dann foo()s implementieren zu müssen die so einen AccessProxy als Parameter nehmen ist selbst schuld.



  • volkard schrieb:

    Aber ich sehe ein, daß man Arcoth und Nathan wohl nicht davon abhalten kann, sowas zu machen, wenn man op[][] anbietet.

    Wenns nur die Beiden wären. Ich habe erst Donnerstag diese Diskussion mit einem Kollegen geführt, der dann auch meinte, dass es doch toll wäre, wenn op[] eine Zeile zurückgeben würde. Und solche Unfälle lassen sich ja schwerlich vermeiden. Da muss ja nur jemand glauben, dass das Ding sicherlich irgendeine row zurückgeben wird und dann sein

    template<class ArbitraryVector>
    void foo(ArbitraryVector& vector);
    

    drauf werfen. Man kann auch irgendwie argumentieren, dass row-major matrizen eine Zeile zurückgeben und column-major eine Spalte, aber dann kriegt man probleme damit, dass

    foo(m[i]);
    foo(transpose(m)[i]);
    

    die selbe Zeile von m verändern aber

    Matrix m = transpose(m);
    foo(m[i]);
    

    dann etwas anderes macht. Und wenn man immer eine Zeile zurückgibt dann hat man das Problem, dass etwas was einfach aussieht eventuell richtig teuer ist.



  • volkard schrieb:

    foo(m[i]); halte ich für einen dunklen Hack, gemein und hinterhältig. Aber ich sehe ein, daß man Arcoth und Nathan wohl nicht davon abhalten kann, sowas zu machen, wenn man op[][] anbietet.

    Äh, moment: Ich würde das nicht machen, ich sehe nur keinen direkten Grund das zu verbieten.
    Bei mir wär der Proxy private und wer drauf zugreift ist selbst schuld.



  • Nathan schrieb:

    Bei mir wär der Proxy private und wer drauf zugreift ist selbst schuld.

    Du meinst, dass es nur zufall ist, dass du ihn aus einer Funktion zurückgibst? 😉



  • otze schrieb:

    Nathan schrieb:

    Bei mir wär der Proxy private und wer drauf zugreift ist selbst schuld.

    Du meinst, dass es nur zufall ist, dass du ihn aus einer Funktion zurückgibst? 😉

    Zugreift im Sinne: Etwas anderes als operator[] aufzurufen. 😉



  • Nathan schrieb:

    otze schrieb:

    Nathan schrieb:

    Bei mir wär der Proxy private und wer drauf zugreift ist selbst schuld.

    Du meinst, dass es nur zufall ist, dass du ihn aus einer Funktion zurückgibst? 😉

    Zugreift im Sinne: Etwas anderes als operator[] aufzurufen. 😉

    Meine Beispiele machen ja auch nicht mehr.



  • otze schrieb:

    Nathan schrieb:

    otze schrieb:

    Nathan schrieb:

    Bei mir wär der Proxy private und wer drauf zugreift ist selbst schuld.

    Du meinst, dass es nur zufall ist, dass du ihn aus einer Funktion zurückgibst? 😉

    Zugreift im Sinne: Etwas anderes als operator[] aufzurufen. 😉

    Meine Beispiele machen ja auch nicht mehr.

    Nee, ich mein den operator vom Proxy. Mehr sollte man damit nicht machen, weils ein Implementierungsdetail ist.



  • Ich persönlich stehe ja mehr auf eine Wiederverwendbare Implementierung, einfach A[i][j] auf A(i,j) delegieren.

    template <typename Matrix>
    class bracket_proxy
    {
    public:
    
        typedef typename Matrix::value_type value_type;
        typedef const value_type& const_reference_type;
    
        typedef typename std::conditional<
            std::is_const<Matrix>::value,
            const_reference_type,
            value_type&
        >::type reference_type;
    
        bracket_proxy(Matrix& A, int i) : A(A), i(i) {}
    
        reference_type operator[](int j)
        {
            return A(i,j);
        }
    
    private:
        Matrix& A;
        int i;
    };
    
    class Matrix {
        /// ....
        bracket_proxy<Matrix> operator[](int i)
        {
            return bracket_proxy<Matrix>(*this, i);
        }
    
        bracket_proxy<const Matrix> operator[](int i) const
        {
            return bracket_proxy<const Matrix>(*this, i);
        }
    
        /// ....
    };
    

Anmelden zum Antworten