generics & templates



  • Hallo
    ich hab mich grad eben gefragt wieso es keine Sprache gibt ( oder korregiert mich da ), die Templates und Generics unterstützt.
    Templates und Generics sind ja zwei unterschiedliche Dinge. Templates sind doch eher Macros, in die der Compiler einen Typ einsetzt.
    Beispiel:

    template<class T> class Aaaaa { };
    
    {
        Aaaaa<int> aaaa;
    }
    

    hier ersetzt doch nur der Compiler T durch int.
    Generics funktionieren ja völlig anders. Hier wird nicht einfach ersetzt, sondern es wird noch auf den Typ überprüft.

    class Aaaaaa<T> : where T : IComparable {}
    
    {
        Aaaaaa<Int32> aaaaaa;
    }
    

    Hier überprüft ja der Compiler noch ob Int32 das Interface IComparable implementiert.

    Wieso gibt es zB in Java oder C# nicht beides ? Ich vermiße halt manchmal Templates aus C++, vor allem bei Kontainer Klassen für primitiven Typen, oder ich will in C# Strucs als Generic-Parameter übergeben, die keine gemeinsamkeiten haben. Ob das Sinn hat ist eine andere Frage. Ich will es einfach können. Außerdem kann man mit Generics ja keine Metaprogrammierung machen. Also sowas cooles:

    template<int N>
    class Factorial {
    public:
        enum { value = N * Factorial<N-1>::value };
    };
    
    class Factorial<1> {
    public:
        enum { value = 1 };
    };
    

    Naja und deswegen die Frage: Wieso nicht? Wieso beherscht Java, C#, C++ endwerder Templates oder Generics, wieso nicht beides ?



  • DEvent schrieb:

    Hallo
    ich hab mich grad eben gefragt wieso es keine Sprache gibt ( oder korregiert mich da ), die Templates und Generics unterstützt.

    In C++/CLI gibt es beides.

    Templates und Generics sind ja zwei unterschiedliche Dinge. Templates sind doch eher Macros, in die der Compiler einen Typ einsetzt.
    Beispiel:

    template<class T> class Aaaaa { };
    
    {
        Aaaaa<int> aaaa;
    }
    

    hier ersetzt doch nur der Compiler T durch int.

    Das ist richtig. 🙂

    Generics funktionieren ja völlig anders. Hier wird nicht einfach ersetzt, sondern es wird noch auf den Typ überprüft.

    class Aaaaaa<T> : where T : IComparable {}
    
    {
        Aaaaaa<Int32> aaaaaa;
    }
    

    Hier überprüft ja der Compiler noch ob Int32 das Interface IComparable implementiert.

    Naja gut, das sind Constraints und diese können anhand des Typs festgemacht werden. Bei C++ Templates werden Constraints anhand der Schnittstelle und vor allem über die Dokumentation festgemacht. Es ist aber nicht so, dass es nicht theoretisch Templates geben könnte, die auch Constraints über den Typen formulieren lassen, es ist halt nur in C++ nicht so gemacht. Das ist also nicht der Unterschied zwischen Templates und Generics.

    Wieso gibt es zB in Java oder C# nicht beides ? Ich vermiße halt manchmal Templates aus C++, vor allem bei Kontainer Klassen für primitiven Typen, oder ich will in C# Strucs als Generic-Parameter übergeben, die keine gemeinsamkeiten haben. Ob das Sinn hat ist eine andere Frage. Ich will es einfach können.

    Bei Java kommen technische Einschränkungen zum Tragen. Was genau für Probleme hast du denn jetzt in C#? Du kannst dort theoretisch jeden Typparameter verwenden. Constraints machst du ja nur, um bestimmte Methoden von T nutzen zu können. Das ist bei C++ Templates nicht anders, nur dass dies dort ausprobieren herausgefunden wird (siehe oben).

    Naja und deswegen die Frage: Wieso nicht? Wieso beherscht Java, C#, C++ endwerder Templates oder Generics, wieso nicht beides ?

    Hmmmmm keine Ahnung, ich würde mir das für C# gar nicht wünschen. Es macht die Sprache sehr viel komplexer, Templates im C++-Stil sind hässlicher zu handhaben und bieten nur ganz am Rande ein paar Dinge, die mich noch interessieren könnten. Dafür kommen sie mit allerhand Nachteilen daher. Ich kann ein C#-Template dann nicht in VB verwenden, Vererbung beisst sich in C++ auch immer ein bisschen mit Templates und sie sind sowieso generell weniger flexibel. Vor allem in Java sind Generics durch die 4 Varianzarten ein sehr gutes Stück flexibler.
    Man hat offensichtlich entschieden, dass die Vorteile die Nachteile nicht aufwiegen und daher einen solchen Mechanismus nicht eingebaut.



  • DEvent schrieb:

    Naja und deswegen die Frage: Wieso nicht? Wieso beherscht Java, C#, C++ endwerder Templates oder Generics, wieso nicht beides ?

    Im Fall von Java ist das teilweise wegen Kompatibilitätsgründen der Fall und teilweise wegen der Java-Mentaliät ansich.

    1. Primitive Typen werden sicherlich wegen Kompatibilitätsgründen nicht in den Generics von Java unterstützt. Das hätte zu große Veränderungen an den JVMs erfordert und zudem möglicherweise alten Code inkompatibel gemacht. Es gibt JVMs von diversen Herstellern und da wird es nicht so ganz leicht sein, denen zu erklären, warum sie die komplett umschreiben müssen. Da muss schon ein enormer Vorteil mit verbunden sein, der da wohl nicht festgestellt wurde. Ich hätte Generics in Java allerdings auch viel lieber mit primitiven Typen. Falls es dir allerdings in erster Linie nur um entsprechende Containerklassen geht, könnte dir auch schon folgender Link weiterhelfen:

    http://pcj.sourceforge.net/

    Mag aber sein, dass es da aktuellere Bibliotheken mit ähnlichem Inhalt gibt.

    2. Metaprogramming verträgt sich nicht so ganz mit Java. Das ist eine Sache, die du in Java im Prinzip erst zur Laufzeit auflösen könntest und das würde IMHO einfach zu viel Zeit brauchen. Ok, ich weiß nicht, welche Implementierungsstrategien es da genau gibt. Vielleicht gibt es da ja auch sehr intelligente Lösungen. Abgesehen davon ist das ein Sprachmittel, das sicherlich in gewissen Bereichen enorme Vorteile bringt, aber für die meisten Anwendungsgebiete braucht man es nicht wirklich. Insofern steht es auch deutlich gegen die Java-Philosophie, die Sprache halbwegs einfach zu halten, da es doch ein recht komplexes Sprachmittel ist, das zudem nur begrenzten Nutzen hat. Das verhält sich im Prinzip ähnlich, wie das Operator Overloading. Das will man aus ähnlichen Gründen nicht in Java haben.

    /me könnte in Java sehr gut Generics mit primitiven Typen gebrauchen und ist sehr enttäuscht, dass man sich dagegen entschieden hat. 😞

    ...aber ich mache mit Java auch Sachen, die jeder normal denkende Mensch eher mit C++ machen würde. 😃



  • Optimizer schrieb:

    Vererbung beisst sich in C++ auch immer ein bisschen mit Templates und sie sind sowieso generell weniger flexibel. Vor allem in Java sind Generics durch die 4 Varianzarten ein sehr gutes Stück flexibler.

    Kannst Du die Punkte ein bißchen genauer erläutern?

    Insbesondere zum ersten Punkt: Alexandrescu hat doch gezeigt, daß sich beide sehr gut ergänzen, oder?



  • - -



  • Mööööööp? 😡

    cu 😃



  • Jester schrieb:

    Optimizer schrieb:

    Vererbung beisst sich in C++ auch immer ein bisschen mit Templates und sie sind sowieso generell weniger flexibel. Vor allem in Java sind Generics durch die 4 Varianzarten ein sehr gutes Stück flexibler.

    Kannst Du die Punkte ein bißchen genauer erläutern?

    Insbesondere zum ersten Punkt: Alexandrescu hat doch gezeigt, daß sich beide sehr gut ergänzen, oder?

    Dass sich beide ergänzen kann ich durchaus glauben. Wenn man jetzt im Fall von Java oder Standard C++ aber nur eines der beiden hat, sind Generics so wie in Java allerdings in einigen Punkten flexibler. Ich habe ein paar Beispiele daheim, die kann ich am Abend mal posten.



  • Man könnte beispielsweise eine solche Funktion konstruieren:

    private static <T> double calcSumOfElements(Collection<? extends Number>... array)
    	{
    		double sum = 0;
    
    		for( Collection<? extends Number> list : array )
    			for( Number x : list )
    				sum += x.doubleValue();
    
    		return sum;
    	}
    

    Wir haben hier schon eine ziemlich flexible Konstruktion, die beispielsweise eine ArrayList<Integer>, LinkedList<Double> und ein HashSet<Float> als Parameter übernehmen kann und aus allen Elementen die Summe bildet.

    double sum = calcSumOfElements(numberList, floatSet, doubleList)
    

    Oder ich kann eine Funktion schreiben, die diese drei Parameter nimmt und eine ArrayList<Number> mit allen Elementen in einer Datenstruktur liefert. Das alles bei statischer Typsicherheit. Ich finde das schon ziemlich beeindruckend und wenn ich mich nicht völlig täusche ist so etwas mit Templates nicht ohne weiteres zu realisieren.

    Es lassen sich weitere beliebig kranke Beispiele konstruieren und wenn ich ehrlich bin, braucht man sowas natürlich nicht jeden Tag. Aber manchmal ist so etwas cool und kann Code-Duplizierung ersparen - im Sourcecode und auch im resultierenden Binary.



  • Das Ergänzen sich bezog sich eigentlich mehr auf C++-templates und Vererbung. Du meintest sie beißen sich. Imho tun sie das nicht, sie ergänzen sich sogar prima.

    Alexandrescus Factory-Implementierung ist zum Beispiel ziemlich nett.

    Das extends ist natürlich nice-to-have, kann man mit nem STATIC_ASSERT aber auch in C++ haben. Dadurch, daß in C++ die Container keine gemeinsame Basisklasse haben kann man sie natürlich auch nicht in der Form austauschen. Dort wird halt traditionell alles über Ranges gemacht: begin, end.



  • STATIC_ASSERT wird zur compile-time ausgewertet, oder?
    Wie kannst du damit so etwas ermöglichen?

    List<? extends Number> = (userInput)  ?  new ArrayList<Integer>() : new LinkedList<Double>();
    calcSumOfElements(...);  // Wie instanziert ein Template jetzt calcSumOfElements?
    

    Außerdem habe ich drei komplett verschiedene Collections auch noch mit verschiedenen Elementypen gleichzeitig bei einem Aufruf übergeben, in einem Array Collection<? extends Number>[] (das macht dieses ...). Wenn du zur Compilezeit nicht mehr weißt, was genau in diesem Array ist, kannst du das doch ziemlich vergessen, dass hier noch ein Template instanziiert werden kann, oder? 😕
    Du kennst den Typ der Collections zur Compilezeit nicht, also weißt du nicht ob std::list<T>::const_iterator oder std::vector::const_iterator und T kennst du auch nicht. Es ist aber auch nicht egal, weil ein std::list<Foo>::iterator nicht das selbe ist wie ein std::list<Bar>::iterator. Bei den Generics in Java kann ich einen Iterator<?> (kann LinkedListIterator oder ArrayListIterator dann sein) verwenden. Sowas ist halt flexibel. Ich kann durch eine Liste iterieren, ohne den Elementtyp zu kennen, damit ist meine Methode gleich viel vielseitiger.
    Oder wenn ich eine Methode sort() habe, kann die eine List<T> nehmen und einen Comparator<? super T>. Andernfalls müsste ich für T einen eigenen Comparator schreiben, obwohl es der für die Basisklasse vielleicht genauso tut. Wenn ich eine List<BMW> habe, kann ich zum vergleichen der Preise dann trotzdem einen CostComparator<Auto> übergeben. Bei Templates ist das alles starr. 😞

    Ich nehm aber das mit der Vererbung wieder zurück, weil ich nicht mehr weiß, was ich da im Hinterkopf hatte. Ich meinte gehört zu haben, dass es mit den virtuellen Methoden zusammen hängt, aber ich weiß es nicht. Kann auch falsch sein. 🙂



  • Optimizer schrieb:

    Ich nehm aber das mit der Vererbung wieder zurück, weil ich nicht mehr weiß, was ich da im Hinterkopf hatte. Ich meinte gehört zu haben, dass es mit den virtuellen Methoden zusammen hängt, aber ich weiß es nicht. Kann auch falsch sein. 🙂

    Vielleicht, daß Member-Templates nicht virtual sein können?
    Aber das wäre wohl recht krass. Man hätte dann sozusagen eine Familie von virtuellen Funktionen und das ist vermutlich nicht im Sinne des Erfinders.

    Die Sache mit den Iteratoren verstehe ich nicht ganz.

    Ich kann doch (wie es in der STL auch gemacht ist) ein

    template<typename IteratorType> DoSomething(IteratorType begin, IteratorType end)
    {
    // ...
    }

    bauen. Damit ist wieder egal welcher Container da liegt. Es wird halt das richtige template instanziiert. An das T komme ich dann wieder dran, indem ich mir die typedefs im Iterator anschaue. Das ist zwar nicht so schön wie der explizite Constraint, erfüllt aber doch letztlich die selbe Funktionalität.

    Daß das mit der Liste ... so nicht geht ist auch klar. Wenn man aber ne gemeinsame Basisklasse der Container hätte und lauter Pointer übergeben würde wäre sogar das möglich, denke ich.
    Aber von diesen Variable-Length-Geschichten ist man in C++ ja eher weggekommen. Typsicher sind sie dort nicht.



  • Wenn man aber ne gemeinsame Basisklasse der Container hätte und lauter Pointer übergeben würde wäre sogar das möglich, denke ich.

    Aber du hättest doch dann immer noch das Problem, dass in dem Array einmal ein List<Foo> und einmal ein List<Bar> sein kann. Selbst wenn jetzt der Typ des Containers selber keine Rolle spielt weil du das irgendwie schaffst, mit nem Basisklassen-Iterator zu arbeiten, ist bei Templates ein iterator<Foo> ja immer noch was anderes als ein iterator<Bar>. Und zur Compilezeit weißt du es halt noch nicht (was ja der Sinn von <? extends T>, <? super T> und <?> ist).



  • Wenn ich Pointer halte kann ich die doch ineinander umwandeln, wenn die Klassen geerbt haben.

    ein shared_ptr<Dervied> läßt sich implizit in einen shared_ptr<Base> konvertieren. Warum sollte ich das nicht auch bei den Iteratoren hinkriegen?

    Ich halte das eigentlich schon für machbar. Der Code wird allerdings wahrscheinlich nicht so einfach wie der Java-Code (insbesondere wenn man nicht so viel Erfahrung damit hat).


Anmelden zum Antworten