end-Iterator decrement definiert?


  • Mod

    Nexus schrieb:

    Übrigens gibts in C++11 die Funktionen std::prev() und std::next() . Ist manchmal ganz nützlich für temporäre Objekte...

    Ja, aber höchstens, wenn man mehr als einmal dekrementieren will.



  • Auch wenns nur einmal ist. --end() muss nicht unbedingt funktionieren.


  • Mod

    Nexus schrieb:

    Auch wenns nur einmal ist. --end() muss nicht unbedingt funktionieren.

    Und std::prev schon, oder was?



  • Genau. Wenn Iteratoren Zeiger sind (was bei std::vector theoretisch erlaubt ist), kannst du den skalaren RValue v.end() nicht verändern, aber sehr wohl ein anderes Objekt zurückgeben.

    Davon abgesehen, dass man manchmal den ursprünglichen Iterator gar nicht verändern will, z.B. in einer verschachtelten Schleife mit allen möglichen Element-Paaren:

    for (auto first = v.begin(); first != v.end(); ++first)
    {
        for (auto second = std::next(first); second != v.end(); ++second)
        {
            doSomething(*first, *second);
        }
    }
    

  • Mod

    Davon abgesehen, dass man manchmal den ursprünglichen Iterator gar nicht verändern will, z.B. in einer verschachtelten Schleife mit allen möglichen Element-Paaren:

    Ich verstehe.

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

    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:

    Das ist mMn. deutlich eleganter.

    Ich weiss nicht was du unter "elegant" verstehst, aber ich brauche für Abweichungen vom Standard-Iterationsschema

    for (auto itr = begin; itr != end; ++itr)
    

    jeweils länger, um den Code zu verstehen.

    Gerade ++second != end(v); und das Verzichten auf den Update-Teil machen für mich vor allem den Eindruck, dass jemand "clever" sein will. Das entspricht nicht meinem Verständnis von gutem Code, geschweige denn Eleganz.



  • Arcoth schrieb:

    Davon abgesehen, dass man manchmal den ursprünglichen Iterator gar nicht verändern will, z.B. in einer verschachtelten Schleife mit allen möglichen Element-Paaren:

    Ich verstehe.

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

    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.

    Aua.


  • Mod

    Jetzt sagt mir nicht, dass das einzige Argument für dein AUA die Abweichung vom Standarditerationsschema ist.

    ~Edit: Grammatikalischen Fehler korrigiert, nicht dass das Volkard auch weh tut...~



  • Sag mal, seit ihr noch nicht in C++11 angekomen?

    for (auto first : range(begin(v), end(v)))
      for (auto second : range(next(first), end(v)))
        doSomething(*first, *second);
    


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


Anmelden zum Antworten