Array übergeben zu Konstruktor


  • Mod

    @wob sagte in Array übergeben zu Konstruktor:

    @SeppJ sagte in Array übergeben zu Konstruktor:

    Ihr beide klingt so, als wäre generischer Code für euch kein riesengroßer Vorteil, der jedwede andere Überlegung hinfällig macht.

    Du klingst so, als ob man ohne generischen Code nicht anfangen könnte, Programmieren zu lernen.

    Nee, ist schon Ok, das man das nicht als erstes lernt. Aber wenn man's einem Anfänger vormacht, dann bitte nicht falsch, oder wenigstens mit einem großen Exclaimer dass das nur zu Lernzwecken dient. Code, der in C++ eine explizite Containervariante vorschreibt um eine Menge von Daten zu übergeben, ist einfach falsch. Das muss so auch gesagt werden und jedem bewusst sein.

    Es hängt übrigens auch sehr stark vom Umfeld ab, in dem du arbeitest. Wenn das alles Informatiker sind, ist es was anderes als wenn es Leute aus anderen Bereichen sind, die aber programmieren müssen. Und da zählt das Argument "einfacher verständlich" mehr als "generisch". (das ist auch ein wesentlicher Grund, warum ich inzwischen zu 90% Python schreibe).

    Aber gerade in Python wird doch überall Generik praktiziert und es eine der großen Stärken der Sprache, dass es dort so einfach ist! Man muss ja nicht einmal darüber nachdenken, es ist der Default, dass alles komplett generisch ist. Um so schlimmer (und ein bekanntes Antipattern!) ist in Python Code, der irgendwelche Typprüfungen macht. Jeder Best-Practices-Guide für Python sagt, möglichst niemals Konstrukte wie if isinstance(variable, list) zu nutzen. Stell dir mal vor, sum würde nur mit list funktionieren. Würde niemals jemand so schreiben!



  • Du meinst falsch im gleichen Sinne wie falsch, wenn eine Funktion einen std::vector zurückgibt, anstatt einen Output Iterator zu erwarten, der beschrieben wird?
    Sorry, das ist lächerlich. Ne, nichtmal sorry, das ist einfach nur lächerlich.


  • Mod

    @DocShoe sagte in Array übergeben zu Konstruktor:

    Du meinst falsch im gleichen Sinne wie falsch, wenn eine Funktion einen std::vector zurückgibt, anstatt einen Output Iterator zu erwarten, der beschrieben wird.
    Sorry, das ist lächerlich.

    Ja, dann ist die STL eben lächerlich. Alles lächerliche Theoretiker. Verstanden.



  • Die STL ist eine wiederverwendbare Bibliothek, eine Filmdatenbank ist ein konkretes Projekt mit konkreten Datentypen.


  • Mod

    @DocShoe sagte in Array übergeben zu Konstruktor:

    Die STL ist eine wiederverwendbare Bibliothek, eine Filmdatenbank ist ein konkretes Projekt mit konkreten Datentypen.

    Wie ich sagte, du hast nicht einmal verstanden, was dein Problem ist, obwohl es dir erklärt wurde.

    @SeppJ sagte in Array übergeben zu Konstruktor:

    @SeppJ sagte in Array übergeben zu Konstruktor:

    Man denkt sich zwar immer "Ich habe von generischer Software gehört, aber ich werde niemals einen zweiten Karumbulflator schreiben oder benutzen, wieso sollte ich das generisch machen?" […] Wahrscheinlich merkt man noch nicht einmal, dass da eigentlich Wiederbenutzbarkeitspotential war, weil man es nicht gewöhnt ist, wiederbenutzbar zu programmieren […] Und dann klopft man sich auf die Schulter, dass man nicht auf diesen weltfremden Informatikerquatsch aus deren Elfenbeiturm angewiesen ist, den man ja in der Praxis nie braucht.

    Fällt dir was auf?



  • Lassen wir das, das führt zu nichts.


  • Mod

    @DocShoe sagte in Array übergeben zu Konstruktor:

    Lassen wir das, das führt zu nichts.

    Bei dir nicht, aber andere können vielleicht was lernen.

    @Andere: Was DocShoe hier beispielsweise nicht sieht, ist, dass seine ach so konkrete Filmdatenbank mit ihren ach so konkreten Typen, eine Spezialisierung eines Datenbankkonzepts ist. Man könnte hier eine generische Datenbank haben und dann als Oberfläche eine Zeile Code, die aus der Datenbank eine Filmdatenbank macht. Aber das ist weltfremder Informatikerquatsch. Niemand auf der Welt hätte jemals Anwendung für eine generische Datenbank. Würde in der Ecke verstauben und niemals wieder benutzt werden. Jede Datenbank wird immer speziell für jeden Anwendungsfall neu entwickelt. Von Profipraxisprogrammierern, die sich nicht von theoretischem Firlefanz beeindrucken lassen.



  • @SeppJ Ich muss mich ja doch etwas über den unprofessionellen Ton wundern, den Du hier an den Tag legst - gerade mit dem Moderator-Hintergrund, den Du zu haben scheinst.

    Solch Diskussionen sollten konstruktiver Natur sein - dabei darf man auch anderer Meinung sein - aber findest Du nicht, dass deine Formulierungen teilweise etwas zu weit gehen?

    Btw.: Das Thema an sich wurde auch ganz schön aufgebläht - die ursprüngliche Frage war recht einfach - mittlerweile gibt es dazu so viele Gedankengänge in diesem Thread - teilweise auch außerhalb des professionellen Rahmens - dass es anfängt peinlich zu werden.
    Gerade als Moderator sollte man versuchen eine Diskussion in professionelle Bahnen zu lenken.



  • Irgendwie kommen wir vom Thema ab.

    Die Ursrpüngliche Aussage war, dass man nie Container als Übergabe Paramter verwenden soll. Losgelöst von der Generik.

    Möglich wäre ja auch sowas, was durchaus generisch wieder verwendbar wäre, aber nach der Aussage auch abzulehnen sei:

    template<typename Range>
    void sort(Range&& r)
    {
      std::sort(std::begin(r), std::end(r));
    }
    

    (ganz locker an Boost.range angelehnt). Oder meinst du @SeppJ das mit

    oder meinetwegen auch ein Templateparameter aus dem der Code sich dann selber eine Iteration macht

    Aber, ich glaube auch, dass es sinnvolle Anwendungen für spezialisierte Container Übergaben gibt. QT nutzt das z.B. auch als Möglichkeit um Items an eine ListView zu übergeben.



  • @Schlangenmensch sagte in Array übergeben zu Konstruktor:

    Aber, wenn ich wirklich Daten einer unbekannten Anzahl weiter geben will / muss, was spricht dagegen die in einem Vektor weiter zu geben?

    Eigentlich ist das unnötig, da man das Design so wählen kann, dass das nicht notwendig ist. Denn woher sollen die flexiblen Mengen an Parametern kommen? IO? Wenn IO dann kann man gleich das Objekt korrekt befüllen.

    Bei std::initializer_list muss die Länge der Liste ja auch zur Compilezeit feststehen, ist also, wenn die Länge erst zur Laufzeit feststeht, nicht geeignet.

    Dafür kann man die Objekte direkt mit Listen von Parametern befüllen.

    Days Weekdays = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"};
    

  • Mod

    @Schlangenmensch sagte in Array übergeben zu Konstruktor:

    Möglich wäre ja auch sowas, was durchaus generisch wieder verwendbar wäre, aber nach der Aussage auch abzulehnen sei:

    template<typename Range>
    void sort(Range&& r)
    {
      std::sort(std::begin(r), std::end(r));
    }
    

    (ganz locker an Boost.range angelehnt). Oder meinst du @SeppJ das mit

    oder meinetwegen auch ein Templateparameter aus dem der Code sich dann selber eine Iteration macht

    So sollte man das machen, und so ist das von mir gemeint. Oder halt meinetwegen mit Iteratoren, wenn man auf älteren Sprachstandards unterwegs ist. Diese beiden Möglichkeiten muss jeder wissen, dass das die Methode ist, wie man containerartige Datenmengen herumreicht. Ausnahmen gibt es nur, wenn man selber containerartige Strukturen schreibt, da ist klar, dass die natürlich auch sich selber benutzen können. Und std::string, mathematische Vektoren und ähnliches, weil die eine spezielle Semantik haben, die über "Folge von Zeichen/Zahlen" hinaus geht.



  • @SeppJ sagte in Array übergeben zu Konstruktor:

    Code, der in C++ eine explizite Containervariante vorschreibt um eine Menge von Daten zu übergeben, ist einfach falsch. Das muss so auch gesagt werden und jedem bewusst sein.

    Das ist einfach nicht wahr.

    Der Code ist vielleicht nicht idiomatisch. Aber falsch noch lange nicht.

    Außerdem: man hat ja oftmals auch Code, der unterschiedliche Performacecharakteristika hat je nach Container bzw. bei dem für jeden Container ein anderer Algorithmus besser ist - d.h. ich muss eh wissen, ob ich da eine Liste oder einen Vector habe. (Gut, du wirst jetzt sagen, ich muss wissen, ob ich einen RadomAccessIterator oder einen ForwardIterator habe) Alles schön und gut. Aber mit einem konkreten Objekt arbeitet es sich nun einfach mal leichter. Und nicht jeder Code muss generisch sein, finde ich.

    Um so schlimmer (und ein bekanntes Antipattern!) ist in Python Code, der irgendwelche Typprüfungen macht. Jeder Best-Practices-Guide für Python sagt, möglichst niemals Konstrukte wie if isinstance(variable, list) zu nutzen.

    Und auch hier muss ich widersprechen. Ich habe solchen Code nämlich. Der geht eine Datenstruktur beim Serialisieren rekursiv durch - und der testet sehr wohl direkt auf "list" (und noch ein paar andere Typen, die alle gesondert behandelt werden). Und ich habe auch diverse Funktionen, die 1 Arg oder einen Container zulassen - auch da muss ich explizit checken - und zwar auf "not isinstance(obj, str)", weil Strings nämlich auch iterable sind (und ich eben den Fall 1 String oder mehrere Strings zulassen will)


  • Mod

    @wob sagte in Array übergeben zu Konstruktor:

    Der Code ist vielleicht nicht idiomatisch. Aber falsch noch lange nicht.

    Es ist halt falsch im Sinne von dass C mit cout auch falsches C++ ist. Es gibt keinen Grund es nicht richtig zu machen, das richtige hat nur Vorteile, daher ist es falsch, es nicht richtig zu machen.

    Gut, du wirst jetzt sagen, ich muss wissen, ob ich einen RadomAccessIterator oder einen ForwardIterator habe

    Ja? Ist das so schlimm? Du brauchst ja nix dafür zu tun. Du schreibst doch einfach nur Code als ob's ein Vector wäre und entweder kann das Ding operator[] oder eben nicht. Da freuen sich dann die deque- und string-Nutzer und für alle anderen bleibt alles gleich.

    @wob sagte in Array übergeben zu Konstruktor:

    Um so schlimmer (und ein bekanntes Antipattern!) ist in Python Code, der irgendwelche Typprüfungen macht. Jeder Best-Practices-Guide für Python sagt, möglichst niemals Konstrukte wie if isinstance(variable, list) zu nutzen.

    Und auch hier muss ich widersprechen. Ich habe solchen Code nämlich. Der geht eine Datenstruktur beim Serialisieren rekursiv durch - und der testet sehr wohl direkt auf "list" (und noch ein paar andere Typen, die alle gesondert behandelt werden). Und ich habe auch diverse Funktionen, die 1 Arg oder einen Container zulassen - auch da muss ich explizit checken - und zwar auf "not isinstance(obj, str)", weil Strings nämlich auch iterable sind (und ich eben den Fall 1 String oder mehrere Strings zulassen will)

    Der String ist eine wohl seltene Ausnahme, die ich auch abundzu benutze, aus genau dem gleichen Grund. Aber dir ist schon bewusst, dass das Gegenteil (zu prüfen, ob es mehrere Strings sind, indem man auf list prüft) ein krasses Antipattern ist? und dass dir das auch jeder so um die Ohren hauen würde? Wieso dann in C++ schlechter machen?



  • @PadMad sagte in Array übergeben zu Konstruktor:

    Und ich persönlich sehe hier optisch durchaus einen Unterschied, was die Anzahl der Elemente betrifft... vielleicht habe ich mich aber auch verzählt...
    std::array<std::string, 7> _weekDays = {"Mo","Di","Mi","Do","Fr","Sa","So"};
    std::vectorstd::string _weekDaysAndMore = {"Mo","Di","Mi","Do","Fr","Sa","So","1","42","0xfff","g"};

    Du initialisierst doch hier den vector mit Absicht falsch 🙄

    Wie ich schon oben sagte: wenn ich zu blöd bin, den vector richtig zu initialisieren, dann schützt mich auch niemand vor:

    std::array<std::string, 9> _weekDays = {"Mo","Di","Mi","Do","Fr","Sa","So"};
    

    @PadMad sagte in Array übergeben zu Konstruktor:

    Aber was möchtest Du mir jetzt damit sagen? Dass, wenn Jemand von einem c-array redet, man durchaus den Vorschlag machen darf, dass std::vector eine gute Alternative ist, std::array aber nicht?

    Das will ich (natürlich) nicht damit sagen.
    Ich bin lediglich der Meinung, dass der vector nicht schlechter ist als das std::array, er aber leichter handhabbar ist.
    Und dass die Tatsache, dass der TE nach (c-)array gefragt hat, nicht zwangsläufig zu std::array führen muss ... Du hattest ja argumentiert:

    @PadMad sagte in Array übergeben zu Konstruktor:

    Weil letzten Endes hat @Johnny01 gefragt wie man ein "array" an einen Konstruktor übergeben kann.



  • Da ich mit Templates normalerweise nichts mache, habe ich da mal ne ganz konkrete Frage:

    Ich habe Objekte vom Typ T in einem Container, mit denen eine Funktion was machen soll.
    Damit die Funktion nicht wissen muss, welchen Typ der Container hat, übergebe ich ihr lediglich Iteratoren:

    template<typename iterator>
    void func(iterator begin, iterator end)
    {
      // ...
    }
    

    so weit, so gut. Kann (sollte ich überhaupt) ich meine Funktion so gestalten (Spezialisierung?), dass sie nur mit Containern arbeitet, die auch meinen Typ T beinhalten und wie würde das aussehen?

    Oder macht man das gar nicht, weil die Funktion sich nicht übersetzen lässt, wenn eine Operation auf dem tatsächlich im Container übergebenen Typ T nicht durchgeführt werden kann?



  • @Belli sagte in Array übergeben zu Konstruktor:

    Oder macht man das gar nicht, weil die Funktion sich nicht übersetzen lässt, wenn eine Operation auf dem tatsächlich im Container übergebenen Typ T nicht durchgeführt werden kann?

    Das. Bzw. es lässt sich nicht machen, weil der Compiler Fehler meldet. Nach Möglichkeit sollte man aber entsprechende STL-Funktionen benutzen und nur die Operation bereitstellen (std::accumulate, std::for_each, std::transform und Konsorten).


  • Mod

    @Belli sagte in Array übergeben zu Konstruktor:

    Da ich mit Templates normalerweise nichts mache, habe ich da mal ne ganz konkrete Frage:

    Ich habe Objekte vom Typ T in einem Container, mit denen eine Funktion was machen soll.
    Damit die Funktion nicht wissen muss, welchen Typ der Container hat, übergebe ich ihr lediglich Iteratoren:

    template<typename iterator>
    void func(iterator begin, iterator end)
    {
      // ...
    }
    

    so weit, so gut. Kann (sollte ich überhaupt) ich meine Funktion so gestalten (Spezialisierung?), dass sie nur mit Containern arbeitet, die auch meinen Typ T beinhalten und wie würde das aussehen?

    Oder macht man das gar nicht, weil die Funktion sich nicht übersetzen lässt, wenn eine Operation auf dem tatsächlich im Container übergebenen Typ T nicht durchgeführt werden kann?

    Das kommt drauf an. Ist halt schwierig, konkrete Designfragen zu einer Funktion func mit Typ Tzu beantworten 🙂
    So wies es aussieht, spielt die Natur von T hier aber gar keine Rolle, denn es kommt in deinem Beispiel ja gar nicht vor. Oder soll das eine Memberfunktion von T sein?

    Wenn das eine freie Funktion ist, dann schaut es mir so aus, als solle sie völlig unabhängig von T sein.

    Wenn das eine Memberfunktion ist, kommt es ein bisschen mehr auf Semantik an, und wir können die Frage nicht klar beantworten. Mir fällt jetzt aber auch kein gutes Beispiel ein, wo so etwas unbedingt ein Iterator<T> sein müsste. Aber es gibt bestimmt irgendwas. Vielleicht irgendwelche Algebraoperationen, wo man "Mischung" von bestimmten Objekttypen vermeiden will? Wobei Algebra aber auch ein gutes Beispiel für die Vorteile von generischen Funktionen sind, denn vieles ist ja immer die gleiche Operation, aber mit anderen Objekttypen.



  • Ich hab mich wohl missverständlich ausgedrückt ...
    Ich habe kein konkretes Beispiel/Problem, es war eine theoretische Frage:
    Mein T kommt doch vor, oder bin ich völlig auf dem Holzweg ...
    Wenn ich innerhalb der Funktion sowas wie:

    while(begin != end)
    {
       cout << *begin;
       // ...
    }
    

    mache, dann ist doch *begin vom Typ T?!
    Und vielleicht macht meine Funktion irgendwas, was nur für T Sinn ergibt, und will lediglich dafür offen sein, dass die Objekte in einem beliebigen Container gehalten werden.
    Würde man dann sicherzustellen versuchen, dass keine Iteratoren für Container mit Objekten, die nicht T sind, übergeben werden können?
    Sagen wir mal, die Funktion soll für alle Elemente die Quadratwurzel ermitteln ... dann geht das nicht, wenn die Objekte vom Typ string sind.
    Überlässt man das dann sozusagen sich selbst weil die Funktion mit T = string ja nicht übersetzt werden kann?
    Oder könnte es auch Konstellationen geben, die übersetzt werden können, aber ein unerwünschtes Ergebnis haben ...
    Vielleicht ... addiere zwei aufeinanderfolgende Elemente: *begin + *(begin + 1), *(begin + 2) + *(begin + 3), ...
    Das würde zB für int, double funktionieren, aber auch für string und vielleicht für ein paar selbstdefinierten Typen.

    Wenn das nun für einige aber keinen Sinn ergäbe, würde man dann den Aufruf mit diesen Typen verhindern wollen, oder sich bei Definition dieses Templates nicht darum kümmern?

    Also sozusagen generisch bzgl. des Containertypen, aber festgelegt auf einen bestimmten Typ in dem Container ... oder ist das alles völliger Blödsinn?



  • @SeppJ sagte in Array übergeben zu Konstruktor:

    So sollte man das machen, und so ist das von mir gemeint. Oder halt meinetwegen mit Iteratoren, wenn man auf älteren Sprachstandards unterwegs ist. Diese beiden Möglichkeiten muss jeder wissen, dass das die Methode ist, wie man containerartige Datenmengen herumreicht. Ausnahmen gibt es nur, wenn man selber containerartige Strukturen schreibt, da ist klar, dass die natürlich auch sich selber benutzen können. Und std::string, mathematische Vektoren und ähnliches, weil die eine spezielle Semantik haben, die über "Folge von Zeichen/Zahlen" hinaus geht.

    Ich würde sagen es gibt viel mehr Dinge die eine spezielle Semantik haben - bzw. wo es einfach Sinn macht es nicht generisch zu machen.

    Und ich finde in grösseren Anwendungen gibt es auch oft gute Gründe die Container weiter einzuschränken. z.B. Compilezeit und Template-Bloat.

    span und string_view sind da IMO oft sehr gut geeignet.

    Davon abgesehen: generischen, wiederverwendbaren Code zu schreiben ist sowohl ziemlich schwer als auch ziemlich aufwendig. Damit ein paar Templates wo drüberzustreuen um konkrete Containertypen zu vermeiden ist es - ausser in sehr einfachen Fällen - lange nicht getan. Mein Rat wäre eher nicht zu viel Zeit damit zu verschwenden zu versuchen Dinge wiederverwendbar zu machen - es sei denn man hat ein solides Verständnis der Problemdomäne, z.B. weil man schon 1-2x eine wenig/nicht-generische Lösung implementiert hat.



  • @Belli sagte in Array übergeben zu Konstruktor:

    Überlässt man das dann sozusagen sich selbst weil die Funktion mit T = string ja nicht übersetzt werden kann?

    Das wäre der alte Weg und das in Kombination mit sehr kryptischen Fehlermeldungen des Compilers, wobei die Compiler in dieser Hinsicht teilweise besser geworden sind. Leider wird noch immer viel zu viel Müll emittiert und man muss zuerst die eigentliche Fehlermeldung suchen. Ab C++20 ist der neue Weg Konzepte (Concept), so dass man bestimmte Eigenschaften des übergebenen Typen T einfordert z.B. das sqrt(T) definiert ist. Dadurch werden die Fehlermeldungen ebenfalls klarer.