Template Vererbung



  • Hi,

    was ich machen will, ist als Templateparameter nur Klassen zulassen, die von einer bestimmten Basisklasse erben, da sie bestimmte funktionen bereitstellen müssen.
    Also irgendwie sowas:
    template <class T(MyBaseKlass)> ...

    Google gibt bei den Stichworten Template/Vererbung immer nur die Frage aus, wie man von einer Templateklasse erben könnte, was aber nicht mein Problem ist.

    Mfg



  • Wozu würdest du das denn brauchen?



  • Was mich momentan juckt ist z.B. die Möglichkeit einer Hashtabelle, sodass die Keys somit nicht immer eine eigene Hash-berechnungs-klasse bräuchte, sondern die hashValue() methode selbst implementieren könnte, was meiner Meinung nach halt viel schöner aussieht.



  • Kommst du aus Java? Dann vergiss mal schnell alles. Templates können mehr als Generics, wenn du willst, dass ein Typ hashValue() implementiert, dann setz das voraus und verwende das einfach.

    template <typename T>
    void blah(const T& value)
    {
        std::cout << value.hashValue(); // Wenn T hashValue() implementiert, funktionierts, wenn nicht -> Compiler-Fehler
    }
    

    Übrigens: Eine Hashtabelle gibts in C++ schon. Heißt im aktuellen Standard std::tr1::unordered_map und im kommenden std::unordered_map.



  • Würde sich warscheinlich etwas mit std::enable_if bauen lassen, vorausgesetzt ich behalte Recht in der Annahme dass es etwas wie derived_of gibt. 😉



  • Mal schnell zusammengefleddert:

    template<typename T,
             std::size_t assert_hashValue_function = sizeof(static_cast<T*>(0)->hashValue())>
    struct accept_only_hashables {
      ...
    };
    


  • Übrigens: Einer der Gründe, warum man ein hash<> template gemacht hat, war, dass man damit auch built-in Typen unterstützen kann. In deine Hashtabelle kann man keine ints, longs, bools, etc speichern 😉



  • Kann man mit SFINAE machen, daß hash<T> automatisch alle Klassen mit .hashValue() unterstützt?



  • Mathews schrieb:

    die von einer bestimmten Basisklasse erben, da sie bestimmte funktionen bereitstellen müssen.

    Um bestimmte Funktionen bereitzustellen, muss man nicht von einer Klasse erben. Google mal nach "compiletime polymorphism".
    Wenn sie die Funktion besitzen müssen, dann benutze die Funktion einfach, und der Compiler meckert genau dann, wenn dein Templateparameter die Funktion nicht unterstützt.

    Du könntest für eine klarere Fehlermeldung eine Metafunktion schreiben, die prüft, ob es ein entsprechendes Member gibt, aber das brauchst du nicht unbedingt.



  • Hi,

    super, das funktioniert so einfach 🙂
    Die std::unordered_map kenne ich, ich wollte mir mehr so eine Wrapper-Hashtable bauen, die halt mit der Funktion hashValue() arbeitet.
    (Sonst brauche ich wie gesagt jedes mal eine eigene Klasse und ein einfaches
    Hashtable<Key,Value> ist halt imo irgendwie angenehmer zu benutzen.^^)

    Btw: richtig erkannt mit dem java.

    Danke für die Hilfe.



  • volkard schrieb:

    Kann man mit SFINAE machen, daß hash<T> automatisch alle Klassen mit .hashValue() unterstützt?

    Ungetestet:

    template <typename T>
    struct hash<T, std::size_t = sizeof(static_cast<T*>(0)->hashValue())>
    {
        std::size_t operator () (const T& value)
        {
            return value.hashValue();
        }
    }
    


  • Ich halte das Vorgehen für albern. Sinnvollerweise ist ein Hash eine Eigenschaft einer Wertemenge, nicht eines Werttyps. Der Klasse eine Hash-Funktion zu geben macht die Verwendung einer Hashmap zwar etwas bequemer, aber die Art und Weise, auf die diese Bequemlichkeit entsteht, lädt geradezu dazu ein, einen Hash zu verwenden, der auf die Wertemenge überhaupt nicht passt. Auf die Art wird man den durch die Hashmap erwarteten Performancegewinn leicht mehr als los.

    Deswegen hat std::tr1::unordered_map auch einen Template-Parameter, über den man einen sinnvollen Hash übergeben kann.

    Wenn du wirklich der Meinung bist, du solltest den Hash an den Typ ketten (und das solltest du dir ein zweites Mal genauer überlegen), schreib halt stumpf

    template<typename T> struct java_hash : public std::unary_function<T, std::size_t> {
      std::size_t operator()(T const &val) const { return val.hash_value(); }
    };
    
    ...
    
    typedef std::tr1::unordered_map<key, value, java_hash<key> > key_value_javamap;
    
    // bzw.
    
    template<typename key_t, typename value_t>
    struct java_map { typedef std::tr1::unordered_map<key_t, value_t, java_hash<key_t> > type; };
    
    java_map<A, B>::type my_map;
    
    // bzw. mit C++11
    
    template<typename key_t, typename value_t>
    using java_map = std::unordered_map<key_t, value_t, java_hash<key_t>>;
    
    java_map<A, B> my_map;
    

    oder etwas in der Art.

    Was Pis Vorschlag angeht, partielle Spezialisierungen im Namensraum std sind nicht standardkonform. Auch bestünde damit die Gefahr, sich mit anderen Bibliotheken zu beißen, die eine ähnliche Idee hatten.



  • seldon schrieb:

    : public std::unary_function<T, std::size_t>
    

    Soweit ich weiß, ist std::unary_function mit C++11 als deprecated vermerkt.

    seldon schrieb:

    Was Pis Vorschlag angeht, partielle Spezialisierungen im Namensraum std sind nicht standardkonform. Auch bestünde damit die Gefahr, sich mit anderen Bibliotheken zu beißen, die eine ähnliche Idee hatten.

    Ich kann aber meine hash-Klasse als template-Parameter übergeben 😉


  • Mod

    314159265358979 schrieb:

    Ungetestet:

    hm. ja.

    314159265358979 schrieb:

    template <typename T>
    struct hash<T, std::size_t = sizeof(static_cast<T*>(0)->hashValue())>
    {
        std::size_t operator () (const T& value)
        {
            return value.hashValue();
        }
    }
    

    Was soll das überhaupt sein? Eine partielle Spezialisierung mit Defaultparameter??



  • seldon schrieb:

    Ich halte das Vorgehen für albern. Sinnvollerweise ist ein Hash eine Eigenschaft einer Wertemenge, nicht eines Werttyps. Der Klasse eine Hash-Funktion zu geben macht die Verwendung einer Hashmap zwar etwas bequemer, aber die Art und Weise, auf die diese Bequemlichkeit entsteht, lädt geradezu dazu ein, einen Hash zu verwenden, der auf die Wertemenge überhaupt nicht passt. Auf die Art wird man den durch die Hashmap erwarteten Performancegewinn leicht mehr als los.

    Was? 😕 Was soll den eine Wertemenge bzw. ein Werttyp sein? Kannst du das z.B. mal an einem Hash für Strings erklären?



  • ???????... schrieb:

    Was? 😕 Was soll den eine Wertemenge bzw. ein Werttyp sein? Kannst du das z.B. mal an einem Hash für Strings erklären?

    Das kam zwar nicht von mir, aber normalerweise landet nur eine Teilmenge aller technisch möglichen Werte in deiner Hash-Tabelle. Und je nachdem wo die Schwerpunkte liegen, kann eine andere Hashfunktion günstiger sein.



  • Am einfachen Beispiel erklärt:

    Angenommen, ich will double-Werte speichern. Ich entwerfe einen einfachen Hash für den erwarteten Wertebereich, etwa

    std::size_t hash(double x) {
      return static_cast<std::size_t>((x - x_min) / (x_max - x_min) * std::numeric_limits<std::size_t>::max() + .5);
    }
    

    was, wenn ich jetzt keinen Programmierfehler gemacht habe, für gleichverteilte Werte wunderbar funktioniert.

    Jetzt erwarte ich aber beispielsweise normalverteilte oder gar lognormal verteilte Werte (ich mache beispielsweise eine Monte-Carlo-Simulation über Aktienkurse oder etwas in der Art), und es stellt sich ziemlich schnell heraus, dass diese auf den ersten Blick sehr vernünftige Hashfunktion - die für gleichverteilte Wertmengen in der Tat vernünftig wäre - selbst bei sehr geringem Load-Faktor der Hashtable dauernd kollidiert, und als wäre das nicht genug, dass sie dauernd in der selben Gegend kollidiert und sich die Positionen der Werte entsprechend stark verschieben (jetzt mit closed hashing gedacht, das ist hier ja üblich).

    Was man daraus sehen kann, ist, dass es wenig Sinn macht, eine Hashfunktion für double zu definieren und diese überall zu benutzen. Es ist nicht sinnvoll, die Hashfunktion an den Werttyp (im Sinne von Datentyp) zu binden, sondern man muss eine für die erwartete Wertemenge sinnvolle Hashfunktion benutzen. Gleiches gilt für alle anderen Datentypen. Speichere ich beispielsweise Strings, ist bei zufälligen Eingaben eine andere Funktion sinnvoll, als wenn ich die Funktionsnamen einer C-Bibliothek speichere, die als Namensraumersatz alle mit den gleichen n Zeichen anfangen. Speichere ich eine Punktmenge, macht es einen Unterschied, was diese beschreibt.

    Es ist nunmal so, dass Hashing nicht ganz trivial ist. Wenn du einfach hinschreiben willst "mach mir etwas, was x nach y mappt", bist du mit std::map besser dran - da hast du Worst-Case-Garantien. Wenn sich das als Performanceproblem entpuppt, können Hashtables die Lösung sein, aber nur, wenn man sich die Arbeit macht, das Hashing vernünftig hinzukriegen.


Log in to reply