Konstante Proxy Klasse?



  • Hallo zusammen,

    ich habe mir folgende Klasse geschrieben:

    #include <vector>
    
    template <class T> class matrix;
    
    template <class T>
    class matrix_row
    {
    	public:
    		explicit matrix_row(matrix<T> &mat_, size_t row_) : mat(mat_), row(row_) {};
    		virtual ~matrix_row() {};
    		T &operator[](size_t col_) { return mat.linear_index(row + col_*mat.row_count()); }
    		T const &operator[](size_t col_) const { return mat.linear_index(row + col_*mat.row_count()); }
    
    	private:
    		size_t row;
    		matrix<T> &mat;
    };
    
    template <class T>
    class matrix
    {
    	public:
    		explicit matrix(size_t rows_, size_t cols_) : rows(rows_), cols(cols_) { data.resize(rows_*cols_); };
    		virtual ~matrix() {};
    
    		T &linear_index(size_t index_) { return data[index_]; };
    		T const &linear_index(size_t index_) const { return data[index_]; };
    
    		matrix_row<T> operator[](size_t row_) { return matrix_row<T>(*this, row_); };
    		matrix_row<T> const operator[](size_t row_) const { return matrix_row<T>(*this, row_); };
    
    		size_t row_count() const { return rows; };
    		size_t col_count() const { return cols; };
    
    	private:
    		size_t rows;
    		size_t cols;
    		std::vector<T> data;
    };
    

    Nur leider bekomme ich es nicht hin, das Ganze const-correct zu machen. Wenn ich beispielsweise folgendes versuche:

    void foo(matrix<double> const &mat)
    {
    	std::cout << mat[0][0]; // Fehler
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	matrix<double> mat(3, 3);
    	foo(mat);
    
    	return 0;
    }
    

    Erhalte ich einen Fehler und zwar error C2665: "matrix_row<T>::matrix_row": Durch keine der 2 Überladungen konnten alle Argumenttypen konvertiert werden. mit Verweis auf Zeile 30 (den matrix_row<T> const operator[] ).

    Wie kann ich das lösen?



  • "const X" zurückzugeben ist sinnlos. Mach das nie.

    matrix_row<const T> operator[](size_t row_) const { return matrix_row<T>(*this, row_); };
    


  • Hallo, danke schonmal für die Antwort. Ich hab die Zeile entsprechend verändert, leider bekomme ich immernoch den gleichen Fehler...



  • Meine Zeile hätte so aussehen sollen:

    matrix_row<const T> operator[](size_t row_) const { return matrix_row<const T>(*this, row_); };
    

    Hätte eigentlich mit ein bisschen mitdenken klar sein sollen.



  • Der Konstruktor von matrix_row nimmt eine Referenz ohne const. Die hast du aber nicht in einer const Funktion.



  • const at_the_right_place schrieb:

    Meine Zeile hätte so aussehen sollen:

    matrix_row<const T> operator[](size_t row_) const { return matrix_row<const T>(*this, row_); };
    

    Hätte eigentlich mit ein bisschen mitdenken klar sein sollen.

    Also das hatte ich schon probiert, klappt aber trotzdem nicht.

    manni66 schrieb:

    Der Konstruktor von matrix_row nimmt eine Referenz ohne const. Die hast du aber nicht in einer const Funktion.

    Hab da noch eine Überladung eingefügt mit einer const Referenz, geht aber leider immer noch nicht. Hab auch mal testweise nur einen Konstruktor mit const Referenz versucht, aber immer der gleiche Fehler 😕



  • Das Problem ist, dass Du Dich mit matrix<const T>& nicht auf ein matrix<T> beziehen kannst. Man könnte da jetzt mit Vererbung tricksen, oder einen zweiten Template-Parameter einführen:

    template<class MatrixContainer, class DerefAs>
    class matrix_row
    {
    public:
       ...
       DerefAs operator[](size_t col_index) const {
          return mat(row_index, col_index);
       }
       ...
    private:
       MatrixContainer& mat;
       size_t row_index;
       ...
    };
    

    und dann z.B. bei matrix<T>::operator[](...)const ein matrix_row<matrix<T>,const T&> herausgeben.

    Aber ich würde es anders lösen: Über eine allgemeinere Slice-Klasse:

    template<class T>
    class Slice
    {
    public:
        ...
        T& operator[](size_t index) const {
            assert(index<dim);
            return base[index*step];
        }
        ...
    private:
        T* base;
        size_t size, step;
    };
    

    und mit base direkt in den Vektor zeigen, also matrix<> gar nicht erwähnen. Das ist so auch allgemeiner, weil Du es automatisch für alles mögliche benutzen kannst (Zeilen, Spalten, Diagonalen, etc)...



  • Oder: Du nimmst einfach Eigen. 🙂



  • Der erste Ansatz von mir gerade, war auch nicht ganz richtig. Probier mal:

    template<class MatrixContainer,
             class Ref = decltype(std::declval<MatrixContainer>()(0,0))>
    class matrix_row
    {
    public:
       ...
       Ref operator[](size_t col_index) const {
          return mat(row_index, col_index);
       }
       ...
    private:
       MatrixContainer& mat;
       size_t row_index;
       ...
    };
    

    wobei Du dann matrix_row<matrix<T>> bzw matrix_row<const matrix<T>> zurück geben könntest.

    Aber wie gesagt, das mit der allgemeineren Slice Klasse finde ich noch besser. Und wenn du kannst/darfst, nimm lieber Eigen.



  • happystudent schrieb:

    Nur leider bekomme ich es nicht hin, das Ganze const-correct zu machen.

    class matrix_row
    {
    public:
        explicit matrix_row(matrix<T> const &mat_, size_t row_) : mat(mat_), row(row_) {};
    //...
        private:
    //...
    	matrix<T> const &mat;
    };
    

    So klappts.



  • kkaw schrieb:

    Oder: Du nimmst einfach Eigen. 🙂

    Nein, Eigen will ich nicht nehmen, ich brauch das Ganze numerische Zeug nicht. Ich brauche keine Matrix im mathematischen Sinne sondern nur etwas wie ein 2D-Array.

    Außerdem verwendet Eigen den ()operator was mir aber nicht gefällt. Das Teil verhät sich wie ein 2D-Array, also will ich es auch so adressieren können.

    kkaw schrieb:

    Der erste Ansatz von mir gerade, war auch nicht ganz richtig. Probier mal:

    Klappt nicht, declval kann mit der Vorwärtsdeklaration von matrix nichts anfangen (error C2027: Verwendung des undefinierten Typs "matrix<double>").

    Caligulaminus schrieb:

    So klappts.

    Ne, klappt nicht. So hab ich dann zwar ne const Referenz aber dafür kann ich dann ja nichts mehr assignen:

    int _tmain(int argc, _TCHAR* argv[])
    {
    	matrix<double> mat(3, 3);
    	mat[0][0] = 1; // Geht dann nicht mehr obwohl mat non-const ist
    }
    

    Das kann doch nicht so schwierig sein das schön und einfach zu lösen?



  • happystudent schrieb:

    Ne, klappt nicht. So hab ich dann zwar ne const Referenz aber dafür kann ich dann ja nichts mehr assignen

    Ups.

    Aber so geht's, auch wenn das wahrscheinlich gleich zerrissen wird. (Dabei habe ich matrix_row::matrix_row(...) extra private gemacht;)

    #include <vector>
    
    template <class T> class matrix;
    
    template <class T>
    class matrix_row
    {
    	friend class matrix<T>;
    	explicit matrix_row(matrix<T> const &mat_, size_t row_) : mat(mat_), row(row_) {};
    public:
    	virtual ~matrix_row() {};
    	T &operator[](size_t col_) { return const_cast<matrix<T>&>(mat).linear_index(row + col_*mat.row_count()); }
    	T const &operator[](size_t col_) const { return mat.linear_index(row + col_*mat.row_count()); }
    
    private:
    	size_t row;
    	matrix<T> const &mat;
    };
    
    template <class T>
    class matrix
    {
    public:
    	explicit matrix(size_t rows_, size_t cols_) : rows(rows_), cols(cols_) { data.resize(rows_*cols_); };
    	virtual ~matrix() {};
    
    	T &linear_index(size_t index_) { return data[index_]; };
    	T const &linear_index(size_t index_) const { return data[index_]; };
    
    	matrix_row<T> operator[](size_t row_) { return matrix_row<T>(*this, row_); };
    	matrix_row<T> const operator[](size_t row_) const { return matrix_row<T>(*this, row_); };
    
    	size_t row_count() const { return rows; };
    	size_t col_count() const { return cols; };
    
    private:
    	size_t rows;
    	size_t cols;
    	std::vector<T> data;
    };
    
    void foo(matrix<double> const &mat)
    {
    	std::cout << mat[0][0] << '\n'; 
    }
    
    int main(int argc, char* argv[])
    {
    	matrix<double> mat(3, 3);
    	foo(mat);
    	matrix<double> mat2(3, 3);
    	mat2[0][0] = 1;
    	std::cout << mat2[0][0] << '\n';
    }
    


  • Ja, so funktioniert es immerhin schonmal, aber const_cast wird ja nachgesagt "böse" zu sein und dass man es bei gutem Design nie braucht... nur wie könnte man hier um den cast herum kommen?

    Allerdings kann matrix_row::matrix_row ruhig public bleiben mMn, oder warum hast du das private gemacht?



  • So wirds gemacht:

    template <class T> class matrix;
    
    template <typename Matrix, typename T>
    class matrix_row
    {
    public:
      explicit matrix_row(Matrix *mat_, size_t row_) : mat(mat_), row(row_) { assert(mat!=0); };
      virtual ~matrix_row() {};
      T& operator[](size_t col_) { return mat->linear_index(row + col_*mat->row_count()); }
    
    private:
      Matrix *mat;
      size_t row;
    };
    
    template <class T>
    class matrix
    {
    public:
      explicit matrix(size_t rows_, size_t cols_) : rows(rows_), cols(cols_) { data.resize(rows_*cols_); };
      virtual ~matrix() {};
    
      T &linear_index(size_t index_) { return data[index_]; };
      T const &linear_index(size_t index_) const { return data[index_]; };
    
      matrix_row<matrix, T> operator[](size_t row_) { return matrix_row<matrix, T>(this, row_); };
      matrix_row<matrix<T> const, const T> operator[](size_t row_) const { return matrix_row<matrix<T> const, const T>(this, row_); };
    
      size_t row_count() const { return rows; };
      size_t col_count() const { return cols; };
    
    private:
      size_t rows;
      size_t cols;
      std::vector<T> data;
    };
    


  • happystudent schrieb:

    oder warum hast du das private gemacht?

    Eben wegen des bösen const_cast , so bleibt es intern, und man kann kein Schindluder damit treiben.


  • Mod

    Edit: Fragen haben sich von selbst geklärt.
    Ich schlage mal

    #include <memory>
    
    template <class T>
    class matrix
    {
    public:
    	explicit matrix(std::size_t rows_, std::size_t cols_) :
    		rows(rows_), cols(cols_),
    		data{ std::make_unique<T[]>(rows*cols) } {} // Garantiert value-initialized!
    
    	T&       operator()(std::size_t index_)       { return data[index_]; };
    	T const& operator()(std::size_t index_) const { return data[index_]; };
    
    	T&       operator()( std::size_t r, std::size_t c )       { return data[c*rows + r]; }
    	T const& operator()( std::size_t r, std::size_t c ) const { return data[c*rows + r]; }
    
    	std::size_t row_count() const { return rows; };
    	std::size_t col_count() const { return cols; };
    
    private:
    	std::size_t rows;
    	std::size_t cols;
    
    	std::unique_ptr<T[]> data; // Gut, streng genommen werden auch hier noch die Dimensionen redundant abgespeichert. Aber das lässt sich kaum elegant verhindern.
    };
    
    #include <iostream>
    
    void foo(matrix<double> const &mat)
    {
    	std::cout << mat(0, 0) << '\n';
    }
    
    int main()
    {
    	matrix<double> mat(3, 3);
    	foo(mat);
    	matrix<double> mat2(3, 3);
    	mat2(0, 0) = 1;
    	std::cout << mat2(0, 0) << '\n';
    }
    

    vor, der wird auch gleich zerissen. 🤡

    Falls kein make_unique vorhanden, selbst ergänzen.

    Und falls man auf die Version mit operator[] besteht, geht das so:

    template <class T>
    class matrix_row
    {
    	friend class matrix<T>;
    	explicit matrix_row( T* ptr_ ) : ptr_(ptr_) {};
    
    	T* ptr_;
    
    public:
    
    	T&       operator[](std::size_t col)       { return ptr_[col]; }
    	T const& operator[](std::size_t col) const { return ptr_[col]; }
    };
    


  • const at_the_right_place schrieb:

    So wirds gemacht:

    Ok, das funktioniert tatsächlich wie ich will aber ich versteh nicht warum 😃

    Der operator[] von matrix_row gibt ja eine nicht-const Referenz zurück, warum kann ich trotzdem foo aufrufen? Das versteh ich nicht 😮

    Arcoth schrieb:

    Edit: Fragen haben sich von selbst geklärt.
    Ich schlage mal
    vor, der wird auch gleich zerissen. 🤡

    Ok, das klappt auch (mit dem operator[] , mit dem operator() isses ja eh kein Problem) aber ist friend nicht auch "böse", ähnlich wie const_cast ? Zumindest ließt man das ja hier im Forum immer wieder, scheint sich aber nicht wirklich jeder dran zu halten 😃


  • Mod

    aber ist friend nicht auch "böse"

    Das Böse-sein ist ein Hinweis an Anfänger, ein Feature nicht zu verwenden bevor man nicht genau seine Fallen kennt.

    Der operator[] von matrix_row gibt ja eine nicht-const Referenz zurück, warum kann ich trotzdem foo aufrufen?

    Der Rückgabetyp ( matrix_row<matrix, T> bzw. matrix_row<matrix<T> const, const T> ) der Operatorfunktion in Matrix ist nicht const .



  • Ok, ich glaub ich habs kapiert 🙂

    Vielen Dank an alle für die Hilfe, damit ist meine matrix Klasse jetzt endlich auch const-correct 👍



  • ?!

    Verstehe nicht, warum ihr das so umständlich macht. Ich verweise nochmal auf die Slice-Klasse von oben.


Log in to reply