Probleme mit eigener Matrix-Klasse



  • @hustbaer sagte in Probleme mit eigener Matrix-Klasse:

    Besser

             const U& operator[](const std::size_t column) const
             {
                 return row_data[column];
             }
    
             U& operator[](const std::size_t column)
             {
                 return row_data[column];
             }
    

    Oder?

    Ja. Aber noch mehr Code zu schreiben und zu pflegen. Ich hatte ja auch dazu geschrieben:

    Für eine reine Proxy-Klasse wie diese hier geht das IMHO okay - falls Row_T auch für den Anwender instanzierbar sein soll, und nicht ausschliesslich für reine temporäre Proxy-Objekte, dann würde ich allerdings empfehlen, das auch so wie std::vector zu machen.

    Man kann sich das meiner Meinung nach für so ein ausschließliches Proxy-Objekt durchaus erlauben, wenn es ohnehin nicht const-qualifiziert verwendet werden soll. Im const Matrix_T-Fall wird Row_T<const T> zurückgegeben und U& ist bereits eine konstante Referenz (const T&). Die Aussage der const-Qualifizierung für den Operator ist bei mir absolut lowlevel: Die Funktion verändert keinen Member des Objekts (der gehaltene Pointer wird nicht umgebogen oder sowas). Deine Variante ist eine erweiterte const-Bedeutung. Nicht strikt notwendig, aber sinnvoll aus "least-surprise"-Gründen. Allderdigs nur, wenn vorgesehen ist, dass der User das Ding tatsächlich irgendwie const-qualifiziert instanziert oder referenziert - sonst ist das "Dead Code" 😉

    Wenn das eine user-instanzierbare Klasse werden soll, dann natürlich besser deine Variante.

    P.S.: Auch interessant: std::vector<T>::data() const -> const T* und std::unique_ptr<T>::get() const -> T*. Zwei Beispiele an denen man sieht, dass das manchmal eine Design-Entscheidung ist. Man kann ja durchaus eine Weltanschauung haben, mit der man beide Klassen für fast dasselbe hält 😉



  • @zeropage:
    Hier haste mal was zum Spielen. Ich habe Row und ConstRow als eigenständige Klassen implementiert, das kann man bestimmt noch iwie geschickter lösen, ohne endlos viel Code zu duplizieren. Ansonsten haben die beiden ziemlich viel boiler plate code, und da ist noch viel Luft nach oben ( z.B. cbegin()/cend(), rbegin()/rend(), crebgin()/crend(), front(), back() und die ganzen anderen Komfortfunktionen:

    #include <vector>
    #include <algorithm>
    #include <type_traits>
    
    template<typename T> class Row;
    template<typename T> class ConstRow;
    
    template<typename T>
    class Matrix2D
    {
       // Matrix aus bool nicht erlauben
       static_assert( !std::is_same<bool,T>::value, "T must not be bool because of std::vector<bool> anomaly" );
    
    public:
       using value_type      = T;
       using reference       = T&;
       using const_reference = T const&;
       using pointer         = T*;
       using const_pointer   = T const*;
       using size_type       = std::size_t;
       using difference_type = std::ptrdiff_t;
       using row_type        = Row<T>;
       using const_row_type  = ConstRow<T>;
    
    private:
       std::vector<T> Elements_;
       size_type      Rows_ = 0;
       size_type      Cols_ = 0;
    
    public:
       Matrix2D() = default;
       Matrix2D( size_type rows, size_type cols ) :
          Matrix2D( row, cols, value_type() )
       {
       }
    
       Matrix2D(size_type rows, size_type cols, const_reference v ) :
          Elements_( rows * cols, v ),
          Rows_( rows ),
          Cols_( cols )
       {
       }
    
       row_type operator[]( size_type index )
       {
          pointer beg = Elements_.data() + index * Cols_; 
          return row_type( beg, beg + Cols_ );
       }
    
       const_row_type operator[]( size_type index ) const
       {
           const_pointer beg = Elements_.data() + index * Cols_; ​
           return const_row_type( beg, beg + Cols_ );
        }
    };
    
    template<typename T>
    class Row
    {
       T* Begin_= nullptr;
       T* End_  = nullptr;
    
    public:
       Row( T* beg, T* end ) :
          Begin_( beg ),
          End_( end )
       {
       }
    
       bool empty() const
       {
          return Begin_ == End_;
       }
    
       std::size_t size() const
       {
          return std::distance( begin(), end() );
       }
    
       T& operator[]( std::size_t index )
       {
          return Begin_[index];
       }
    
       T const& operator[]( std::size_t index ) const
       {
          return Begin_[index];
       }
    
       T* begin()
       {
          return Begin_;
       }
    
       T* end()
       {
          return End_;
       }
    
       T const* begin() const
       {
          return Begin_;
       }
    
       T const* end() const
       {
          return End_;
       }
    };
    
    template<typename T>
    class ConstRow
    {
       T const* Begin_ = nullptr;
       T const* End_   = nullptr;
    
    public:
       ConstRow( T const* beg, T const* end ) :
          Begin_( beg ),
          End_( end )
       {
       }
    
       ConstRow( Row<T> const& r ) :
          Begin_( r.begin() ),
          End_( r.end() )
       {
       }
    
       T const& operator[]( std::size_t index ) const
       {
          return Begin_[index];
       }
    };
    
    int main()
    {
       Matrix2D m( 2, 2,-1 );
       m[0][0] = 1;
    }
    

    Wenn man Row und ConstRow um einen Increment erweitert lassen sie sich auch als Column und ConstColumn verwenden, da musste halt für den operator[]etwas rumrechnen.



  • @Finnegan sagte in Probleme mit eigener Matrix-Klasse:

    Für eine reine Proxy-Klasse wie diese hier geht das IMHO okay - falls Row_T auch für den Anwender instanzierbar sein soll, und nicht ausschliesslich für reine temporäre Proxy-Objekte, dann würde ich allerdings empfehlen, das auch so wie std::vector zu machen.

    Oops, sorry. Hab nicht den ganzen Thread gelesen 🙂

    Deine Variante ist eine erweiterte const-Bedeutung. Nicht strikt notwendig, aber sinnvoll aus "least-surprise"-Gründen. Allderdigs nur, wenn vorgesehen ist, dass der User das Ding tatsächlich irgendwie const-qualifiziert instanziert oder referenziert - sonst ist das "Dead Code" 😉

    Ja. Aber ist recht wenig dead code. Das wäre IMO vertretbar.

    Wobei mir auffällt... damit das wirklich Sinn macht, müsste man die Proxy-Klasse non-copyable machen. Sonst kann sich ja jeder ne Kopie ziehen, die ist dann nicht mehr const, und dann kann man über die Kopie zugreifen. Und non-copyable hat auch wieder Nachteile. Also vermutlich eh besser ohne den const-Overload im Proxy.

    Nicht alles was ich schreibe ist immer 100% durchdacht 🙂



  • @hustbaer Noch east-const und es ist schön ;p



  • @Swordfish Ich hab mich am const std::size_t column orientiert. Ansonsten natürlich immer east const.



  • @hustbaer sagte in Probleme mit eigener Matrix-Klasse:

    @Swordfish Ich hab mich am const std::size_t column orientiert. Ansonsten natürlich immer east const.

    Mir ist das ehrlich gesagt ziemlich egal, hauptsache man einigt sich auf eins und zieht es dann konsequent durch. Ich verwende West.

    Was ich allerdings erstaunlich bei dem Blog-Eintrag in deiner Signatur finde, ist dass ich danach irgendwie mehr von west-const überzeugt war, als von east-const. Der Autor schreibt zwar, die Argumente für west-const seien nicht sonderlich stark, aber ein Knüllerargument für east-const konnte ich irgendwie nicht finden. Nach der Lektüre ist es mir immer noch ziemlich wumpe. Wenn das wirklich ne Revolution werden soll, dann ist mir das nicht polarisierend genug 😉



  • @Finnegan sagte in Probleme mit eigener Matrix-Klasse:

    @hustbaer sagte in Probleme mit eigener Matrix-Klasse:

    @Swordfish Ich hab mich am const std::size_t column orientiert. Ansonsten natürlich immer east const.

    Mir ist das ehrlich gesagt ziemlich egal, hauptsache man einigt sich auf eins und zieht es dann konsequent durch. Ich verwende West.

    This!

    Was ich allerdings erstaunlich bei dem Blog-Eintrag in deiner Signatur finde, ist dass ich danach irgendwie mehr von west-const überzeugt war, als von east-const. Der Autor schreibt zwar, die Argumente für west-const seien nicht sonderlich stark, aber ein Knüllerargument für east-const konnte ich irgendwie nicht finden.

    Vor allem steht da bei dem einem Beispiel "These declarations are harder to read when the West const notation is used.", während ich es genau umgekehrt empfinde. Gerade bei Pointern bevorzuge ich "const möglichst weit auseinander", damit man da nichts verwechselt. Bei east const ist meine erste Assoziation immer: "Aha, nur der Pointer konstant. Moment mal. Ach nee, dann müsste das const ja noch weiter rechts stehen."

    Aber wahrscheinlich alles Gewöhnungs- und Geschmackssache. Warum ich west nutze: weil die east-const Leute gefühlt immer behaupten, dass ihre Präferenz die einzig wahre wäre. Beispiel hier von @Swordfish: nichts zum eigentlichen Thema, aber hier wieder mal "west ist nicht schön" einbringen. Das erzeugt bei mir nur noch mehr Trotz, da "schön" subjektiv ist und ich noch kein überzeugendes Argument gesehen habe.



  • lol

    Dass man es nicht konsequent durchziehen kann ist doch gerade das Argument gegen const west. Ich meine... zeigt mir mal bitte wie man mit konsequentem const west nen konstanten Zeiger auf nen Integer schreibt.

    ps: Ja, Firefox ist auch der bessere Browser 😄



  • @hustbaer sagte in Probleme mit eigener Matrix-Klasse:

    Ja, Firefox ist auch der bessere Browser 😄

    😂

    @wob sagte in Probleme mit eigener Matrix-Klasse:

    Beispiel hier von @Swordfish: nichts zum eigentlichen Thema, aber hier wieder mal "west ist nicht schön" einbringen. Das erzeugt bei mir nur noch mehr Trotz, da "schön" subjektiv ist und ich noch kein überzeugendes Argument gesehen habe.

    Das war nur für Husti, weil ich weiß daß er /eigentlich/ ein east-constler ist. Musst dich nicht gestichelt fühlen.



  • @hustbaer sagte in Probleme mit eigener Matrix-Klasse:

    [...] zeigt mir mal bitte wie man mit konsequentem const west nen konstanten Zeiger auf nen Integer schreibt.

    Da man einen konstanten Zeiger ohnehin mit einem anderen Zeiger initialisieren muss, um den sinnvoll verwenden zu können*:

    int value;
    const auto pointer = &value;
    

    ... oder für Hardware-Bastler:

    const auto pointer = reinterpret_cast<int*>(0xdeadbeef);
    

    Für eine via Member Initializer List initialisierte const Member-Variable, die hab ich aber leider keine gute Lösung anzubieten.

    Allerdings dürfte man nen konstanten Pointer doch eher extrem selten direkt als Typ dekrarieren müssen. Die treten denke ich ungleich häufiger z.B. als (non-const-deklarierte) Member von const Class oder als Template-Parameter const T (mit z.B. T = int*) auf.

    Aber nichtsdestotrotz ist es mir immer noch egal. Ich kann ohne Schmerzen auch mit east-const arbeiten. Würd aber nicht gut zu meinem anderen Code passen - insofern ist das Argument der bestehenden Codebases schon ziemlich stark 😉

    *gibts nen Anwendungsfall für nen uninitalisierten const-Garbage-Wert?



  • Danke für die Codes.
    @DocShoe

    In Deinem Code habe ich an dieser Stelle folgenden Fehler:

     const_row_type operator[](size_type index) const
        {
            const_pointer beg = Elements_.data() + index * Cols_; ​
            return const_row_type(beg, beg + Cols_); // Error C3878	syntax error: unexpected token 'return' following 'id_expression'
        }
    

    Wenn ich nach dem Fehler suche, kommt nur "Hoppla! Es wurde keine F1-Hilfe gefunden." Da ich aber seit Visual Studio 22 mit C++20 öfters Fehler bekomme, die es unter VS19 mit C++17 nicht gab, habe ich den Code damit versucht zu kompilieren, und dann kommt nur ein lapidares

    C2760 syntax error: unexpected token 'return', expected ';'	
    

    wofür es aber immerhin eine Hilfe gibt, die mir aber nicht weiterhilft.
    Zu erwähnen wäre vielleicht noch, das die Variablen in dieser Funktion nicht farblich gekennzeichnet werden, als ob der Compiler diese nicht zuordnen kann. Bei

    row_type operator[](size_type index)
        {
            pointer beg = Elements_.data() + index * Cols_;
            return row_type(beg, beg + Cols_);
        }
    

    macht er noch die farbliche Kennzeichnung.

    edit: So langsam habe ich das Gefühl, das man für die Nutzung von eckigen Klammern in Arrays jenseits der STL seine Doktorarbeit schreiben kann 🙃



  • ideone

    Jo, da waren noch ein paar Fehler drin, hab das gefixt und hochgeladen.
    Besonders nervig ist da ein unsichtbares Zeichen in Zeile 52, keine Ahnung, wo das herkam.



  • Wunderbar! Vielen Dank.
    Ich habe noch den Konstruktor mit initializer-list hinzugefügt

      Matrix2D(const std::initializer_list<std::initializer_list<T>>& elements) :
            Matrix2D{ elements.size(), std::max(elements, [](const auto& row, const auto& column) { return row.size() < column.size(); }).size() }
        {
            size_type r = 0;
            for (const auto& row : elements)
            {
                if (r >= Rows_) break;
                size_type c = 0;
                for (const auto& val : row)
                {
                    if (c >= Cols_) break;
                    (*this)[r][c] = val;
                    ++c;
                }
                ++r;
            }
        }
    

    Und damit funktioniert auch mein Test-Case (wenn ich das so nennen darf). Hoffe nur, das ich damit nicht einen weiteren Fehler fabriziert habe. Aber scheint ja alles gut zu sein.

    #include "Matrix.h"
    #include <iostream>
    
    class Mat
    {
    	Matrix2D<int> matrix;
    
    public:
    	Mat() : matrix{
    		{ 1, 2, 3 },
    		{ 4, 5, 6 },
    		{ 7, 8, 9 } }  {}
    
    	void printVal(const std::size_t r, const std::size_t c) const
    	{
    		std::cout << matrix[r][c] << '\n';
    	}
    
    	void writeVal(const std::size_t r, const std::size_t c, const int val) 
    	{
    		matrix[r][c] = val;
    	}
    };
    
    int main()
    {
    	Mat mat;
    
    	mat.printVal(0, 0);
    	mat.writeVal(0, 0, 9);
    	mat.printVal(0, 0);
    }
    


  • geht da nix mit std::copy?



  • Wird es dadurch einfacher, oder noch komplexer?

    Ich hätte noch eine Frage zu der Klassenbezeichnung Matrix2D. Gibt es überhaupt höherdimensionale Matrizen? Nach meinem Verständnis ist eine Matrix doch nur ein Array2D mit dem man rechnen kann? Und eine Matrix kann auch nicht einen beliebigen Inhalt haben. Ein string zB als Matrix-Inhalt hat doch keinen Sinn, in einem beliebig dimensionalen Array schon.

    Also eine Matrix hat doch immer nur zwei Dimensionen, weshalb man dies nicht noch extra betonen muss?



  • @zeropage sagte in Probleme mit eigener Matrix-Klasse:

    Ich hätte noch eine Frage zu der Klassenbezeichnung Matrix2D. Gibt es überhaupt höherdimensionale Matrizen?

    Nein, man nutzt z.B. n-stufige Tensoren, die kann man dann als n-dimensionale Felder schreiben. Allerdings ist das nur eine mögliche Darstellung.

    Und eine Matrix kann auch nicht einen beliebigen Inhalt haben.

    Matrizen ergeben nur im Kontext eines Vektorraumes Sinn. Ein Vektorraum ist eine algebraische Struktur, die auf einem Skalarenkörper K\mathbb{K} definiert ist. Elemente, die man für die Komponenten von Vektoren und Matrizen im Vektorraum verwendet, müssen Elemente aus K\mathbb{K} sein. Konkreter sind sie aus der Menge M\mathbb{M} auf der die Operationen ++ und \cdot definiert sind. Dazu muss die Algebraische Struktur (M,+,)(\mathbb{M}, + , \cdot) die Körperaxiome erfüllen. Es muss ein Null-Element 0\mathbf{0} und Eins-Element 1\mathbf{1} existieren, die Menge ist abgeschlossen bezüglich der beiden Operationen ++ und \cdot, es gibt inverse Elemente so dass aa=1a\cdot a^{\dagger_{\cdot}} = \mathbf{1} und b+b+=0b + b^{\dagger_{+}} = \mathbf{0} ist. …

    Ein string zB als Matrix-Inhalt hat doch keinen Sinn, in einem beliebig dimensionalen Array schon.

    Wenn die Strings eine Darstellung von Formeln sind, ergibt das schon einen Sinn. Aber dann sollte man besser eine neue Klasse einführen, die dann intern Strings nutzt.



  • Hätte mich jetzt auch enttäuscht, wennn kein Beispiel für höhere Mathematik gekommen wäre 😉

    Bei mit Matrizen rechnen dachte ich jetzt auch eher an solch "einfache" Sachen wie,

    grund-wissen.de/mathematik/lineare-algebra-und-analytische-geometrie/matrizen

    Und ich denke, der obige Code kann trotz aller Komplexität nur mit solchen Matrizen umgehen, weshalb das 2D in Matrix eigentlich nicht nötig ist.



  • @zeropage Da ich im Studium mal 'ne ganze Zeit mit der Eigen3 lib gearbeitet habe, denke ich bei Matrix2D immer erstmal an eine 2x2 Matrix (https://eigen.tuxfamily.org/dox/classEigen_1_1Matrix.html).
    Ich weiß aber gerade nicht, wie das vom Naming her bei anderen Bibliotheken gelöst ist.



  • Ja meine Herren, dann nennt das Ding doch einfach matrix. Oder Matrix. Von mir aus auch matricks. Hab den Code von meiner Array2D Klasse benutzt und nur den Klassennamen geändert.
    Ich hab hier manchmal echt den Eindruck, dass man nur drauf wartet, Details zu bemängeln und sich dann draufzustürzen. Allerdings hat es sonst niemand geschafft, ein brauchbares Beispiel zu posten.



  • @DocShoe sagte in Probleme mit eigener Matrix-Klasse:

    Ich hab hier manchmal echt den Eindruck, dass man nur drauf wartet, Details zu bemängeln und sich dann draufzustürzen. Allerdings hat es sonst niemand geschafft, ein brauchbares Beispiel zu posten.

    Da das hier das Topic von @zeropage war und er danach gefragt hat, habe ich ihm darauf geantwortet 😉 Sonst wäre mir das Naming hier egal.


Anmelden zum Antworten