range based for-loop : wie kommt man an den Index?



  • Nun, du kannst (bspw. bei einem vector ) die Adresse des aktuellen Elements von der Adresse des ersten Elementes abziehen. Das ist überhaupt das einzig direkte, was mir ohne weitere Laufvariablen einfällt.

    Ja, aber das wird dann in intern in einen int gecasted, also z. B. durch 4
    geteilt, Adresse ist ja nicht Index.



  • wastl schrieb:

    Adresse ist ja nicht Index.

    Nein, aber Differenz der Adressen ist gleich Index. Die Grösse der Datentypen wird automatisch berücksichtigt, wenn du zwei Iteratoren oder Zeiger subtrahierst; das Resultat ist die Anzahl notwendiger Inkrementierungen, um von einen zum anderen zu gelangen.



  • Ich hatte auch schon oft darüber nachgedacht, doch ich glaube die ranged for loop ist einfach nicht dafür gedacht. Sicher, man möchte das coole neue Feature anwenden, aber wenn es im Rahmen der Numerik um diskretisierte Punkte geht, dann bist du glaube ich mit dem Indexzugriff des Vektors am Besten bedient.

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird, es gibt ja auch Algorithmen, wo nur manche
    Indizes benutzt werden, oder durcheinander usw.



  • wastl schrieb:

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird

    Nein, dass über alle Elemente iteriert wird.



  • Nexus schrieb:

    wastl schrieb:

    Adresse ist ja nicht Index.

    Nein, aber Differenz der Adressen ist gleich Index. Die Grösse der Datentypen wird automatisch berücksichtigt, wenn du zwei Iteratoren oder Zeiger subtrahierst; das Resultat ist die Anzahl notwendiger Inkrementierungen, um von einen zum anderen zu gelangen.

    Inherhalb von C++ ist das richtig. Wenn man aber in den Assembler reinsieht, dann wird die Adresse abgezogen und dann durch sizeof(double) (z. B.) geteilt, oder shift, dieser Wert kommt dann in ein Register und ist der Index. Ist sizeof(double) z. B. 8, so ist die Differenz der effektiven Adressen zweier benachbarter doubles in der Maschine 8 und nicht 1.
    Es ist gut möglich, dass die Variante mit einer zusätzlichen Index-Variablen gar nicht länger läuft, da i++ gleichzeitig mit den Floating-Point-Sachen in der CPU abläuft, vorausgesetzt, der Index befindet sich in einem Register, bzw. in einem Register der Maschine, die den X86-Registersatz simuliert ...



  • Spezielle Anwendungen brauchen spezielle Mittel. Da man in der Numerik häufig mit Vektoren und Matrizen als elementare Bausteine arbeitet, nimmt man eben eine Bibliothek, die das kann und verzichtet dann komplett auf explizite Loops. Das ist sogar viel besser, weil die Bibliothek dann intern große Geschütze auffahren kann, um zum Beispiel sin und cos über SIMD parallel zu berechnen. Das hat ausserdem den Vorteil, dass der Code lesbarer wird.

    blas::vector<double> X = sin(k*blas::irange(0,100));
    


  • wastl schrieb:

    Ich hatte auch schon oft darüber nachgedacht, doch ich glaube die ranged for loop ist einfach nicht dafür gedacht. Sicher, man möchte das coole neue Feature anwenden, aber wenn es im Rahmen der Numerik um diskretisierte Punkte geht, dann bist du glaube ich mit dem Indexzugriff des Vektors am Besten bedient.

    Es geht mir nicht um "modisch". Sondern

    for(auto x : X)
    

    drückt auch offensichtlich aus, dass über alle Indizes
    iteriert wird, es gibt ja auch Algorithmen, wo nur manche
    Indizes benutzt werden, oder durcheinander usw.

    Ich verstehe deinen Punkt nicht so recht.

    Wenn ich z.B. x- und y-Werte jeweils in einem std::vector speichere, dann kann ich auch einfach mittels

    for(unsigned int i = 0; i < vec.size(); ++i)
    // do something for each i
    

    arbeiten. Ich weiß nicht so recht, was daran jetzt weniger intuitiv sei als die Verwendung des ranged for loop? 😕

    Gruß,
    -- Klaus.



  • Ich verstehe deinen Punkt nicht so recht.

    Wenn ich z.B. x- und y-Werte jeweils in einem std::vector speichere, dann kann ich auch einfach mittels

    for(unsigned int i = 0; i < vec.size(); ++i)
    // do something for each i
    

    arbeiten. Ich weiß nicht so recht, was daran jetzt weniger intuitiv sei als die Verwendung des ranged for loop? 😕

    Es geht nicht um richtig oder falsch, bis auf den 😮 oben, sondern um "eindeutig". Die Ausdrücke in der runden Klammer bei for() können ja auch komplizierter oder länger sein. Bei der range-based-for-loop sehe ich ohne Brille aus 2 m Entfernung (das war einmal: also 70 cm Entfernung), dass über das Ganze drübergezogen wird, bei der herkömmlichen for-loop muss man, je nachdem, genauer hinsehen. Es dokumentiert sich sozusagen selbst und völlig eindeutig. Mit dem Komma Separator kann man ja so manches machen und ich war nicht sicher, ob der nicht auch bei der range-based-for-loop zum Einsatz kommen kann, nun offenbar nicht.
    for_each arbeitet mit Iteratoren, das sind im Prinzip Adressen und für die numerische Bearbeitung braucht man aber den Index, der, wie oben dargelegt, in Assembler weitere Operationen benötigt, um aus den Adressen gewonnen zu werden, z. B.

    Pseudo-Assembler:
    index = (address - base_address) / size
    

    Je nach Maschine kann das länger dauern, oder auch gar nicht länger (mehrere Rechenwerke). Ich wollte das einfach einmal wissen. Wirkliche Numerik (z.B. FFT) hat z. T. wüste Index-Berechnungen, der Code dafür ist immer "häßlich" und C-like, Abstraktionen helfen da nichts, das liegt in der Natur der Sache.

    Danke für alle Antworten, es ist nicht in jedem Forum so, dass die Antworter mehr wissen, als der Frager, das Gegenteil ist sehr oft der Fall.



  • wastl schrieb:

    Nexus schrieb:

    Oder schreib dir einen Adapter für eine Range, der beim Dereferenzieren ein std::pair<double, std::size_t> oder ein eigenes Objekt dafür zurückgibt. Würde ich aber nur machen, wenn ich gute Gründe dafür hätte, denn der Code wird für andere Leute nicht unbedingt verständlicher.

    und schneller laufen wird es wohl auch nicht...

    Langsamer aber auch nicht.



  • Arcoth schrieb:

    so wäre es wenigstens lokal:

    Ja, aber extrem hässlich. Wie willst du den Code mehrmals durchlaufen?

    Wenn du die Indize brauchst, mach es doch ganz normal (wie in Variante 3). Wo liegt das Problem? Oder, wenn du richtig geil auf range-based for bist, dann nimm boost::irange (wie kürzlich im Forum demonstriert).

    for( auto i : boost::irange<int>(0, X.size()) )
        X[i] = sin(k*i);
    

    Jo.
    Irgendwie finde ich http://perldoc.perl.org/functions/keys.html hübsch, in C++ wäre das wohl

    for( auto i : keys(X) )
        X[i] = sin(k*i);
    

    Der set::iterator, hat der nicht so ein schönes first und second? Eih, verwirrend, daß Iteratoren mal Durchrenner, mal Ende-Marken und mal Suchergebnisse sind.



  • Jeder Vektor sollte doch auch einen Iterator haben, den man anfordern kann.
    Mit diesem Iterator hat man dann auch den Index.



  • MC78 schrieb:

    Jeder Vektor sollte doch auch einen Iterator haben, den man anfordern kann.
    Mit diesem Iterator hat man dann auch den Index.

    Wie sähe das z. B. aus?


  • Mod

    Das ist schnell geschrieben:

    template <typename C>
    boost::integer_range<typename C::size_type> keys( C const& c )
    {
    	return {0, c.size()};
    }
    
    template <typename C, std::size_t N>
    boost::integer_range<C> keys( C (&arr)[N] )
    {
    	return {0, N};
    }
    

    Übrigens habe ich gerade einen lust'gen Bug bei Clang entdeckt, bei folgendem Code

    #include <boost/range/irange.hpp>
    
    template <typename C>
    auto keys( C const& c ) -> boost::irange<typename C::size_type>
    {
    	return {0, C.size()};
    }
    
    int main()
    {
    	int arr[3];
    	keys(arr);
    }
    

    Der ist so lustig, weil er nur gelegentlich auftritt. Kompiliert das mal zehn mal hintereinander, und seht, wie oft Clang abkrazt. Bei mir ist es ca. 4 mal.



  • Ich finde for-range im Moment nur bedingt einsatzfähig. Ich würde es gern für viel mehr Fälle einsetzen.

    Meine Hoffnung ist, dass spätestens bei C++17 dann auch Hilfsmittel in der Art von Boost-Range-Adaptern dabei sind, über die ich z.B. einen Index oder einen "Reißverschluss" bekomme:

    Beispiel für das Mitzählen

    vector<double> x;
    for (auto t : counted(x)) {
       // t könnte hier ein tuple<decltype(x.size()),double&> sein
       cout << "Der " << (1+get<0>(t)) << ". Eintrag ist "
            << get<1>(t) << endl;
    }
    

    Beispiel für zip

    vector<double> vecx;
    vector<double> vecy;
    for (auto t : zip(vecx,vecy)) {
       // t könnte hier ein tuple<double&,double&> sein
       cout << get<0>(t) << ", " << get<1>(t) << endl;
    }
    

    ...wobei ich mir noch nicht im Klaren darüber bin, ob man hier wirklich "Referenz-Tupel" will oder nicht. Ich favorisiere T = decltype((*iterator)) für tuple<...,T,...> , denke ich.

    Und wer sich hier jetzt hat inspirieren lassen und wegen Langeweile so etwas basteln will, könnte auch noch hier für noch mehr Inspiration schauen.



  • Es wurde richtig bemerkt, dass man mit static vorsichtig sein soll, weil das Programmstück nur beim ersten und einzigen Durchlauf funktioniert. Der Grund ist ja der, dass solche Variablen nicht auf dem Stack angelegt werden, sondern zu Start des Programms irgendwo im Freispeicher und dabei auch inititalisiert werden und somit bei Wiedereintritt in den Block keine weitere Initialisierung auftritt.

    Wenn garantiert ist, dass der Block nur ein einziges Mal ausgeführt wird, ist das ok, z.B. wenn der Block kein Funktionskörper ist, sondern einfach so im Hauptprogramm steht, wie das bei einer Initialisierung sein kann.

    Verwendet man z. B. ein Funktionsobjekt, dass den Index mitzählen und die Berechnungen durchführen soll, so tritt genau dieser Effekt auch ein, dass nämlich bei der Konstruktion des Objektes ein einziges Mal inititalisiert wird und sonst nie wieder. Man muss also auch hier ggf. die Lebensdauer des Objektes begrenzen, damit bei Wiederholung kein Unsinn passiert.

    {
    for(auto & x : X)
       {
       static unsigned i = 0;
       x = somefunc(i++);
       }
    }
    

    ist also ähnlich wie:

    class funcobj
       { 
       public: 
       funcobj():i(0){};
       float operator()(){return somefunc(i++);};
       private:
       unsiged i;
       };
    
    ...
    
    funcobj gen;
    generate(X.begin(),X.end(),gen);
    generate(X.begin(),X.end(),gen);  //??
    

    Es ist ganz interessant, dass in Büchern steht, man nehme halt ein Funktionsobjekt, aber über die Wiederholung steht da nichts.



  • Die Laufzeiten sind auch ganz interessant.
    Es scheint so, als haben die Lösungen mit range-based-for und generate die Nase weit vorne, mit ca. nur 30% der Zeit, die die klassische for-Schleife mit oder ohne Iteratoren braucht. Natürlich ist das bei size() = 100 uninteressant. Aber ich nehme ja C++, weil die Problemgröße interessant ist, also eher size() > 1e8.



  • wastl schrieb:

    Es ist ganz interessant, dass in Büchern steht, man nehme halt ein Funktionsobjekt, aber über die Wiederholung steht da nichts.

    Dann erzeugt man eben ein neues.



  • kkaw schrieb:

    Beispiel für das Mitzählen

    vector<double> x;
    for (auto t : counted(x)) {
       // t könnte hier ein tuple<decltype(x.size()),double&> sein
       cout << "Der " << (1+get<0>(t)) << ". Eintrag ist "
            << get<1>(t) << endl;
    }
    

    Das ist aber etwas unschön mit dem Tupel. Lieber ein Typ mit verständlich benannten Elementen:

    vector<double> x;
    for (auto t : counted(x)) {
       // t könnte hier ein tuple<decltype(x.size()),double&> sein
       cout << "Der " << (1+t.index) << ". Eintrag ist "
            << t.value << endl;
    }
    


  • ➡

    vector<double> x{1.1,2.2,3.3,4.4,5.5,6.6};
    auto indexed = x | boost::adaptors::indexed(1);
    for (auto t : boost::irange(begin(indexed), end(indexed)))
        cout << "Der " << t.index() << ". Eintrag ist "
             << *t << endl;
    


  • TyRoXx schrieb:

    wastl schrieb:

    Es ist ganz interessant, dass in Büchern steht, man nehme halt ein Funktionsobjekt, aber über die Wiederholung steht da nichts.

    Dann erzeugt man eben ein neues.

    Genau. Ich habe es nur erwähnt, weil sich Leute über ein Beispiel furchtbar aufgeregt haben, dass man ebenso betitteln könnte: einmal ausführren, wenn zweimal, dann Block erneut hinschreiben (man braucht eine Inititalisierung nur einmal, aber egal...)


Anmelden zum Antworten