end-Iterator decrement definiert?



  • Arcoth, wenn du schon eine einfache Lösung ablehnst und mit dem "Argument" der Eleganz auf eine unnötig komplizierte hinweist, liegt es an dir, eine Begründung dazu zu liefern. Ansonsten bleibe ich bei der "clever"-Theorie.

    c++11-coder, für den Fall, dass du deinen Post tatsächlich ernst gemeint hast: Davon abgesehen, dass du range() auch zuerst schreiben musst, funktioniert das ohnehin nicht, da first und second keine Iteratoren, sondern Kopien der Elemente sind.



  • Nexus schrieb:

    c++11-coder, für den Fall, dass du deinen Post tatsächlich ernst gemeint hast: Davon abgesehen, dass du range() auch zuerst schreiben musst, funktioniert das ohnehin nicht, da first und second keine Iteratoren, sondern Kopien der Elemente sind.

    Muss ich jetzt auch noch erklären, wie range funktioniert 🙄

    for (int i : range(0,10))
      std::cout << i << '\n';
    // gibt "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" aus
    

    Wie kann man ohne dieses range nur leben?


  • Mod

    unnötig komplizierte hinweist

    Was genau ist daran kompliziert?

    Vergleiche folgende Methoden, über ein Intervall zu iterieren:

    for( unsigned i = 1; i != 20; ++i )
        /* ... */;
    
    unsigned i = 0;
    while( ++i != 20 )
        /* ... */;
    

    Was mir auffällt ist folgendes: Ersteres ist leichter zu lesen. Man braucht ein Sekündchen für das !=, was auch ein <= oder ein < sein könnte, aber im Prinzip erkennt man schnell, dass über [1, 20) iteriert wird.
    Das zweite ist zwar nicht komplizierter (wenn nicht einfacher) in der Syntax, aber da es dem gewöhnlichen Schema for( int-Deklarieren ; Vergleich; Inkrement )
    nicht folgt, braucht man noch zwei Sekündchen um zu kapieren, worüber iteriert wird.

    Jetzt vergleiche mal

    // (1)
    unsigned i = computeGamma( i / 2 ) * pi;
    while( ++i != 20 )
        /* ... */
    
    // oder (2)
    
    unsigned i = computeGamma( i / 2 ) * pi + 1;
    while( i != 20 )
    {
        /* ... */
        ++i;
    }
    
    // oder (3)
    unsigned i = computeGamma( i / 2 ) * pi + 1;
    for(; i != 20; ++i )
        /* ... */
    
    // oder (4)
    
    for( unsigned i = computeGamma( i / 2 ) * pi + 1; i != 20; ++i )
    

    Ich würd' sagen, (3) sieht gut aus, bei (4) kommen Erinnerungen an Werner hoch (buchstäblich), bei (2) nervt das Extra-Statement (ist eigentlich kein Kandidat) und bei (1) siehts auch ganz gut aus. Nur stört mich bei (3), dass die + 1 an den Initializer der Variable drangehängt wird, bei (1) kommt das +1 in dem Intervall deutlicher zu Tage.

    Weil std::next( second ) schon ein ganz klein wenig lang ist, tendiert es, obiger Argumentation zu folgen. Daher finde ich die while -Version nicht schlimmer als die for -Version, längere Statements in der Initialisierung nerven bei for .

    Edit: Wenn ich noch mal drüber schaue, habe ich Mist gelabert. Menno.



  • Du vergisst

    // oder (5)
    
    for(auto i : range<unsigned>(computeGamma(i/2)*pi + 1, 20))
      /* ... */;
    


  • Ihr diskutiert wirklich, wie man über ein Range iteriert?
    Ich mein, die Antwort ist doch klar! Der einzige wahre Weg ist:

    void iterate(iterator begin, iterator end)
    {
      (do_sth(*begin), begin != end) && (iterate(++begin, end), true);
    }
    

    Ich bitte euch!



  • Nathan schrieb:

    Ihr diskutiert wirklich, wie man über ein Range iteriert?
    Ich mein, die Antwort ist doch klar! Der einzige wahre Weg ist:

    void iterate(iterator begin, iterator end)
    {
      (do_sth(*begin), begin != end) && (iterate(++begin, end), true);
    }
    

    Ich bitte euch!

    UB wenn begin==end. Ich sags dir, über einen Range zu iterieren ist nicht trivial, deshalb ist man froh, die Funktion range() zu haben.



  • c++11-coder schrieb:

    Nathan schrieb:

    Ihr diskutiert wirklich, wie man über ein Range iteriert?
    Ich mein, die Antwort ist doch klar! Der einzige wahre Weg ist:

    void iterate(iterator begin, iterator end)
    {
      (do_sth(*begin), begin != end) && (iterate(++begin, end), true);
    }
    

    Ich bitte euch!

    UB wenn begin==end. Ich sags dir, über einen Range zu iterieren ist nicht trivial, deshalb ist man froh, die Funktion range() zu haben.

    Stimmt, falsche Reihenfolge

    begin != end && (do_sth(*begin), iterate(++begin, end), true);
    

    Mein Fehler.



  • Schade, ich dachte schon ihr wärt an einer ernsthaften Diskussion interessiert.

    @Arcoth: Bitte bleib beim konkreten Beispiel und sag, wo dort die Vorteile an deiner Version liegen. Dein Stellvertreter-Argument kann ich so nicht übertragen. Ich denke aber es wäre einfacher, nachzugeben 😉



  • Nathan schrieb:

    Stimmt, falsche Reihenfolge

    begin != end && (do_sth(*begin), iterate(++begin, end), true);
    

    Mein Fehler.

    Immer noch UB, allerdings sehr subtil: do_sth könnte einen Typ zurückgeben, der den operator, überladen hat. Dann ist do_sth(*begin) nicht mehr vor iterate(++begin, end) gesequenced, was bedeutet, dass *begin und ++begin nicht gesequenced sind, was dazu führen kann, dass do_sth(*(begin+1)) aufgerufen wird, was einerseits falsch ist und andereseits UB für begin+1==end.
    </OT>

    Nexus schrieb:

    Schade, ich dachte schon ihr wärt an einer ernsthaften Diskussion interessiert.

    Bin ich. Ich finde meine range-Variante kürzer und lesbarer.

    Und zwar aus dem Grund, dass es auf den ersten Blick genauso kurz verständlich ist wie die explizite for-loop, aber man bei der for-Loop immer noch einen kurzen Check machen muss, ob die Form wirklich stimmt. Wie schnell merkst du, dass bei den folgenden Schleifen was faul ist:

    for( unsigned i = 1; i == 20; ++i )
    
    for( unsigned i = 1; i != 20; --i )
    


  • Sorry, Nexus aber:

    c++11-coder schrieb:

    Nathan schrieb:

    Stimmt, falsche Reihenfolge

    begin != end && (do_sth(*begin), iterate(++begin, end), true);
    

    Mein Fehler.

    Immer noch UB, allerdings sehr subtil: do_sth könnte einen Typ zurückgeben, der den operator, überladen hat. Dann ist do_sth(*begin) nicht mehr vor iterate(++begin, end) gesequenced, was bedeutet, dass *begin und ++begin nicht gesequenced sind, was dazu führen kann, dass do_sth(*(begin+1)) aufgerufen wird, was einerseits falsch ist und andereseits UB für begin+1==end.
    </OT>

    Die Tricks vom IOCCC funktionieren wohl wirklich nur mit C.

    Nexus schrieb:

    Schade, ich dachte schon ihr wärt an einer ernsthaften Diskussion interessiert.

    Bin ich. Ich finde meine range-Variante kürzer und lesbarer.[...]

    Absolut deiner Meinung.


  • Mod

    Ich denke aber es wäre einfacher, nachzugeben

    Jup, ich gebe nach. War alles Blödsinn. Auch wenn ich while-Schleifen einfach deutlich mehr mag, als for-Schleifen. Ich mag es halt nicht, wenn mehrere Dinge auf eine Zeile fallen, dann müssen schon viele Leerzeichen dabei sein...


  • Mod

    Nathan schrieb:

    Sorry, Nexus aber:

    c++11-coder schrieb:

    Nathan schrieb:

    Stimmt, falsche Reihenfolge

    begin != end && (do_sth(*begin), iterate(++begin, end), true);
    

    Mein Fehler.

    Immer noch UB, allerdings sehr subtil: do_sth könnte einen Typ zurückgeben, der den operator, überladen hat. Dann ist do_sth(*begin) nicht mehr vor iterate(++begin, end) gesequenced, was bedeutet, dass *begin und ++begin nicht gesequenced sind, was dazu führen kann, dass do_sth(*(begin+1)) aufgerufen wird, was einerseits falsch ist und andereseits UB für begin+1==end.
    </OT>

    Die Tricks vom IOCCC funktionieren wohl wirklich nur mit C.

    cast nach void hilft.



  • c++11-coder, kannst du etwas mehr über range() verraten? Basiert das auf Boost.Range oder hast du das selbst gestrickt?

    Ich frage mich vor allem deswegen, weil

    for (auto e : r)
    

    davon ausgeht, dass für den Typ von r ein begin() und end() (global oder als Member) vorhanden ist, und diese beiden Funktionen jeweils Iteratoren zurückgeben.

    Damit aber so etwas möglich wäre wie in der verschachtelten Schleife, wo e selbst ein Iterator (und kein Element) ist, müsstest du eine zusätzliche Indirektion einbauen. Machst du das tatsächlich oder war es mehr eine Idee?



  • Ich habe mir mein range() zwar selber geschrieben, aber neu ist die Idee nicht. Du könntest auch boost::irange verwenden:

    #include <iostream>
    #include <string>
    #include <boost/range/irange.hpp>
    
    int main()
    {
      for (int i : boost::irange(0,5))
        std::cout << i << '\n';
    
      std::string s = "abcdef";
      using std::begin; using std::end;
      for (auto i : boost::irange(begin(s), end(s)))
        for (auto j : boost::irange(std::next(i), end(s)))
          std::cout << *i << *j << '\n';
    }
    

    Ausgabe:

    0
    1
    2
    3
    4
    ab
    ac
    ad
    ae
    af
    bc
    bd
    be
    bf
    cd
    ce
    cf
    de
    df
    ef
    

    Dieser range() ist eine schöne Ergänzung zum normalen range based for, weil das for eine Indirektion wegnimmt, man aber manchmal die Iteratoren eben doch braucht. Und es funktioniert super mit int-Ranges.



  • Arcoth schrieb:

    Ich denke aber es wäre einfacher, nachzugeben

    Jup, ich gebe nach. War alles Blödsinn. Auch wenn ich while-Schleifen einfach deutlich mehr mag, als for-Schleifen. Ich mag es halt nicht, wenn mehrere Dinge auf eine Zeile fallen,

    Du magst for nicht so gern, NUR WEIL Du for mißbrauchst, Alda!


  • Mod

    volkard schrieb:

    Du magst for nicht so gern, NUR WEIL Du for mißbrauchst, Alda!

    Jetzt halt schon die Schnauze mit deinem Assi-Dialekt! Bist du erst 25 oder was?

    Und ich missbrauche for -Schleifen nie! Ich nutze sie tatsächlich lediglich für das Standardschema. Beispiel:

    for( unsigned i = 2; i != primes.size(); ++i )
    

    Für nichts anderes. Außer vielleicht

    for( unsigned i; std::cin >> i; )
        //...
    

    Wobe ich da schon eher zu while tendiere.



  • Arcoth schrieb:

    Und ich missbrauche for -Schleifen nie! Ich nutze sie tatsächlich lediglich für das Standardschema. Beispiel:

    for( unsigned i = 2; i != primes.size(); ++i )
    

    Für nichts anderes.

    Aha...

    Arcoth schrieb:

    for (auto first = begin(v); first != end(v); ++first) // Hier sollte die Abbruchbedingung wahrscheinlich eher first != --end(v) lauten
    	for( auto second = first; ++second != end(v); )
    		doSomething( *first, *second );
    

    Das ist mMn. deutlich eleganter.



  • Arcoth schrieb:

    Allerdings kann man dieses temporäre Objekt gleich selbst in die Hand nehmen

    …
    	for( auto second = first; ++second != end(v); )
    		doSomething( *first, *second );
    

    Das ist mMn. deutlich eleganter.

    😮 😮 😮


  • Mod

    Aha...

    Ups! Ich sollte es natürlich nicht so formulieren. Ich nutze es auch zum Iterieren mit Iteratoren. Standardschema hieß oben
    for( Initialisieren; Vergleich; Inkrement )
    wie immer.

    Außerdem habe ich nicht gesagt, dass

    for( auto second = first; ++second != end(v); )
    

    die eleganteste Methode ist. Es ist nur so: Ich finde es schöner als

    for( auto second = std::next( first ); second != end(v); ++second )
    

    ,
    aber wahrscheinlich würde ich in meinem Code eher while benutzen. Und zwar so

    auto second = first;
    while( ++second != end(v) )
    

    Weil ich for, sobald es von obigem Schema abweicht, einfach nicht mehr attraktiv finde. Bei while kann die Bedingung auch gut und gerne ein wenig komplizierter werden.

    Nun fand ich bei dem konkreten Beispiel doof, dass man die Klammern ergänzen muss, wenn man while verwendet (weil man noch eine Zeile zum Definieren von second braucht)! Ich versuche immer möglichst ohne diese Klammern auszukommen - das ist ein Tick von mir 🤡



  • Arcoth schrieb:

    for( auto second = std::next( first ); second != end(v); ++second )
    

    Ein Bißchen Syntax-Rauschen weg

    for( auto second=next(first); second!=end(v); ++second )
    

    und schon isses hübsch. 🤡
    Nee, mal ehrlich, eine komplexte Laufbedingung nervt Dich weniger als eine komplexe Initialisierung, das finde ich irgendwie C.


Anmelden zum Antworten