generics & templates
-
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:
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).