foreach( i, Array(1,2,3,4,5) )



  • volkard schrieb:

    Dann muß ja nurnoch die Kopie des nicht-temporären const-Containers wegfallen.

    Klappt jetzt auch, danke den RValue-Referenzen. Hab's oben rein-editiert.



  • Badestrand schrieb:

    volkard schrieb:

    Dann muß ja nurnoch die Kopie des nicht-temporären const-Containers wegfallen.

    Klappt jetzt auch, danke den RValue-Referenzen. Hab's oben rein-editiert.

    Aber dann muß der Container in seinem eigenen Code irgendwas bestimmtes mit RValue-Referenzen, damit das klappt, oder?



  • volkard schrieb:

    Aber dann muß der Container in seinem eigenen Code irgendwas bestimmtes mit RValue-Referenzen, damit das klappt, oder?

    Hm, verstehe deine Frage nicht ganz (fehlt da nicht ein Verb?).

    Ich hab mit den RValue-Referenzen ein wenig herumgespielt und soweit ich herausfinden konnte, wird eine Funktion, die RValue-Referenzen annimmt, nur aufgerufen, wenn der übergebene Wert sonst nicht mehr angesprochen werden kann (also eine temporäre Kopie ist). Das macht die Unterscheidung dann ja eindeutig. Und der Container muss swap unterstützen, aber das machen ja hoffentlich alle. Ich murkse es sicherheitshalber aber mal auf das "sichere" swappen um ( using std::swap; swap(...); ).



  • Was spricht denn gegen sowas:

    a = array[1,2,3,4]
    std::for_each(a.begin(), a.end(), Ausgabefunktor)
    

    oder:

    int a[5] = {1,2,3,4,5}
    std::for_each(a, a+5, Ausgabefunktor)
    

    Wobei Ausgabefunktor auch ein Lambdaausdruck(C++0x) sein kann.

    Entschuldigung, ich habe den Thread nur angerissen, da der Beispielcode mich dermassen abgeschreckt hat und ich mich fragte, warum nur ...



  • knivil schrieb:

    Was spricht denn gegen sowas:

    a = array[1,2,3,4]
    std::for_each(a.begin(), a.end(), Ausgabefunktor)
    

    Das Ergebnis.

    foreach(i,a)
       cout<<i;
    

    Wie sähe noch Deine Schleife komplett aus, also mit der Definition des Ausgabefunktors?
    Übrigens benutzt Du bei "a = array[1,2,3,4]" den Kommaoperator. Das geht in C++ anders.

    knivil schrieb:

    Entschuldigung, ich habe den Thread nur angerissen, da der Beispielcode mich dermassen abgeschreckt hat und ich mich fragte, warum nur ...

    Beispielcode?

    /* Usage-example:
    int vals[] = { 1, 2, 3, 4 };
    foreach ( i, vals )
        i *= 2
    // vals == {2,4,6,8} now
    
    const std::vector<std::string> caps;
    foreach ( s, caps )
        cout << s << endl;        // Modification not allowed here
    */
    

    Den finde ich gar nicht so schlimm.



  • Übrigens benutzt Du bei "a = array[1,2,3,4]" den Kommaoperator. Das geht in C++ anders.

    Deswegen habe ich auch nur code-tags verwendet, nicht cpp-tags. Ich war zu faul, den Quelltext durch einen Compiler auf Syntax zu pruefen. Ich wollte das Prinzip nur verdeutlichen.

    class IndexOut
    {
        private:
            int current_;
    
        public:
            IndexOut(int current) : current_(current) {}
            void operator()(int ele)
            {
                std::cout << current_ << " " << ele << std::endl;
                ++current_;
            }
    };
    
    int main()
    {
        int a[5] = {1,2,3,4,5};
        std::for_each(a, a+5, IndexOut(0) );
        return 0;
    }
    

    IndexOut kann in einen Lambdaausdruck verpackt werden, damit nicht immer eine Klasse fuer den Funktor geschrieben werden muss. Mit der Syntax befasse ich mich aber erst, wenn C++0x heraus ist. Auch eine Modifikation der Elemente sollte auf diese Weise problemlos moeglich sein, wenn Referenzen im operator()( ... ) verwendet werden.

    Ich hoffe, das loest das Orginalproblem (ganz ohne Makros - sie sind boese 🙂 ). Auch ist es bestimmt nicht die einzige Loesung dieser Art.



  • Badestrand schrieb:

    Ich hab mit den RValue-Referenzen ein wenig herumgespielt und soweit ich herausfinden konnte, wird eine Funktion, die RValue-Referenzen annimmt, nur aufgerufen, wenn der übergebene Wert sonst nicht mehr angesprochen werden kann.

    Das kommt ganz darauf an, welche Version von Rvalue-Referenzen Du meinst. Die alte Version konnte noch Lvalues binden. Und genau das nutzt Du sogar aus:

    Badestrand schrieb:

    template<typename Container, typename Iter>
    struct ConstData
    {
        Container container;
        Iter      cur, end;
    
        ConstData( Container&& container_ )
        {
            using std::swap;
            swap( container, container_ );
            cur = container.begin();
            end = container.end();
        }
    
        .....
    };
    
    .....
    
    template<typename T>
    ConstData<T, typename T::const_iterator> createData( T&& arr )
    {
       return ConstData<T, typename T::const_iterator>( arr );
    }
    

    Benannte Rvalue-Referenzen werden wie Lvalue-Ausdrücke behandelt. Es müsste also heißen:

    template<typename T>
    ConstData<T, typename T::const_iterator> createData( T&& arr )
    {
       return ConstData<T, typename T::const_iterator>( std::move(arr) );
    }
    

    In Deinem Fall kompiliert's trotzdem, weil Dein Compiler auch Rvalue-Referenzen mit Lvalue-Ausdrücken initialisieren kann. Die Gefahr ist dabei natürlich, dass man versehentlich Objekte ändert, die noch gebraucht werden. Deswegen solltest Du auch einen privaten Konstruktor erzeugen, der "T const&" akzeptiert -- zumindest solange Du noch mit einem Compiler arbeitest, der die alten Regeln verwendet.

    Hier ein kleiner Überblick:

    void foo(int const&);  // #1
    void foo(int &&);      // #2
    
    void bar(int && t) {   // #3
      foo(t);            // ->#1
      foo(move(t)); // ->#2
    }
    
    int main() {
      int i=23;
      foo(i);       // ->#1
      foo(42);      // ->#2
      bar(i);       // ->#3 (alt),  ill-formed (neu)
      bar(move(i)); // ->#3
      bar(42);      // ->#3
    }
    

    Siehe dazu
    N2027: A Brief Introduction to Rvalue References (alte Regeln)
    N2812: A Safety Problem with Rvalue References
    N2844: Fixing a Safety Problem with Rvalue References
    Rvalue References 101 (neue Regeln, super Text von Dave Abrahams)

    Gruß,
    SP



  • knivil schrieb:

    Ich hoffe, das loest das Orginalproblem (ganz ohne Makros - sie sind boese 🙂 ). Auch ist es bestimmt nicht die einzige Loesung dieser Art.

    Dein Code ist so unglaublich viel schlimmer als der Beispielcode...

    Dein Vorschlag

    class IndexOut 
    { 
        private: 
            int current_; 
    
        public: 
            IndexOut(int current) : current_(current) {} 
            void operator()(int ele) 
            { 
                std::cout << current_ << " " << ele << std::endl; 
                ++current_; 
            } 
    }; 
    
    ...
        std::for_each(a, a+5, IndexOut(0) );
    

    Badestrands Ziel:

    int index=0;
    foreach(ele,a)
    {
       cout<<index<<' '<<ele<<'\n';
       ++index;
    }
    

    Was die Schleife tut, sollte auch in die Schleife geschrieben werden, nicht irgendwo vor der ganzen Funktion.

    Die Makros innendrin sind Implemtierungsdetails und sollten Dich eher weniger jucken.



  • Entweder habe ich das Ausgangsproblem nicht verstanden, oder aber ihr versteht mich nicht.

    Was die Schleife tut, sollte auch in die Schleife geschrieben werden, nicht irgendwo vor der ganzen Funktion.

    Das ist Ansichtssache und ist in anderen Programmiersprachen Gang und Gaebe (Lisp/Scheme). Das der Schleifencode ueber einen separaten Funktor (der durch eine Klasse) realisiert wird, ist der Nachteil des aktuellen C++. Was spricht gegen lambda-Funktionen/Ausdruecke (oder wie auch immer das dort genannt wird) in C++0x?

    Die Makros innendrin sind Implemtierungsdetails und sollten Dich eher weniger jucken.

    Doch, immer wenn ich bestimmte Teile von Boost mir ansehe, kann ich nur den Kopf schuetteln. Nein, das ist nicht mein Weg.



  • knivil schrieb:

    Was spricht gegen lambda-Funktionen/Ausdruecke (oder wie auch immer das dort genannt wird) in C++0x?

    Das würdest Du eh nicht verstehen, wenn Du es bevorzugst, so eine Klasse wie oben zu schreiben.
    Badestrands Aufrufsyntax ist einfach die bessere. So ein ordentliches foreach ist übrigens auch in anderen Sprachen gang und gäbe.
    Und auf jeden Fall lieber

    int index=0;
    for(auto ele=a.begin();ele!=a.end();++ele)
    {
       cout<<index<<' '<<*ele<<'\n';
       ++index;
    }
    

    als die Klassenlösung.

    Jetzt ist es schon soweit gekommen, daß eine ausgesprochene Spiel- und Experimentierlösung vom typischen C++-Posting "Machs doch lieber viel umständlicher! Wenn Du so weitermachst, könnte jemand das Programm lesen können! Ich kenne da einen Weg, wie der Code schlechter lesbar wird." bedrängt wird.



  • Das würdest Du eh nicht verstehen, wenn Du es bevorzugst, so eine Klasse wie oben zu schreiben.

    Jetzt werde aber nicht persoenlich. Lambda ist gerade dazu da, Funktoren (in anderen Programmiersprachen auch Closures genannt) nicht mehr ueber separate Klassen zu realisieren, sondern an Ort und Stelle.

    Badestrands Aufrufsyntax ist einfach die bessere. So ein ordentliches foreach ist übrigens auch in anderen Sprachen gang und gäbe.

    Was besser ist, das ist Ansichtssache. Ein foreach wie in anderen Sprachen finde ich aber in C++ als Syntaxelement a la "foreach i in [a,b,c,d]" unnoetig (es gibt std::for_each). Auch ist das Ausgeben der einzelnen Elemente ein recht triviales Beispiel fuer den Einsatz von foreach, std::for_each. Wozu sind denn sonst die Algorithmen wie unique, sort, replace, remove, count, accumulate ... und natuerlich for_each in <algorithm> definiert? Wozu soll man noch ein foreach selbst schreiben, wenn schon eins zur Verfuegung gestellt wird?

    Spiel- und Experimentierlösung

    Leider finden diese Loesungen Einzug in echten Code, der irgendwann gewartet werden muss. So in der Art: Ich ignoriere Bestehendes und mache es einfach wie in C# (oder Perl oder ...). Da es anscheinend Mode geworden ist, auf andere Threads zu verweisen: http://www.c-plusplus.net/forum/viewtopic-var-t-is-245047.html

    Nun da die Lösung aber existiert, werde ich sie auch in Zukunft verwenden.



  • knivil schrieb:

    Wozu sind denn sonst die Algorithmen wie unique, sort, replace, remove, count, accumulate ... und natuerlich for_each in <algorithm> definiert? Wozu soll man noch ein foreach selbst schreiben, wenn schon eins zur Verfuegung gestellt wird?

    Die können nix. Sind doch nur Standard-Sachen im Sinne von häufig-benötigte Sachen.

    Neulich von mir:

    int count=0;
    for(map<string,u64>::iterator i=siteToTotalGameTime.begin(),e=siteToTotalGameTime.end();i!=e;++i)
    	if(i->second==minTime and rand()%++count==0)
    		minPlayer2=i->first;
    

    gewünscht hätte ich mir

    int count=0;
    for(player,time,siteToTotalGameTime)
    	if(time==minTime and rand()%++count==0)
    		minPlayer2=player;
    

    mit std::foreach ohne lamdas möchte ich mir nicht ausmalen, wie das werden soll.

    allerdings haben lamdas vortiele, die nicht zu unterschätzen sind: man kann container mit lamda-foreach anbieten und dafür auf iteratoren verzichten. das könnte bäume und manche hashtables viel leckerer machen.

    Spiel- und Experimentierlösung

    Leider finden diese Loesungen Einzug in echten Code, der irgendwann gewartet werden muss. So in der Art: Ich ignoriere Bestehendes und mache es einfach wie in C# (oder Perl oder ...).

    Klar.



  • knivil, warum bist du immer so negativ gegenüber Lösungen eingestellt, die wunderbar anwendbar sind, aber halt entsprechenden Code für die Implementierung benötigen? Schon letztes Mal mit meinem generischen Sortierfunktor. Oder in deinem verlinkten Thread. Kein Wunder, dass du Boost nicht magst, wenn du etwas gegen kompliziertere Implementierungen hast.

    knivil schrieb:

    Was besser ist, das ist Ansichtssache. Ein foreach wie in anderen Sprachen finde ich aber in C++ als Syntaxelement a la "foreach i in [a,b,c,d]" unnoetig (es gibt std::for_each). Auch ist das Ausgeben der einzelnen Elemente ein recht triviales Beispiel fuer den Einsatz von foreach, std::for_each. Wozu sind denn sonst die Algorithmen wie unique, sort, replace, remove, count, accumulate ... und natuerlich for_each in <algorithm> definiert? Wozu soll man noch ein foreach selbst schreiben, wenn schon eins zur Verfuegung gestellt wird?

    Weil es anders angewandt wird? Man muss nicht immer funktional programmieren. Eine Schleife bietet oft viel mehr Flexibilität. Eine Lambda-Expression, die Code über mehrere Zeilen besitzt, finde ich zum Beispiel etwas fragwürdig.

    Ein kurzes foreach(elem, container) ist sehr übersichtlich, da man wie in einer normalen Iterierschleife arbeiten kann, aber den Schleifenkopf nicht mit unnötigen Informationen vollstopft. Ich arbeite jetzt schon die ganze Zeit mit BOOST_FOREACH , weil ich es sehr praktisch finde.



  • knivil, warum bist du immer so negativ gegenüber Lösungen eingestellt, die wunderbar anwendbar sind, aber halt entsprechenden Code für die Implementierung benötigen?

    Tut mir leid. Vielleicht bin etwas sehr kritisch ...



  • Die Frage war ja, wie man mit so etwas ähnliches wie

    foreach( i, Array(1,2,3,4,5) )
        std::cout << i << "\n";  //Oder auch "*i"
    

    mit C++0x-Bordmitteln erreichen kann. Die Lösung ist recht einfach:

    for(int i : {1,2,3,4,5})
        std::cout << i << "\n";
    

    Hier wird die "for-range" Schleife zusammen mit "initializer lists" ausgenutzt. Da die Elemente im Beispiel aufeinanderfolgende Ganzzahlen sind, frage ich mich, warum Badestrand nicht gleich

    for(int i=1; i<=5; ++i)
        std::cout << i << "\n";
    

    schreiben will. Aber nagut...

    Ohne for-range loop und ohne initializer lists ist es aufwändiger. Ich würde hierbei trotzdem nicht auf Makros zugreifen sondern eher etwas schreiben wie

    template<typename T>
      inline void multi_push_back(std::vector<T> & vec) {}
    
      template<typename T, typename U, typename... V>
      inline void multi_push_back(std::vector<T> & vec, U && u, V && ... params)
      {
        vec.push_back(std::forward<U>(u));
        multi_push_back(vec,std::forward<V>(params)...);
      }
    
      template<typename T, typename... U>
      inline std::vector<T> make_vector(U && ... params)
      {
        std::vector<T> vec;
        vec.reserve( sizeof...(params) );
        multi_push_back(vec,std::forward<U>(params)...);
      }
    
      template<typename Container>
      inline auto begin(Container & c) -> decltype(c.begin())
      { return c.begin(); }
    
      template<typename Container>
      inline auto end(Container & c) -> decltype(c.end())
      { return c.end(); }
    
      template<typename Range, typename Functor>
      inline Functor for_range(Range && r, Functor && f)
      {
        auto       beg = begin(r);
        auto const end = end(r);
        while (beg!=end) {
          f(*beg);
          ++beg;
        }
        return std::forward<Functor>(f);
      }
    
      int main() {
        for_range(make_vector<int>(2,3,5,7,11,13,17),[](int i){
          std::cout << i << "\n";  
        });
      }
    

    Mit initializer lists aber ohne for-range loop kann man sich den ganzen make_vector-Code von oben sparen und das hier schreiben:

    int main() {
        for_range(std::vector<int>{2,3,5,7,11,13,17},[](int i){
          std::cout << i << "\n";  
        });
      }
    

    Gruß,
    SP



  • volkard schrieb:

    Neulich von mir:

    int count=0;
    for(map<string,u64>::iterator i=siteToTotalGameTime.begin(),e=siteToTotalGameTime.end();i!=e;++i)
    	if(i->second==minTime and rand()%++count==0)
    		minPlayer2=i->first;
    

    gewünscht hätte ich mir

    int count=0;
    for(player,time,siteToTotalGameTime)
    	if(time==minTime and rand()%++count==0)
    		minPlayer2=player;
    

    Das ist ein schönes Beispiel. Ich glaube, die for-each-Schleife von D kann sowas von Haus aus (Tupel auspacken). Mit der For-Range Schleife in C++0x geht das aber fast genauso:

    int count=0;
    for(auto & pair : siteToTotalGameTime)
    	if(pair.second==minTime and rand()%++count==0)
    		minPlayer2=pair.first;
    

    oder

    int count=0;
    string player;
    u64 time;
    for(tie(player,time) : siteToTotalGameTime)
    	if(time==minTime and rand()%++count==0)
    		minPlayer2=player;
    

    Allerdings verhindet nur die erste Version das unnötige Kopieren der Elemente. Hier ist die Annahme, dass operator= auf einem tuple<string&,u64&> mit einem pair<string,u64> als Parameter aufgerufen werden kann.

    Wir sollten uns vielleicht mal auf die "Regeln" einigen -- also welche C++0x features benutzt werden dürfen und welche nicht. 😉

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Da die Elemente im Beispiel aufeinanderfolgende Ganzzahlen sind, frage ich mich, warum Badestrand nicht gleich

    for(int i=1; i<=5; ++i)
        std::cout << i << "\n";
    

    schreiben will.

    Gemeint waren eigentlich beliebige Elemente (gleichen Typs). Die normale for-Schleife kenne ich schon noch 😉

    Sebastian Pizer schrieb:

    Die Lösung ist recht einfach:

    for(int i : {1,2,3,4,5})
        std::cout << i << "\n";
    

    Das ist schick! Ich programmiere aber haupsächlich mit dem Visual Studio, dessen Compiler in der neuen Version (10) leider keine Ranges oder Initialisierungs-Listen unterstützen wird.
    Und die hier entwickelte Lösung gefällt mir eigentlich ganz gut, das Makro finde ich hierbei auch nicht schlimm (die Ersetzung und Auswirkungen sind ja recht überschaubar).

    Sebastian Pizer schrieb:

    Wir sollten uns vielleicht mal auf die "Regeln" einigen -- also welche C++0x features benutzt werden dürfen und welche nicht. 😉

    Visual Studio 10 gibt die erlaubten Elemente vor (std::move hat er anscheinend, jedenfalls im Release Candidate 1, auch noch nicht. Ich hoffe hier auf die finale Version).

    knivil schrieb:

    Ein foreach wie in anderen Sprachen finde ich aber in C++ als Syntaxelement a la "foreach i in [a,b,c,d]" unnoetig (es gibt std::for_each)

    std::for_each kann man leider nicht mit temporären (also von einer Funktion zurückgegebenen) Containern anwenden 😕

    knivil schrieb:

    Leider finden diese Loesungen Einzug in echten Code, der irgendwann gewartet werden muss.

    Die Implementierung ist übersichtlicher als die von BOOST_FOREACH und testweise hab ich "mein" foreach mal in ein mittelkleines privates Projekt von mir eingebaut (ungefähr 100 Ersetzungen), gab keine Probleme (bei 'ner Menge verschiedener Datentypen und Container, über die iteriert wird)



  • Badestrand schrieb:

    Sebastian Pizer schrieb:

    Wir sollten uns vielleicht mal auf die "Regeln" einigen -- also welche C++0x features benutzt werden dürfen und welche nicht. 😉

    Visual Studio 10 gibt die erlaubten Elemente vor (std::move hat er anscheinend, jedenfalls im Release Candidate 1, auch noch nicht. Ich hoffe hier auf die finale Version).

    std::move sollte wie std::forward in <utility> sein. Ich kann's mir kaum vorstellen, dass VS10 Rvalue-Referenzen unterstützt, aber nicht std::move und std::forward anbietet. Variadische Templates kann es auch noch nicht, soweit ich weiß (gibts schon bei g++), aber dafür kann es Lambdas (noch nicht in g++).

    Im Notfall schreibst Du einfach static_cast<T&&>(dings) anstatt move(dings) für T=Typ des Ausdrucks "dings".

    Badestrand schrieb:

    Die Implementierung ist übersichtlicher als die von BOOST_FOREACH und testweise hab ich "mein" foreach mal in ein mittelkleines privates Projekt von mir eingebaut (ungefähr 100 Ersetzungen), gab keine Probleme (bei 'ner Menge verschiedener Datentypen und Container, über die iteriert wird)

    Ich kann mich trotzdem nicht mit Makros richtig anfreunden. Die versteht man meist nur, wenn man sie selbst geschrieben hat. 😉

    Gruß,
    SP



  • Badestrand schrieb:

    knivil schrieb:

    Ein foreach wie in anderen Sprachen finde ich aber in C++ als Syntaxelement a la "foreach i in [a,b,c,d]" unnoetig (es gibt std::for_each)

    std::for_each kann man leider nicht mit temporären (also von einer Funktion zurückgegebenen) Containern anwenden 😕

    Das Problem ist hier aber nicht, dass sie temporär sind, sondern dass man eigentlich 2 Funktionen aufrufen muss (begin und end). Mit einer eigenen for_range-Funktion, die nur einen Parameter für die Sequenz erwartet und nicht zwei Iteratoren, ist es dann auch mit Rvalue-Containern möglich:

    template<typename Sequence, typename Functor>
    inline Functor for_range(Sequence && seq, Functor f)
    {
      for(auto beg = seq.begin(),
               end = seq.end();
          beg!=end; ++beg)
      {
        f(*beg);
      }
      return std::move(f);
    }
    

    Dieses Template "frisst" so ziemlich alles und könnte auch mit Arrays klarkommen, wenn man bei begin()/end() auf freie Funktionen statt Elementfunktionen setzt (so, wie es jetzt für C++0x angedacht ist).

    Man beachte: Obwohl seq als Sequence&& deklariert wird und Rvalue-Referenzen nicht mehr Lvalues binden, ist das Funktions-Template trotzdem mit Lvalue-Containern verwendbar (template argument deduction rules + reference collapsing).

    Gruß,
    SP


Anmelden zum Antworten