Template Vererbung



  • Ü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.


Anmelden zum Antworten