Konstante Proxy Klasse?
-
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
einmatrix_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>>
bzwmatrix_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.
-
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[]
vonmatrix_row
gibt ja eine nicht-const Referenz zurück, warum kann ich trotzdemfoo
aufrufen? Das versteh ich nichtArcoth 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 demoperator()
isses ja eh kein Problem) aber istfriend
nicht auch "böse", ähnlich wieconst_cast
? Zumindest ließt man das ja hier im Forum immer wieder, scheint sich aber nicht wirklich jeder dran zu halten
-
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[]
vonmatrix_row
gibt ja eine nicht-const Referenz zurück, warum kann ich trotzdemfoo
aufrufen?Der Rückgabetyp (
matrix_row<matrix, T>
bzw.matrix_row<matrix<T> const, const T>
) der Operatorfunktion inMatrix
ist nichtconst
.
-
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.
-
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
-
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)