for Schleife vs std::for_each + lambda



  • Die drei Dinger drücken drei verschiedene Dinge aus. Verwend je nach Situation das, was ausdrückt, was du gerne ausdrücken würdest...



  • Das einzig gute Argument ist, dass for_each auf Teilsequenzen angewandt werden koennen. Hat demnach seine Daseinsberechtigung, nicht nur pre-C++11. Alle anderen Argumente wie Lesbarkeit sind subjektiv. Ich verstehe auch nicht, warum for_each und lambdas nicht zusammenpassen (wie Sone meint).

    for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt, sondern mit vordefinierten Funktionen oder Lambdas

    Ich uebersetze: for_each wird nicht mit Lambdas benutzt sondern mit Lambdas. Haeh?

    Auch gibt es keine Unterschiede beim Aufruf zwischen std::for_each, std::accumulate, std::copy_if, ... alles Funktionen hoeherer Ordnung die alle! mit "in-place" Lambdas super funktionieren.

    Die drei Dinger drücken drei verschiedene Dinge aus. Verwend je nach Situation das, was ausdrückt, was du gerne ausdrücken würdest...

    Bei der Verwendung

    for(auto e: cont) {...}
    for(it = cont.begin(); it != cont.end(); ++it) { ... }
    for_each(cont.begin(), cont.end(), []( ...) { ... });
    

    druecken sie nicht unterschiedliche Dinge aus. Das kann jetzt gern noch mittels const und Referenzen angepasst werden, aber es ist gehupt wie gesprungen ...



  • knivil schrieb:

    for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt, sondern mit vordefinierten Funktionen oder Lambdas

    Ich uebersetze: for_each wird nicht mit Lambdas benutzt sondern mit Lambdas. Haeh?

    Du übersetzt falsch. Es macht für mich keinen Sinn, die Prozedur für jedes Element in ein Lambda zu schmeißen und dieses dann als Funktionsobjekt an for_each zu übergeben. Das ist einfach nicht sinngemäß. Erst wenn von irgendwoher ein Funktionsobjekt stammt, und man eine durch Iteratoren eingeschlossene Menge hat, sehe ich einen Sinn in for_each.

    Aber das ist mein Geschmack. Man könnte auch for_each dafür benutzen, das sehe ich allerdings als Sinnlosigkeit an, wenn man range-based for hat. Dann kann man das Compound-Statement doch direkt hinschreiben anstatt sich um krimskrams wie das lambda-capture zu kümmern o.ä.
    Ich fasse es nicht dass das einige ernst meinen.

    Range-based-for und std::foreach + lambda nehmen sich performancetechnisch [...] nichts, ersteres ist aber besser lesbar und daher zu bevorzugen[...].

    👍 Auch:

    Nexus schrieb:

    Das stimmt zwar, allerdings sind die meisten Anwendungsfälle dennoch begin-end-Paare. Oft wären Ranges tatsächlich besser.

    👍

    paraglider schrieb:

    Siehst du es ein, dass sich for_each sehr leicht in parallel_for_each umschreiben lässt? for_each bietet mehr Abstraktion als ein plain for.

    Das ist hier irrelevant.

    lohnt es sich also in keinem Fall durch das Projekt zu gehen, und alle for iterationen durch std::for_each zu ersetzen

    Genau, nein!

    dennoch aber zukünftig eher std::for_each verwenden?

    Wo siehst du einen Vorteil für dich?



  • Also wenn range-based-for nicht zur Verfügung steht, ist:

    std::foreach(container.begin(), container.end(), [](const Element& elem)
    {
        // tu was
    });
    

    immer noch deutlich besser als:

    for(Container::const_iterator it = container.begin(); it != container.end(); ++it)
    {
        const Element& elem = *it;
        // tu was
    }
    

    aus Gründen von Schreibarbeit. Und weil Container plötzlich anders heißen kann. Und weil man für Änderung von const->non-const nicht mehr zwei sondern nur noch eine Änderung(en) machen muss. Für ein einfaches Durchblättern ist das also viel schöner. Und wenn man nicht einfach durchgehen sondern löschen will, finde ich erase/remove_if-Idiom mit Lambda statt dem blöden:

    for(Container::iterator it = container.begin(); it != container.end();)
    {
        if(irgendwas)
            it = container.erase(it);
        else
            ++it;
    }
    

    auch viel besser.

    Diese " it = ? "-Variante finde ich eigentlich nie schön. Wo sollte man die denn bitte überhaupt noch benutzen (Frage geht auch an dot, der dieser Variante - falls sie eine der drei von ihm angesprochenen sein soll - einen Anwendungszweck zugesprochen hat)?



  • Eisflamme schrieb:

    Also wenn range-based-for nicht zur Verfügung steht, ist:

    std::foreach(container.begin(), container.end(), [](const Element& elem)
    {
        // tu was
    });
    

    immer noch deutlich besser als:

    for(Container::const_iterator it = container.begin(); it != container.end(); ++it)
    {
        const Element& elem = *it;
        // tu was
    }
    

    aus Gründen von Schreibarbeit. Und weil Container plötzlich anders heißen kann. Und weil man für Änderung von const->non-const nicht mehr zwei sondern nur noch eine Änderung(en) machen muss.

    Observe!

    for(auto it = container.begin(); it != container.end(); ++it)
    {
        auto elem = *it;
        // tu was
    }
    

    wobei elem =*it schon Grenzwertig als Zeile ist 🙂



  • Ok, auto gehört zum korrekten Vergleich dazu. Trotzdem hast Du eine Zeile mehr, wobei Zeile 1 dafür kürzer ist. Dafür sehe ich kein const& mehr. Nagut, ist jetzt wohl Geschmackssache. Ich mag es einfach Iteratoren nur implizit zu verwenden. Ja, so ist es! Jetzt habt ihr mich.



  • Außerdem lässt sich alles mit Makros vereinfachen.

    #define ITERATE( range, it_name ) \
        for(auto it_name = (range).begin(); it_name != (range).end(); ++it_name)
    

    Eisflamme schrieb:

    Ich mag es einfach Iteratoren nur implizit zu verwenden. Ja, so ist es! Jetzt habt ihr mich.

    Und da ist nichts schönes dran. Alles irgendwie den STL-Algorithmen rein zu wurschteln ist nicht schön. Schreibfaulheit ist auch eine schlechte Angewohnheit.

    Trotzdem hast Du eine Zeile mehr, wobei Zeile 1 dafür kürzer ist.

    So ein akribischer Vergleich ist doch schon paranoid.



  • dot schrieb:

    Iteratoren sind in einem Debug Build per default checked, das kann man aber selbstverständlich deaktivieren, was im Release Build natürlich per default passiert...

    Ich bin mir 90% sicher dass die per Default immer checked sind.



  • hustbaer schrieb:

    dot schrieb:

    Iteratoren sind in einem Debug Build per default checked, das kann man aber selbstverständlich deaktivieren, was im Release Build natürlich per default passiert...

    Ich bin mir 90% sicher dass die per Default immer checked sind.

    Mit Debug mehr als mit Release, aber wenn man keinen Check will muss man das mit Defineschaltern abstellen.



  • manni66 schrieb:

    hustbaer schrieb:

    dot schrieb:

    Iteratoren sind in einem Debug Build per default checked, das kann man aber selbstverständlich deaktivieren, was im Release Build natürlich per default passiert...

    Ich bin mir 90% sicher dass die per Default immer checked sind.

    Mit Debug mehr als mit Release, aber wenn man keinen Check will muss man das mit Defineschaltern abstellen.

    Korrektur: in VS2012 sind die Schalter im Release per default aus.



  • Sone:
    Geschmackssache, was implizite Iteratorenverwendung anbetrifft, unter Python wird das auch hochgeehrt und umjubelt. In STL-Algorithmen wurschteln ist bei der Kürze wo genau das Problem? Wenn es keinen Nachteil erzeugt, aber ich die Lesbarkeit besser finde, halte ich das für besser (hier wieder: Geschmackssache). Und Schreibfaulheit ist keine schlechte Angewohnheit. Muss ich andauernd langen Code schreiben, geht die Entwicklungszeit hoch. Tut sie das, brauche ich für Aufgaben länger oder bewältige Aufgaben in gleicher Zeit schlechter. In jedem Fall ist mehr Schreibarbeit unökonomischer. D.h. nicht, dass man kurzen Code um jeden Preis ersuchen soll, aber wenig schreiben zu wollen liefert in meinen Augen den richtigen Anreiz.

    Und was paranoid angeht: Benutze das Wort bitte korrekt, wenn Du es schon verwendest. Selbst wenn man paranoid vom Vergleich auf den Vergleichenden überträgt, ergibt das keinen Sinn.

    Ach das war mehr Text als sinnvoll war. Mir liegt das doch eigentlich gar nicht am Herzen, das ist das Vormittagsloch (nicht so schlimm wie das Nachmittagsloch, aber trotzdem ein Loch :().



  • Sone schrieb:

    Außerdem lässt sich alles mit Makros vereinfachen.

    #define ITERATE( range, it_name ) \
        for(auto it_name = (range).begin(); it_name != (range).end(); ++it_name)
    
    ITERATE(object.getList(), itr)
    {
       itr->fn();
    }
    

    BAM 👎



  • std::for_each sagt "wende folgende Funktion auf jedes Element an".
    range-based for loop sagt "führe folgendes Statement für jedes Element in der gegebenen Range aus".
    for loop sagt "nimm folgenden iterator und laufe so lange bis condition false wird".

    Man beachte z.B., dass man in std::for_each bzw. einer range-based for loop keinen Enfluss auf die Iteration nehmen kann.

    hustbaer schrieb:

    dot schrieb:

    Iteratoren sind in einem Debug Build per default checked, das kann man aber selbstverständlich deaktivieren, was im Release Build natürlich per default passiert...

    Ich bin mir 90% sicher dass die per Default immer checked sind.

    Das war iirc in VS 2005 so, wurde nach lautem Aufschrei der Community ("omgomgomg VS 2005 is laaaangsaaaam!!!!1111einseinseinself") aber schnell wieder geändert... 😉



  • unter Python wird das auch hochgeehrt und umjubelt.

    Python? Wir sind im C++-Forum!

    Nexus schrieb:

    Sone schrieb:

    Außerdem lässt sich alles mit Makros vereinfachen.

    #define ITERATE( range, it_name ) \
        for(auto it_name = (range).begin(); it_name != (range).end(); ++it_name)
    
    ITERATE(object.getList(), itr)
    {
       itr->fn();
    }
    

    BAM 👎

    Das sehe ich als persönliche Herausforderung:

    template<typename T>
    auto determine_type [[noreturn]] ( T&& t ) -> decltype(std::forward<T>(t)) {}
    
    #define ITERATE( range, it_name ) \
        for( bool _loopcond = true; _loopcond ; ) \
    		for( decltype(determine_type(range)) _range = range; _loopcond; _loopcond = false ) \
    			for( auto it_name = std::begin(_range); it_name != std::end(_range); ++it_name )
    

    P.S.: Ja, paranoid ist hier völlig falsch.



  • Warum decltype(determine_type(range)) und nicht auto&& ?



  • 1. WTF:

    Nexus schrieb:

    Warum decltype(determine_type(range)) und nicht auto&& ?

    Damit er ein völlig sinnloses [[noreturn]] reinstopfen kann, obwohl in dem Fall ein Auslassen des Funktionsbodys, oder sogar ein auto&& gereicht hätte.

    2. WTF:
    begin() wird nicht geadlt.

    namespace sone {
      struct mess{
        mess(int i) : i(i) {}
        mess(mess const&) =delete;
        mess(mess&&) =delete;
        int i;
      };
      int *begin(sone::mess& m) { return &m.i; }
      int *end(sone::mess& m) { return &m.i + 1; }
    }
    

    3. WTF:
    Geht nicht mit initializer_list

    #define LIST {1,2,3,4} // ja ich bin nett und versuche gar nicht, das als Parameter zu übergeben
      ITERATE(LIST, itr) { // forensyntaxhighlighter ftw
        std::cout << *itr << '\n';
        break;
      }
    


  • Also Containerabstraktion/Algorithmenabstraktion laeuft ueber Iteratoren in C++. D.h. Iteratoren sind in idiomatischem C++ vorzuziehen, also auch vor for(auto e: cont) { ... } . Deswegen ist for_each nur konsequent. Nun, mitlerweile hat man gemerkt, dass man sowieso meist mit ganzen Containern arbeitet, zumal sich Iteratoren nur schlecht kombinieren lassen.

    @Sone: Lass doch einfach diese ganzen Makros. Makros are evil. Genau wie goto sollte man den Einsatz begruenden. Ich persoenlich habe kein Bock, deine Makros zu verstehen, egal wie einfach sie scheinen moegen. Bleiben wir doch bei C++.



  • Hmm, bin ich wohl der einzige, der einfacch std::for_each verwendet wenn er eine unäre Funktion auf eine Range anwenden möchte. 😕

    vector<void*> ptrs;
    for_each(ptrs.begin(), ptrs.end(), &free);
    

    (Jaja, in dem Fall würde man RAII nutzen ...)



  • knivil schrieb:

    Also Containerabstraktion/Algorithmenabstraktion laeuft ueber Iteratoren in C++. D.h. Iteratoren sind in idiomatischem C++ vorzuziehen, also auch vor for(auto e: cont) { ... } . Deswegen ist for_each nur konsequent.

    Range-based loops laufen doch auch über Iteratoren, sind also genauso konsequent.

    Die Syntax ist zwar eine andere, aber insgesamt sind sie doch deutlich einfacher zu lesen, vor allem wenn man auch noch auto als Typ verwendet.
    Wenn man hingegen schon ein Funktionsobjekt oder eine Funktion hat, sieht es deutlich übersichtlicher aus und dann ist std::for_each vorzuziehen.

    Edit: Insgesamt nimmt sich das aber nicht viel, also soll es doch jeder so machen, wie es ihm gefällt.



  • Nexus schrieb:

    Warum decltype(determine_type(range)) und nicht auto&& ?

    Hab ich gar nicht gesehen. Ich habe den Trick schon im Standard gefunden, dann aber wieder vergessen. 🙂
    Das [[noreturn]] war jetzt analog zu declval, nur so als Hinweis dass diese Funktion niemals aufgerufen werden soll und keiner nach einer Definition suchen soll. (Hätte zwar auch ein Kommentar getan, aber tja).

    sone__mess schrieb:

    2. WTF:
    begin() wird nicht geadlt.

    Ja, dafür muss was her was §6.5.4/1 entspricht.

    3. WTF:
    Geht nicht mit initializer_list

    Geht wunderbar. Mit GCC 4.9.0.


Anmelden zum Antworten