Konstante Proxy Klasse?



  • 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.


  • Mod

    kkaw schrieb:

    Ich verweise nochmal auf die Slice-Klasse von oben.

    Nimm doch einfach Template-Parameter für die Schrittlänge.



  • Arcoth schrieb:

    kkaw schrieb:

    Ich verweise nochmal auf die Slice-Klasse von oben.

    Nimm doch einfach Template-Parameter für die Schrittlänge.

    Für Matritzen dynamischer Grösse ... lol


  • Mod

    Für Matritzen dynamischer Grösse ... lol

    Ganz vergessen.



  • kkaw schrieb:

    ?!

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

    Hm, hab mir das mit der Slice-Klasse (warum eigentlich "Slice", hat das was mit Object Slicing zu tun?) nochmal angeschaut und kriegs nicht hin, da ich auch da immer den gleichen Fehler wie im Eingangspost bekomme... Also wenn das hier meine slice Klasse ist:

    template <typename T>
    class matrix_slice
    {
    	public:
    		explicit matrix_slice(std::vector<T> &data_, size_t row_, size_t size_) : data(data_), row(row_), size(size_) {}
    
    		T& operator[](size_t col_) { return data[row + col_*size]; }
    
    	private:
    		std::vector<T> &data;
    		size_t row, size;
    };
    

    dann meckert der Compiler halt wieder dass "durch keine der 2 Überladungen alle Parameter konvertiert werden konnten", also das gleiche Problem wie wenn ich direkt die Matrix übergeben würde...



  • Slice steht für "Scheibe" à la "eine Scheibe abschneiden". So, wie ich mir das gedacht hatte, ist es allgemeiner als "Row". Du kannst es eben auch Spalten und Diagonalen verwenden. Wenn Du es nur für Zeilen brauchst, wobei step_ dann immer 1 wäre (by row-major storage), könntest du das natürlich auch zwecks Größenoptimierung weglassen und wieder zu matrix_row oder so umbenennen.

    So, wie du das jetzt aufgeschrieben hattest (mit Referenz auf non-const matrix statt eines Iterators) ist Deine Klasse natürlich nicht für den const-only-Zugriff nutzbar. Keine Ahnung, warum Du unbedingt wieder eine Matrix-Referenz benutzen musstest. Das war ja sicher ein Teil Deines Problems.

    Relativ allgemein aufgeschrieben und diesmal mit 'nem Iterator statt eines rohen Zeigers sieht das so aus.

    template<class RandomAccessIter>
    class slice
    {
    public:
        typedef typename std::iterator_traits<RandomAccessIter>::reference reference;
        slice(RandomAccessIter base, std::size_t size, std::size_t step = 1)
        : base_(base), size_(size), step_(step)
        {}
    
        std::size_t size() const { return size_; }
    
        reference operator[](std::size_t index) const {
            assert(index<size_);
            return base_[index*step_];
        }
    
    private:
        RandomAccessIter base_;
        std::size_t size_;
        std::size_t step_;
    };
    

    (ungetestet, aber eventuelle Fehler solltest Du behoben kriegen)

    Und Deine Matrix-Klasse könntest Du jetzt so bauen:

    template<class T>
    class matrix
    {
    public:
        typedef slice<typename std::vector<T>::iterator> slice_type;
        typedef slice<typename std::vector<T>::const_iterator> const_slice_type;
    
        explicit matrix(std::size_t rows, std::size_t cols)
        : rows_(rows), cols_(cols), elememts_(rows*cols)
        {}
        ...
        const_slice_type row(std::size_t index) const {
            assert(index<rows_);
            return const_slice_type(elements_.begin()+index*cols_,cols_,1);
        }
        slice_type row(std::size_t index) {
            assert(index<rows_);
            return slice_type(elements_.begin()+index*cols_,cols_,1);
        }
        const_slice_type col(std::size_t index) const {
            assert(index<cols_);
            return const_slice_type(elements_.begin()+index,rows_,cols_);
        }
        slice_type col(std::size_t index) {
            assert(index<cols_);
            return slice_type(elements_.begin()+index,rows_,cols_);
        }
        ...
    private:
        ...
        std::size_t rows_, cols_;
        std::vector<T> elements_;
        ...
    };
    

    (auch ungetestet, Fehler kannst du behalten)



  • Alternativ verwendest Du Boost.MultiArray. Das ist noch einen Tucken generischer in der Hinsicht, dass die Dimension des Arrays noch ein Templateparameter ist. Damit kannst Du auch 5D-Arrays bauen oder so ...


Anmelden zum Antworten