Modernes OOP unter C++



  • asc schrieb:

    ...Es hat z.B. Gründe der Effizienz und Wiederverwertbarkeit das auch ungebundene Funktionen ihre Daseinsberechtigung haben....

    ... oder auch das bessere Design. Ein Sortieralgorithmus braucht z.B. eine umgebende Klasse ebensowenig wie einen GUI-Button.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    asc schrieb:

    ...Es hat z.B. Gründe der Effizienz und Wiederverwertbarkeit das auch ungebundene Funktionen ihre Daseinsberechtigung haben....

    ... oder auch das bessere Design. Ein Sortieralgorithmus braucht z.B. eine umgebende Klasse ebensowenig wie einen GUI-Button.

    Na ja. Mit einer hinreichend aufwendigen Klassenhierarchie könnte man das durchaus sinnvoll implementieren. Z.B. könnte man sich eine abstrakte Basisklasse (oder Interface) namens 'Range' vorstellen, welche die Methode 'Sort' definiert und von der alle sortierbaren Containertypen erben. Das wäre dann konzeptuell gleichmächtig wie die C++-Implenentierung (vorausgesetzt, es gäbe auch die Möglichkeit, nur einen Teilbereich einer Menge zu extrahieren, was ja durchaus denkbar wäre).



  • Konrad Rudolph schrieb:

    Na ja. Mit einer hinreichend aufwendigen Klassenhierarchie könnte man das durchaus sinnvoll implementieren...

    Das wiederspricht aber den Designentwurf von C++ (IMHO auch zurecht).



  • Konrad Rudolph schrieb:

    ...Z.B. könnte man sich eine abstrakte Basisklasse (oder Interface) namens 'Range' vorstellen, welche die Methode 'Sort' definiert und von der alle sortierbaren Containertypen erben. ...

    Was sollte das bringen ?
    Inwieweit profitiert man von einer derart starken Kopplung zwischen Containern und Algorithmen ?
    Ich finde es deutlich sinnvoller (und "sauberer" - was immer man darunter verstehen mag), wenn Container nur ein "Zugriffsinterface" für seine Elemente zur Verfügung stellen, das alle Anwender (und dazu zählt dann auch ein sort) nutzen können. Ein Container hat IMHO genau eine Aufgabe: Elemente verwalten - und mehr sollte er gar nicht können.

    Was passiert, wenn man anfängt, alle Algos in die Klasse zu quetschen (egal ob via Vererbung oder Aggregation), kann man an std::string sehen - und mir gefällt das nicht.

    Gruß,

    Simon2.



  • asc schrieb:

    Konrad Rudolph schrieb:

    Na ja. Mit einer hinreichend aufwendigen Klassenhierarchie könnte man das durchaus sinnvoll implementieren...

    Das wiederspricht aber den Designentwurf von C++ (IMHO auch zurecht).

    asc, ich wollte nicht darauf hinaus, dass man es in C++ anders machen sollte oder dass es zwangsläufig besser wäre. Ich wollte nur darauf hinweisen, dass es sich genauso implementieren lässt. Und was wichtiger ist: in einer (imaginären) geeigneten Sprache wäre es genauso effizient und flexibel. Letztendlich ist das nur Syntax, die Semantik müsste man nicht antasten.

    (Mich interessiert das ganze, weil ich immer noch daran arbeite, eine solche Sprache zu designen.)



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...Z.B. könnte man sich eine abstrakte Basisklasse (oder Interface) namens 'Range' vorstellen, welche die Methode 'Sort' definiert und von der alle sortierbaren Containertypen erben. ...

    Was sollte das bringen ?
    Inwieweit profitiert man von einer derart starken Kopplung zwischen Containern und Algorithmen ?

    Auf mehrere Art und Weise (aber ich möchte nochmal darauf hinweisen: Ich meinte nicht, dass es in C++ schlecht gelöst ist). Besonders dadurch, dass man mit expliziten Type-Constraints arbeiten kann, statt wie in C++ (noch) mit impliziten. Ich denke da an ein Typsystem wie in Haskell. Nun ist es in Haskell so, dass es lediglich freie Funktionen gibt, aber das ist wie gesagt nur Syntax. Ob man nun 'sort myrange' oder 'myrange.sort' schreibt, ist vollkommen irrelevant.

    Übrigens wäre die Kopplung zwischen Container und Algorithmus a priori nicht stärker als in C++. Ich spiele eigentlich nur auf das an, was C++0x mit seinen Concepts auch können wird. Damit wir nicht aneinander vorbeireden, ich stelle mir quasi folgendes vor:

    trait Range[T]
        is Sortable
        # …
    end
    
    class Vector[T] # …
    class List[T] # …
    
    instance Vector of Range # (Standard-Implementierung übernehmen)
    
    instance List of Range
        function Sort(ord as StrictWeakOrdering)
            # Implementiert effiziente Suche auf verketteten Listen
        end
    end
    

    Wichtig ist hierbei, dass 'Range' nichts anderes als ein Wrapper um ein Paar von Iteratoren ist und ein halboffenes Intervall darstellt. Damit ist dieser Code gleichmächtig wie sein Äquivalent in C++.

    Ein weiterer Vorteil dieser Schreibweise ist Vereinheitlichung; man kann dadurch erreichen, dass es nur noch einen Typ von Funktionsaufrufen gibt (wie auch immer die Syntax dafür aussieht). Dass man in den meisten Sprachen syntaktisch zwischen Methoden und freien Funktionen unterscheidet finde ich nicht prickelnd, weil dieser Unterschied semantisch nicht getragen wird.

    Ein großer Vorteil einer solchen Hierarchie ist übrigens, dass die Notwendigkeit des Argument-abhängigen Lookups (Koenig-Lookup) entfällt, ohne an Erweiterbarkeit oder Modularität einzubüßen: Jeder Funktionsaufruf fällt in einen Kontext, der allein durch sein erstes Argument den Definitionsort bestimmt und höchstens in der Vererbungshierarchie hochwandern muss.

    Ich habe unter http://madrat.net/caliph/ mal etwas sehr unvollständiges und teilweise auch veraltetes darüber veröffentlicht. Evtl. ist es ja trotzdem lesenswert (aber ich muss es unbedingt mal aktualisieren).

    Ich finde es deutlich sinnvoller (und "sauberer" - was immer man darunter verstehen mag), wenn Container nur ein "Zugriffsinterface" für seine Elemente zur Verfügung stellen, das alle Anwender (und dazu zählt dann auch ein sort) nutzen können. Ein Container hat IMHO genau eine Aufgabe: Elemente verwalten - und mehr sollte er gar nicht können.

    Ja, aber wo zieht man die Grenze? Das ist IMHO ein sehr gleitender Übergang. 'size' muss eine Memberfunktion sein. 'empty' auch? Nein, denn es kann über 'size' implementiert werden. Trotzdem besitzt jeder Container diese Funktion.

    Was passiert, wenn man anfängt, alle Algos in die Klasse zu quetschen (egal ob via Vererbung oder Aggregation), kann man an std::string sehen - und mir gefällt das nicht.

    Was genau gefällt Dir daran nicht?



  • Vielen Dank für Eure Ratschläge 🙂 Ich werde mir die von Euch vorgeschlagenen Quellen anschauen. Über weitere Quellen werde ich mich sehr freuen.

    Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...Z.B. könnte man sich eine abstrakte Basisklasse (oder Interface) namens 'Range' vorstellen, welche die Methode 'Sort' definiert und von der alle sortierbaren Containertypen erben. ...

    Was sollte das bringen ?
    Inwieweit profitiert man von einer derart starken Kopplung zwischen Containern und Algorithmen ?
    Ich finde es deutlich sinnvoller (und "sauberer" - was immer man darunter verstehen mag), wenn Container nur ein "Zugriffsinterface" für seine Elemente zur Verfügung stellen, das alle Anwender (und dazu zählt dann auch ein sort) nutzen können. Ein Container hat IMHO genau eine Aufgabe: Elemente verwalten - und mehr sollte er gar nicht können.

    Hmm... da bin ich der gleichen Meinung, dass man Algos vom Zugriffsinterface trennen sollte. Mit einem Algo bezeichnet man aber IMO die Implementierung und nicht den Bezeichner. Wenn ein Container/Collection jedoch das Interface (z.B.) "Sortable" bereitstellt, dann ist das eine Eigenschaft! Ich weiß nun, dass dieser Container sortierbar ist. (Was man sich in der STL mühsam zusammenlesen muss).
    Dies hat noch einen weiteren Vorteil: Polymorphie. Ohne das sind moderne Design-Patterns kaum denkbar.
    Fallbeispiel:
    Was interessiert mich denn, ob eine (bestimmte) Methode X eine einfach verkettete Liste, eine doppelt v. Liste oder einen AVL-Baum zurückgibt? Was ich ledeglich brauche ist ein "Sortable" und ein "Iterable". Die Implementierung der Methode X braucht mich nicht zu interessieren. Dammit lassen sich sehr schöne Entwurfsmusster entwickeln 🙂

    mfg

    PS: Nein, das ist kein "Java vs C++" Thread. Bitte last es nicht zu einem solchen ausarten. Hier werden ledeglich Designetscheidungen disskutiert.



  • linu(x)bie schrieb:

    Dies hat noch einen weiteren Vorteil: Polymorphie. Ohne das sind moderne Design-Patterns kaum denkbar.

    Da hast Du die C++-Algorithmen gründlich missverstanden. Die sind nämlich polymorph, um genau zu sein sind sie sogar *wesentlich* weniger gekoppelt als in Java. Nur handelt es sich eben um Compilezeit-Polymorphie, was aber auch in fast allen (> 99%, würde ich schätzen) Fällen vollkommen ausreichend ist. Dass man ein fundamentales Verhalten zur Laufzeit ändert, ist doch schon ein sehr seltener Fall.

    Was interessiert mich denn, ob eine (bestimmte) Methode X eine einfach verkettete Liste, eine doppelt v. Liste oder einen AVL-Baum zurückgibt?

    Eben. Und genau in dieser Hinsicht ist C++ sogar weiter als Java. Java verlangt seinen Algorithmen ab, auf irgendeinen Container zuzugreifen. Die Algorithmen in C++ scheren sich nicht um Container, stattdessen arbeiten alle C++-Algorithmen mit einem beliebigen offenen Intervall [a, b[. Das ist wesentlich allgemeiner.



  • linu(x)bie schrieb:

    Hmm... da bin ich der gleichen Meinung, dass man Algos vom Zugriffsinterface trennen sollte. Mit einem Algo bezeichnet man aber IMO die Implementierung und nicht den Bezeichner. Wenn ein Container/Collection jedoch das Interface (z.B.) "Sortable" bereitstellt, dann ist das eine Eigenschaft! Ich weiß nun, dass dieser Container sortierbar ist. (Was man sich in der STL mühsam zusammenlesen muss).

    Für mich ist etwas wie ein Interface auch eine feste Kopplung. Du bist gezwungen die Implementierung in der Klasse zu machen, die STL entkoppelt dies durch Trennung der Interatoren von den Containern.

    Der Typ des Iterators eines Containers gibt an was damit gemacht werden kann, und erlaubt es auch einen Algorithmus nachträglich durch einen Spezialisierten auszutauschen ohne das Interface des Containers ergänzen zu können. Und sei es durch eine Templatespezialisierung eines vorhandenen Algorithmus.

    Ich kann beide Ansätze gut verstehen, sowohl den Ansatz mit Interfaces (Laufzeitpolymorphie) als auch der Ansatz der STL (Compilezeitpolymorphie). Letzteres dürfte dir aus Java gänzlich unbekannt sein. Beide haben ihren Sinn und beide haben ihre Vor- und Nachteile. Die STL ist z.B. besonders stark Richtung Effizienz ausgelegt, dafür erfolgt die Bindung zur Compilezeit.

    linu(x)bie schrieb:

    Dies hat noch einen weiteren Vorteil: Polymorphie. Ohne das sind moderne Design-Patterns kaum denkbar.

    Hier stößt du auf eine Wissensgrenze wie oben erwähnt. Interfaces, abstrakte Basisklassen, virtuelle Funktionen etc. erlauben Polymorphie. So weit ganz klar. Aber die Generische Programmierung über Templates erlauben auch Polymorphie. Die eine Erfolgt zur Laufzeit die andere zur Compilezeit.

    Nehmen wir mal folgendes an:

    template<typename T>
    void foo(const T& obj)
    {
        obj->foo();
    }
    

    Sofern ein Typ T eine Methode foo besitzt die Konstant ist, lässt sich dies auflösen. In der Laufzeitpolymorphie müsste dazu eine Basisklasse/Interface festgelegt sein, in der Compilezeitpolymorphie muss der Ausdruck auf den entsprechenden Typ anwendbar sein (unabhängig von Klassenhierachien). Sollte letzteres nicht gegeben sein, tritt ein Fehler zur Compilezeit auf.

    Ich nehme deinen Satz mal und Zweckentfremde ihn hier gehässigerweise:

    linu(x)bie schrieb:

    ...Dammit lassen sich sehr schöne Entwurfsmusster entwickeln :)...

    Mit Templates lässt sich noch weit mehr machen (Berechnungen über Typen; Metaprogrammierung...) aber für jetzt sollte dieses kleine Beispiel besser zu dem von dir aufgeworfenen Punkt passen.

    linu(x)bie schrieb:

    PS: Nein, das ist kein "Java vs C++" Thread. Bitte last es nicht zu einem solchen ausarten. Hier werden ledeglich Designetscheidungen disskutiert.

    Ich sehe hier keinen der bislang gegen Java gesprochen hat. Die Designziele sind unterschiedlich und die eine Sprache hat ihre Stärken in dem einen, die andere in dem anderen Anwendungsfall. Nur muss man die Unterschiede klarstellen um sie zu verstehen.

    cu André



  • Dieses Bild sollte deutlich genug sein, um zu zeigen, was das Ziel war, das die Container eben kein Sortable-Interface haben sollten:
    http://www.kharchi.eu/wiki/lib/exe/fetch.php?cache=cache&media=cpp:std:iterator.png

    Ich kann sogar ein primitives C-Array durch std::sort sortieren oder durch std::search durchsuchen lassen. Das C-Arraay kann aber bekanntlich keine Interfaces haben. 😉 Ich finde das ziemlich cool! 🕶

    Achja, was ist wenn ich in einem Container suchen lassen will? Brauche ich dann ein Findable-Interface? Wieviele Interfaces soll ich am Ende auf einem Container implementieren? Wird doch etwas viel, oder?

    Durch die Trennung kann ich eine Fülle an Algorithmen auf (fast) beliebige Container-Typen anwenden. Akademisch gesehen, greifen die Algos ja nicht mal auf Container zu, sondern nur auf Iteratoren. Sollte man beachten, wenn man Haarspalterei betreiben will. 😃



  • Hi Konrad,

    vielen Dank für die ausführliche Antwort. Jetzt weiß ich ein wenig mehr, worauf Du hinauswillst. Teilweise sehe ich das auch so, teilweise empfinde ich doch eine "Vererbungskopplung" als zu stark - aber nun gut.

    Konrad Rudolph schrieb:

    ...
    Das ist IMHO ein sehr gleitender Übergang.
    'size' muss eine Memberfunktion sein.
    'empty' auch? Nein, denn es kann über 'size' implementiert werden.
    Trotzdem besitzt jeder Container diese Funktion.
    ...

    - Stimmt
    - stimmt
    - stimmt
    - stimmt - finde ich aber schade.

    Konrad Rudolph schrieb:

    ...
    Was genau gefällt Dir daran nicht?

    Verschiedenes (z.B. die verschiedenen "char*-Einspränkelungen" - aber da hätte man wohl einiges mehr an der Sprache umbauen müssen) aber besonders, dass es eine (viel zu) "fette Klasse" ist.

    Gruß,

    Simon2.



  • Artchi schrieb:

    Ich kann sogar ein primitives C-Array durch std::sort sortieren oder durch std::search durchsuchen lassen. Das C-Arraay kann aber bekanntlich keine Interfaces haben. 😉

    Eben. Und deswegen ist die in C++ benutzte Lösung für C++ auch die beste. Aber Stroustrup hat selbst verschiedentlich gesagt, dass er C++ ganz anders designed hätte, wenn die C-Altlast nicht wäre. In einer Sprache, die keine Rücksicht auf sowas wie primitive Datentypen nehmen muss, kann man anders herangehen.

    Achja, was ist wenn ich in einem Container suchen lassen will? Brauche ich dann ein Findable-Interface? Wieviele Interfaces soll ich am Ende auf einem Container implementieren? Wird doch etwas viel, oder?

    Was ist „etwas viel“? Was spricht dagegen, wenn ein Container viele Konzepte implementiert? Das ist doch zur Zeit auch schon der Fall (schau Dir mal die SGI-Dokumentation der STL an!), nur geschieht das noch nicht explizit im Code.



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...
    Das ist IMHO ein sehr gleitender Übergang.
    'size' muss eine Memberfunktion sein.
    'empty' auch? Nein, denn es kann über 'size' implementiert werden.
    Trotzdem besitzt jeder Container diese Funktion.
    ...

    - Stimmt
    - stimmt
    - stimmt
    - stimmt - finde ich aber schade.

    ich find's gut. So kann ich zum beispiel in konstanter Zeit überprüfen, ob eine Liste leer ist, wohingegen der size-Aufruf lineare Zeit kosten darf (und es imho bei einer brauchbaren Implementierung auch tun sollte).



  • Konrad Rudolph schrieb:

    Was ist „etwas viel“? Was spricht dagegen, wenn ein Container viele Konzepte implementiert?

    http://www.cplusplus.com/reference/algorithm/ << Das ist die Liste der std::algorythmen - und die verdient durchaus die Bezeichnung "etwas viel" Wenn jeder Container von einer entsprechenden Basisklasse erben wuerde, waere das ein wahrer Vererbungsdschungel. Stell dir mal vor du implementierst selbst einen Container, und parallel dazu einen Algorithmus mit dem du auf beliebigen Containern etwas machen moechtest, was in den Standardalgorithmen noch nicht drin ist. Fuer letzteren musst du alle bereits vorhandenen Containerdefinitionen die du nutzt, abaendern, damit sie von deinem neuen Interface erben, und der Container muss von allem erben was in Betracht kommt fuer eine spaetere Benutzung. (Und wenn er fuer was benutzt wird was du nicht in Betracht gezogen hast viel spass beim neucompilieren des dann geaenderten Containers.
    Da nimmt man doch lieber die Plug&Play-STL die man nach belieben erweitern kann, auch wenns fuer die Vertreter der "OOP-muss-Vererbung-sein!"-Lobby kein OOP ist.



  • pumuckl schrieb:

    Konrad Rudolph schrieb:

    Was ist „etwas viel“? Was spricht dagegen, wenn ein Container viele Konzepte implementiert?

    http://www.cplusplus.com/reference/algorithm/ << Das ist die Liste der std::algorythmen - und die verdient durchaus die Bezeichnung "etwas viel" Wenn jeder Container von einer entsprechenden Basisklasse erben wuerde, waere das ein wahrer Vererbungsdschungel.

    Auch nicht schlimmer als diese Liste. Es ist ja kein Unterschied, ob's freie oder gebundene Funktionen sind.

    Stell dir mal vor du implementierst selbst einen Container, und parallel dazu einen Algorithmus mit dem du auf beliebigen Containern etwas machen moechtest, was in den Standardalgorithmen noch nicht drin ist. Fuer letzteren musst du alle bereits vorhandenen Containerdefinitionen die du nutzt, abaendern, damit sie von deinem neuen Interface erben, und der Container muss von allem erben was in Betracht kommt fuer eine spaetere Benutzung.

    Nein, eben nicht. Sondern Du würdest (wieder in einer imaginären Sprache) folgendes schreiben:

    trait MeinTollerNeuerContainerTrait[T as Printable]
        function MeineTollemethode()
            # Alle Elemente ausgeben:
            for element in this
                Console.WriteLine(element)
        end
    end
    
    trait Range is MeinTollerNeuerContainerTrait
    

    – Man braucht quasi die Möglichkeit, „Mix-Ins“ zu definieren. Mehr nicht.

    Da nimmt man doch lieber die Plug&Play-STL die man nach belieben erweitern kann

    Nochmal: Was mir vorschwebt, ist semantisch dasselbe wie die STL-Algorithmen (wenn man vom Methoden-Lookup-Algorithmus absieht), nur die Syntax ist anders, um die Zugehörigkeit besser widerzuspiegeln.



  • Konrad Rudolph schrieb:

    Nochmal: Was mir vorschwebt, ist semantisch dasselbe wie die STL-Algorithmen (wenn man vom Methoden-Lookup-Algorithmus absieht), nur die Syntax ist anders, um die Zugehörigkeit besser widerzuspiegeln.

    Wobei das Thema "Modernes OOP unter C++" und nicht einer anderen Sprache ist. Der Fragesteller wollte wohl wissen warum die Designentscheidung so und nicht anders in C++ getroffen wurde, als zu wissen wie du es mit anderen Mitteln umsetzen würdest ;p

    cu André



  • asc schrieb:

    Wobei das Thema "Modernes OOP unter C++" und nicht einer anderen Sprache ist. Der Fragesteller wollte wohl wissen warum die Designentscheidung so und nicht anders in C++ getroffen wurde, als zu wissen wie du es mit anderen Mitteln umsetzen würdest ;p

    Stimmt, ich bin abgedriftet. Aber wie ich vor einigen Tagen schon in einem anderen Thread sagte: So ist das nunmal in Diskussionen. Ohne solche Diskussionen wäre das Forum doch wesentlich weniger wert.


  • Mod

    Konrad Rudolph schrieb:

    ...
    Das ist IMHO ein sehr gleitender Übergang.
    'size' muss eine Memberfunktion sein.
    'empty' auch? Nein, denn es kann über 'size' implementiert werden.
    Trotzdem besitzt jeder Container diese Funktion.
    ...

    - ja.
    - Warum? Der gleiche Fehlschluss stört mich auch bei Sutters ansonsten excellenter Analyse von basic_string. Was hat die Wahl des Interfaces mit der Implementierbarkeit der Funktionalität zu tun? Oder bei Sutter - warum sollte es den Anwender interessieren, welcher der verschiedenen Overloads nun der Allgemeinste ist (der kriegt die Membersyntax)
    - nicht unter den Komplexitätsbedingungen, die an empty gestellt werden (list), allerdings ist empty als begin()==end() implementierbar



  • camper schrieb:

    Konrad Rudolph schrieb:

    'size' muss eine Memberfunktion sein.

    - Warum? Der gleiche Fehlschluss stört mich auch bei Sutters ansonsten excellenter Analyse von basic_string. Was hat die Wahl des Interfaces mit der Implementierbarkeit der Funktionalität zu tun? Oder bei Sutter - warum sollte es den Anwender interessieren, welcher der verschiedenen Overloads nun der Allgemeinste ist (der kriegt die Membersyntax)

    So argumentiere ich ja gar nicht. Andererseits: Wie würdest Du es machen? 'size' als Algorithmus implementieren? Denkbar. Aber was wären die Vorteile?

    Mir ging es ja in meiner Argumentation darum, dass ich die Unterscheidung grundsätzlich für ziemlich willkürlich halte. Eine Schnittstelle ist für mich konzeptuell das, was man mit einer Klasse (bzw. Exemplaren davon) machen kann.



  • linu(x)bie schrieb:

    Wenn ein Container/Collection jedoch das Interface (z.B.) "Sortable" bereitstellt, dann ist das eine Eigenschaft!

    Eine Sequenz mit Objekten vom Typ T ist genau dann sortiertbar, wenn T eine Ordnungsrelation (hier operator<) besitzt. Daher ist die Eigenschaft sortierbar keine des Sequenzcontainers, sondern sie ist eine der Elemente.

    Ich weiß nun, dass dieser Container sortierbar ist. (Was man sich in der STL mühsam zusammenlesen muss).

    Das ist überflüssig, das weiß man schon vorher. Die Eigenschaft hängt nur von Deinen Elementen ab und nicht vom Container!

    Beispiel:

    #include <vector>
    #include <algorithm>
    
      class A {
        int i_;
      public:
        A(int const i) : i_(i) {}
      };
    
      class B {
        int i_;
      public:
        B(int const i) : i_(i) {}
        bool less (B const& rhs) const {
          return this->i_ < rhs.i_;
        }
      };
      bool operator< (B const& lhs, B const& rhs) {
        return lhs.less(rhs);
      }
    
    int main () {
      std::vector<A> a;
      std::vector<B> b;
    
      for (size_t i = 100; i != 0; --i) {
        a.push_back(A(i));
        b.push_back(B(i));
      }
    
      std::sort (b.begin(), b.end());
      // std::sort (a.begin(), a.end());
    }
    

    std::vector<B> ist sortierbar, std::vector<A> ist es nicht, es hängt also gar nicht vom Container ab, sondern von T. Wenn t.operator< oder operator<(T,T) existiert, dann ist auch jeder Sequenzcontainer sortierbar.


Anmelden zum Antworten