for Schleife vs std::for_each + lambda



  • manni66 schrieb:

    Sone schrieb:

    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt,

    Doch, daS ist durchaus so vorgesehen. Herb Sutter predigt das auch immer in seinen Vorträgen. Jeder Compiler kann das optimieren.

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Um Visual C++ idiotensicher zu machen, ist der Zugriff auf Iteratoren standardmässig "gecheckt". Das ist langsam, kann aber nicht optimiert werden.

    std::for_each hingegen muss nur einmal checken und kann dann unchecked Iteratoren verwenden. Das ist dann schneller.

    Sone schrieb:

    Nicht für for_each! Dafür gibt es range-based for!

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range (es sei denn, jemand schreibt Zusatzcode, aber dann ist for_each wieder kürzer).



  • manni66 schrieb:

    Aber wie gesagt, for_each und Lambda ist durchaus so vorgesehen.

    Wo?

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range (es sei denn, jemand schreibt Zusatzcode, aber dann ist for_each wieder kürzer).

    boost::iterator_range hilft ab! 🤡 Gut, das ist jetzt ein Scherz, für Subranges ist for_each natürlich besser.

    ~P.S.: Alle Welt wird verwirrt weil VC++ wieder schlecht ist? Hach...~



  • baef schrieb:

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Das ist möglich, ich bezweifle es aber.



  • manni66 schrieb:

    baef schrieb:

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Das ist möglich, ich bezweifle es aber.

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.
    Ich zitiere:

    volkard schrieb:

    Ich glaube auch Gurus kein Wort, wenn sie keine Begründung anbringen können.



  • Sone schrieb:

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.

    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.



  • Bezüglich irgendwelcher "MSVC-Limitations": Wären mir keine bekannt. MSVC unterstützt sowohl Lambdas als auch std::for_each und range-based for loops. 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...



  • Sone schrieb:

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.
    Ich zitiere:

    volkard schrieb:

    Ich glaube auch Gurus kein Wort, wenn sie keine Begründung anbringen können.

    manni66 schrieb:

    Bei for_each weiss man beim Lesen direkt, dass über alle Elemnte iteriert werden soll. Bei for muss man sich erst den Schleifenkopf komplett ansehen.

    Das Argument zieht meiner Meinung nach aber nicht bei range based for, sondern nur bei Schleifen wie in der Frage.



  • #define foreach for
    


  • baef schrieb:

    Um Visual C++ idiotensicher zu machen, ist der Zugriff auf Iteratoren standardmässig "gecheckt". Das ist langsam, kann aber nicht optimiert werden.

    Wer optimiert schon im Debug-Modus?

    Davon abgesehen sind Checked Iterators ein wahnsinnig nützliches Feature. Wenn man nämlich vernünftig C++ programmiert und Abstraktionstechniken wie RAII verwendet, gehören Iteratoren fast zu den häufigsten Fehlerquellen. Das liegt daran, dass sie doch vergleichsweise "low-level" sind. Ich sollte endlich mal eine breite Offensive mit Ranges starten.

    baef schrieb:

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range

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



  • Ok, also Mal Butter bei die Fische: Range-based-for und std::foreach + lambda nehmen sich performancetechnisch (im Release-Mode [wie Nexus schon sagt, unsinnig das dazuschreiben zu müssen]) nichts, ersteres ist aber besser lesbar und daher zu bevorzugen, gesetzt der Fall man nutzt den richtigen Compiler.

    Können wir das so stehen lassen oder gibt es Einwände?



  • Ich bedanke mich mal für die ganzen Stimmen, die sich hier zu Wort gemeldet haben 😉
    Wenn ich das so lese, lohnt es sich also in keinem Fall durch das Projekt zu gehen, und alle for iterationen durch std::for_each zu ersetzen, dennoch aber zukünftig eher std::for_each verwenden? Das hilft mir dann doch schon weiter.

    mfg



  • 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.


Anmelden zum Antworten