Frage zu CRTP (Curiously recurring template pattern)
-
@Shade Of Mine:
Dein Code (mit dem falsch geschriebenen GetHesh) sollte nichtmal compilieren.Und ich checke auch gerade nicht ganz wie Concepts hier helfen sollten.
-
hustbaer schrieb:
@Shade Of Mine:
Dein Code (mit dem falsch geschriebenen GetHesh) sollte nichtmal compilieren.Wieso nicht? Wenn ich mal von etwaigen Tippfehlern absehe.
Das Problem ist der Cast.
return static_cast<T*>(this)->GetHash();Aber T hat immer ein GetHash, naemlich wenn es kein eigenes hat, dann das von IHashable. Und damit haben wir eine endlos rekursion.
Concepts helfen hier, weil IHashable ein Concept sein soll. Concepts sind die Interfaces der statischen Welt.
foo(IHashable<T>) ist ja nichts anderes als eine Garantie dass der Parameter ein GetHash() anbietet. Genau sowas garantiert man aber mit Concepts (bzw. type traits bzw in C++ leider garnicht)
-
Aaaaaah, jetzt verstehe ich was du meinst.
Ich nehme alles zurück und behaupte das Gegenteil!Ja, hast Recht, natürlich compiliert der Code, aber macht dann nen Stackoverflow.
Klar, um solche Fehler zu vermeiden würden Concepts helfen.
Ich hatte mich jetzt eher auf die letzte Frage konzentriert, nämlich wie statische Polymorphie dynamisch verwenden kann. Was natürlich nicht geht, ausser wenn man entsprechende Wrapperklassen baut. Was dann aber den Vorteil von statischer Polymorphie (inlining) wieder vernichtet.
Und da würden Concepts auch nicht helfen.
-
Ishildur schrieb:
...
Was gewinnst Du denn damit?
Warum auch einfach, wenn's kompliziert geht, was?
-
hustbaer schrieb:
Ich hatte mich jetzt eher auf die letzte Frage konzentriert, nämlich wie statische Polymorphie dynamisch verwenden kann. Was natürlich nicht geht, ausser wenn man entsprechende Wrapperklassen baut. Was dann aber den Vorteil von statischer Polymorphie (inlining) wieder vernichtet.
Und da würden Concepts auch nicht helfen.
Ja. Den Punkt habe ich nicht wirklich beachtet weil man entweder statische oder dynamische Polymorphie hat - mixen geht nicht gut. Was geht ist die vtable per CRTP ein und auszuschalten je nachdem was man will... Aber direktes mixen klappt nicht.
Denn was Base* fuer dynamische Polymorphie ist, ist T fuer statische.
-
Hi, du verwendest das Pattern leider falsch.
template<class T> class IHashable{ public: T& self(){ return static_cast<T*>(this); } }; class MyHashableClass:public IHashable<MyHashableClass>{ public: int GetHash(){ return 5; } }; template<class T> void DoSomethingWithHashableClassInstance(IHashable<T> *Hash){ printf("%i\n",Hash->self().GetHash()); }
so wird ein Schuh draus. Und ja, das ist eine ziemlich coole Emulation von Concepts. Indem ich von IHashable erbe gebe ich an, dass T alle Eigenschaften eines IHashable erfüllt, ohne den Typen weiter einzuschränken. Natürlich kann ich nicht überprüfen, ob der Typ das wirklich tut, das können nur concepts. Allerdings fängt er vieles durch Compilefehler ab.
Boost Ublas verwendet das ziemlich häufig um vektor und matrixargumente statisch zu unterscheiden.
-
@otze: Nur mal aus Interesse: Kannst du mir mal erklären, was diese Konstruktion für einen Vorteil bietet gegenüber
template<typename Hashable> void DoSomethingWithHashable(T* hash) { printf("%i\n",Hash->GetHash()); }
Ich baue mir da eine zusätzliche Indirektion und eine imho nutzlose Vererbungsbeziehung ein - und ob der übergebene Typ wirklich eine GetHash()-Methode liefert, wird damit auch nicht sichergestellt.
-
Jau. Das Beispiel da oben ist jetzt ziemlich schlecht dafür. Aber es gibt viel bessere
Ich nehme gleich mal lineare Algebra als Beispiel:
template<class T> struct VectorExpression{ T& self(){ return static_cast<T*>(this); } }; //Vektortypen template<class Element> struct DenseVector:public VectorExpression<DenseVector<Element> >{ typedef Element value_type; Element& operator()(size_t i){...} }; template<class Element> struct SparseVector:public VectorExpression<SparseVector<Element> >{ typedef Element value_type; Element& operator()(size_t i){...} }; template<class T> struct MatrixExpression{ T& self(){ return static_cast<T*>(this); } }; //Matrixtypen template<class Element> struct DenseMatrix:public MatrixExpression<DenseMatrix<Element> >{ typedef Element value_type; Element& operator()(size_t i,size_t j){...} }; template<class Element> struct SparseMatrix:public MatrixExpression<SparseMatrix<Element> >{ typedef Element value_type; Element& operator()(size_t i,size_t j){...}//ineffizient, aber jetzt fürs Beispiel okay... }; //Summe aller elemente: template<class T> typename T::value_type sumOfElements(VectorExpression<T>& vec){...} template<class T> typename T::value_type sumOfElements(MatrixExpression<T>& mat){...}
Die Anwendungsfunktionen sind jetzt zwar wieder trivial, aber das ist okay. Das Problem ist, dass du in sumOfElements zwischen vektoren und Matrzen unterscheiden musst. Und du musst damit kämpfen, dass es unterschiedliche Vektoren und Matrizen gibt. Deswegen reicht T& als Argument nicht aus. Gleichzeitig möchtest du keine virtuelle Basisklasse haben, da virtueller Arrayzugriff für Numerik irgendwie suboptimal ist.
Ausserdem hast du immer noch eine bessere Compilerunterstützung. Die Fehlermeldung "Kann keine passende Funktion vom Typ foo(Matrix) finden. Kandidat ist foo(VectorExpression<T>&)" ist wesentlich aussagekräftiger als wenn du von 3 Seiten Compilerfehlermeldungen über fehlende Methoden und typedefs überhäuft wirst.Ich baue mir da eine zusätzliche Indirektion und eine imho nutzlose Vererbungsbeziehung ein - und ob der übergebene Typ wirklich eine GetHash()-Methode liefert, wird damit auch nicht sichergestellt.
Natürlich wirds das. Du Garantierst es mit der Klasse (zumindest garantierst du, dass es kein Typ mit anderer _Semantik_ als der Basisklasse ist). Und wenn du dich vertippst, meckert der Compiler sofort.
-
OK, in diesem Zusammenhang macht es Sinn.
otze schrieb:
Ich baue mir da eine zusätzliche Indirektion und eine imho nutzlose Vererbungsbeziehung ein - und ob der übergebene Typ wirklich eine GetHash()-Methode liefert, wird damit auch nicht sichergestellt.
Natürlich wirds das. Du Garantierst es mit der Klasse (zumindest garantierst du, dass es kein Typ mit anderer _Semantik_ als der Basisklasse ist). Und wenn du dich vertippst, meckert der Compiler sofort.
Worauf ich hinaus wollte: Nur dadurch, daß ich meine Klasse von IHashable (bzw. VectorExpression) ableite, garantiere ich noch überhaupt nichts zu den Operationen, die sie unterstützen wird. Ich muß immer noch irgendwo (informell) spezifizieren, was für für Methoden eine Hash-Klasse bzw. ein Vektor unterstützen muß und wenn ich ableite ohne diese Methoden zu bieten, kriege ich vermutlich trotzdem eine meterlange Fehlermeldung tief im Inneren des Template-Codes.
-
Habe die 2. Seite nicht gesehen
-
CStoll schrieb:
Worauf ich hinaus wollte: Nur dadurch, daß ich meine Klasse von IHashable (bzw. VectorExpression) ableite, garantiere ich noch überhaupt nichts zu den Operationen, die sie unterstützen wird.
Ja, die Technik kann dir nicht Unittests für die Interfaces abnehmen. Wenn es so einfach wäre, hätten wir die Diskussion über concepts in c++0x nicht gebraucht :).
Hauptsächlich ist es eben eine Hilfe für die Benutzer der Bibliothek. Haben die Programmierer vorher gute Arbeit geleistet, werden die meisten Fehler schon recht früh abgefangen. Und durch den Mechanismus ist auch die Erweiterung der Bibliothek durch neue Funktionalität einfacher.
//edit tippsler