Matrix/Vektorklasse



  • Guten Abend,

    ich bearbeite gerade meine alten Matrix/Vektorklassen, die ich mir für ein Spiel geschrieben hatte. Momentant sind das noch zwei verschiedene Klassen, ich bin mir allerdings nicht ganz sicher, ob ich sie noch "vereine". (Das gehört aber eigentlich nicht zur Frage.)
    Also meine Matrixklasse sieht in etwa so aus:

    template<typename T, unsigned N>
      struct BlockMatrix
      {
        // ...
      private:
        T data_[N][N];
      public:
        // ...
        // Bitte keine Hinweise auf size_t statt unsigned. ;)
        T* operator [] (unsigned index)
        {
          return data_[index];
        }
        T const* operator [] (unsigned index) const
        {
          return data_[index];
        }
        // ...
      };
    

    Ich gebe hier also T* bei operator [] zurück, was mir so nicht gefällt. Eine Referenz auf einen Vektor zurückzugeben würde ich wesentlich angenehmer finden. Allerdings muss die Matrix eine .data() Methode haben, die auf sicher zusammenhängenden Speicher zeigt. (Um sie in einen Shader zu kopieren.)
    Das bedeutet, dass ich nicht einfach einen GeometricVector<T, N> vectors_[N] verwenden kann, was natürlich perfekt wäre. (Nervt mich eh extrem, dass der Standard kein Mittel zur Paddingkontrolle gibt. -.-)
    Ich könnte natürlich eine Vektorklasse schreiben, die einfach einen Pointer auf ihren Speicherbereich erhält. Wenn ich das dann allerdings zurückgeben würde (value), würde das komisches Verhalten nach sich ziehen:

    auto v = matrix[0];
    v.x() = 5; // Verändert Matrix, obwohl v keine Referenz ist.
               // So ein Verhalten ist aber natürlich völlig undurchsichtig.
    

    operator [] muss also definitiv eine Referenz auf irgendetwas zurückgeben. Wenn ich also zusätzlich zu T[N][N] noch ein Array aus N Vektoren definiere, die jeweils einen Teil des Speichers "verwalten", und auf die ich dann Referenzen liefern kann, stellt sich ein neues Problem. Wie initialisiere ich die? Da die Größe nicht bekannt ist, kann man die Initialisierungsliste vergessen. Ok, also in einer Schleife im Konstruktor. Trotzdem, operator = ist ja schon besetzt, der macht eine tiefe Kopie. Also vielleicht über eine "setMemory()" Methode? Ja, das könnte wohl gehen, aber irgendwie scheint mir das Ganze mehr und mehr zusammengefrickelt, und ich wollte mal hören, ob hier jemand eine bessere Idee dazu hat.

    Besten Dank im Voraus. 🙂

    Edit: Mittlerweile bin ich fast gewillt, den operator [] ganz wegzulassen und nur einen operator (i, j) anzubieten.



  • Dank Pis völliger Schnapsidee versuche ich mich gerade an reinterpret_cast<>. 😃
    Soweit ich mich erinnern kann, ist hier

    struct A { int i; };
    

    garantiert, dass gilt: (&A == &A.i).
    Da die Vektorklasse ja auch nur einen Member hat, sollte so ein Cast also eigentlich möglich sein:
    http://ideone.com/luBe4

    #include <iostream>
    
    struct V
    {
      char foo[5];
      V()
      {}
      char& operator [] (unsigned index)
      {
        return foo[index];
      }
      void print()
      {
        std::cout << foo[2] << '\n';
      }
    };
    
    struct A
    {
      char foo[5][5];
      V& operator [] (unsigned index)
      {
        return reinterpret_cast<V&>(foo[index]);
      }
    };
    
    int main()
    {
      A a;
      a[2][2] = 'L';
      a[2].print();
    }
    

    Ich frage mich jetzt nur, ob dieser Cast auch von POD nach nicht POD definiert ist?



  • cooky451 schrieb:

    Edit: Mittlerweile bin ich fast gewillt, den operator [] ganz wegzulassen und nur einen operator (i, j) anzubieten.

    Warum baust du dir keinen Proxy, der den operator(i,j) aufruft?



  • Ein löbliches Ziel, wie ich finde. Ich sehe nur keine vernünftige Lösung. Dass "auto" mit Vorsicht zu genießen ist, viel mir schon auf, als ich mich mit Expression Templates beschäftigt habe.

    Der C++ Standard macht es nicht besser:

    vector<bool> foo;
    foo.push_back(false);
    auto x = foo.front();
    x = true;
    cout << foo.front() << endl;
    

    http://ideone.com/luHK0

    Ich habe schon öfters mal darüber nachgedacht, ob bei so einer Stelle "operator auto" nicht ganz praktisch sein könnte:

    class bool_ref
    {
      vector<bool>& vec;
      int index;
    public:
      …
      bool_ref& operator=(bool b) const { vec[index] = b; return *this; }
    
      bool operator auto() const { return vec[index]; }
      …
    };
    
    …
    
      auto x = …bool_ref-Ausdruck…;
      decltype(x) --> bool
    


  • Ich bin der reinterpret_cast-Lösung momentan wirklich zugeneigt. Funktioniert zumindest mit VS und GCC, und ich kann nur vermuten, dass das Verhalten sogar garantiert ist.
    Die Iteratoren der Matrix habe ich auch von T()[N] auf T geändert. Fragt sich eigentlich nur noch, ob es sich lohnt der Matrixklasse zwei einzelne Dimensionen zu spendieren und die Vektorklasse aufzulösen. Allerdings müsste man bei Vektoren dann v[0][1] statt v[1] schreiben, andererseits schreibe ich für Vector2, Vector3 und Vector4 eh extra Klassen, bei denen man auch mit .x(), .y(), .. auf die Elemente zugreifen kann. Schwere Entscheidung. 🤡
    Aber ich glaube mit reinterpret_cast immerhin für das operator [] Problem eine nette Lösung gefunden zu haben.
    Wenn der Kram fertig ist poste ich ihn mal hier, mathematisch wohl eher uninteressant aber vielleicht freut es ja ein paar Spieleleute.

    @krümelkacker
    Ein Grund mehr vector<bool> zu meiden. 🙂



  • cooky451 schrieb:

    Eine Referenz auf einen Vektor zurückzugeben würde ich wesentlich angenehmer finden.

    Wäre bei

    std::array< GeometricVector<T, N>, N >
    

    das einzige Problem, dass GeometricVector<T, N> durch Padding grösser als N * sizeof(T) sein könnte, selbst wenn du intern ein Array T[N] verwenden würdest?

    krümelkacker schrieb:

    Ich habe schon öfters mal darüber nachgedacht, ob bei so einer Stelle "operator auto" nicht ganz praktisch sein könnte

    Sowas könnte man vielleicht auch durch Traits bewerkstelligen:

    template <typename T>
    struct unproxified_type
    {
        typedef T type;
    };
    
    template <>
    struct unproxified_type< std::vector<bool>::reference >
    {
    	typedef bool type;
    };
    
    template <typename T>
    typename unproxified_type<T>::type unproxify(T expr)
    {
        return expr;
    };
    
    #define AUTO(var, expr) auto x = unproxify(expr)
    

    Vielleicht noch mit std::forward . Gibt es eigentlich bereits eine Metafunktion, die einen Typen gleich r/lvalue-qualifiziert wie einen anderen?

    metafn<double, int>::type        == double
    metafn<double, int&&>::type      == double&&
    metafn<double, const int&>::type == const double&
    

    Das AUTO -Makro ist zwar nicht so hübsch wie die Zuweisung und muss dem Anwender bekannt sein, aber operator auto kommt mir ehrlich gesagt auch etwas speziell vor 😉


  • Mod

    Ein static_assert(sizeof(Vector<T, N>)==sizeof(T[N])) ist trotzdem erforderlich, um Padding am Ende zu umgehen. Andernfalls wird dir
    Matrix[0]=Matrix[1];
    Probleme bereiten.



  • Nexus schrieb:

    Wäre bei

    std::array< GeometricVector<T, N>, N >
    

    das einzige Problem, dass GeometricVector<T, N> durch Padding grösser als N * sizeof(T) sein könnte, selbst wenn du intern ein Array T[N] verwenden würdest?

    Jop.

    camper schrieb:

    Andernfalls wird dir
    Matrix[0]=Matrix[1];

    Sicher? Der operator = ist ja manuell geschrieben, oder darf da im Hintergrund noch mehr kopiert werden? (Ich denke mal du beziehst dich darauf, dass dann mehr kopiert werden könnte?)


Anmelden zum Antworten