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



  • Badestrand schrieb:

    edit: Ach, geht so ja gar nicht. Die Kommata im Array-Aufruf zählen ja als Makro-Parameter..

    Das gilt nicht, wenn sie in einer zusätzlichen Klammer stehen. Aber das funktioniert trotzdem nicht immer, da die Klammern bei der Makro-Expansion ebenfalls berücksichtigt werden.

    #define MAKRO(EXPR) EXPR
    
    int main()
    {
    	MAKRO(std::pair<int, std::string>) p;
    	// funktioniert nicht, da das als zwei Argumente angesehen wird
    
    	MAKRO((std::pair<int, std::string>)) q;
    	// funktioniert ebenfalls nicht, da das Makro zu folgendem expandiert:
    	(std::pair<int, std::string>) q; // keine Deklaration, sondern Cast
    }
    


  • Der Code macht mir Angst.



  • volkard schrieb:

    Der Code macht mir Angst.

    Mir auch, da muss ich noch einiges Ummodeln (vor allem umschachteln und passende Namen finden). Erstmal aber sollte er nur funktionieren 🙂



  • So, ist schlanker geworden:

    namespace ForeachLoopHelper
    {
    	// Holds the range where the foreach-loop iterates over
    	template<typename Iter>
    	struct RefData
    	{
    		Iter cur, end;
    
    		RefData( Iter cur_, Iter end_ )
    			: cur(cur_)
    			, end(end_)
    		{
    		}
    	};
    
    	// Holds the range as well as a moved instance of the original container
    	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();
    		}
    
    		ConstData( ConstData&& other )
    		{
    			using std::swap;
    			swap( container, other.container );
    			cur = container.begin();
    			end = container.end();
    		}
    
    	private:
    		ConstData( const ConstData& );
    	};
    
    	// Temporary containers will be moved into ConstData.
    	// Note that we could typedef the iterator in 'ConstData' but we need SFNIAE
    	//   here for unambiguousness with 'createData( T& )'.
    	template<typename T>
    	ConstData<T, typename T::const_iterator> createData( T&& arr )
    	{
    		return ConstData<T, typename T::const_iterator>( arr );
    	}
    
    	// Non-const containers will be held by reference since they are guaranteed to
    	//   be valid during the foreach-loop.
    	template<typename T>
    	RefData<typename T::iterator> createData( T& arr )
    	{
    		return RefData<typename T::iterator>( arr.begin(), arr.end() );
    	}
    
    	// Const-containers won't be temporary values (we have the rvalue-overload) so we can provide the iterators again.
    	template<typename T>
    	RefData<typename T::const_iterator> createData( const T& arr )
    	{
    		return RefData<typename T::const_iterator>( arr.begin(), arr.end() );
    	}
    
    	// Ordinary arrays will be held by reference since they can not be temporary.
    	template<typename T, size_t size>
    	RefData<T*> createData( T(&arr)[size] )
    	{
    		return RefData<T*>( &arr[0], &arr[0]+size );
    	}
    }
    
    /* 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
    */
    #define foreach( iter, container )                                                                                                                                        \
                                                                                                                                                                              \
        /* This loop runs just once, it exists for declaring and initalising 'foreach_inner_loop_done' only */                                                                \
        for ( bool foreach__inner_loop_done=true, foreach__once=true; foreach__once; foreach__once=false )                                                                    \
                                                                                                                                                                              \
            /* Main loop. 'foreach_inner_loop_done' gets false only when the user's loop-code called break */                                                                 \
            for ( auto foreach__data( ForeachLoopHelper::createData(container) );  foreach__data.cur!=foreach__data.end && foreach__inner_loop_done;  ++foreach__data.cur  )  \
                                                                                                                                                                              \
                /* Runs just once, it resets 'foreach__inner_loop_done' to 'false' */                                                                                         \
                for ( foreach__inner_loop_done=false, foreach__once=true; foreach__once; foreach__once=false )                                                                \
                                                                                                                                                                              \
                    /* This loop runs just once, it exists for declaring a reference to the current element for the user */                                                   \
                    for ( auto& iter=*foreach__data.cur; !foreach__inner_loop_done; foreach__inner_loop_done=true )
    

    Anmerkungen und Kritik gerne willkommen.

    edit: So, ab jetzt werden keine Kopien mehr angelegt, da temporäre Objekte (werden ge-move-t) und konstante Container dank RValue-Referenzen auseinandergehalten werden können.



  • Wow, und der VS10-Compiler optimiert gut:

    int arr[] = { 12, 13, 14, 15 };
    foreach ( i, arr )
    	std::cout << i << "\n";
    

    ➡

    ; 233  : 	int arr[] = { 12, 13, 14, 15 };
    
    	mov	DWORD PTR _arr$[esp+24], 12		; 0000000cH
    	mov	DWORD PTR _arr$[esp+28], 13		; 0000000dH
    	mov	DWORD PTR _arr$[esp+32], 14		; 0000000eH
    	mov	DWORD PTR _arr$[esp+36], 15		; 0000000fH
    
    ; 234  : 	foreach ( i, arr )
    
    	mov	al, 1
    	lea	esi, DWORD PTR _arr$[esp+24]
    	npad	5
    $LL9@main:
    	test	al, al
    	je	SHORT $LN27@main
    
    ; 235  : 		std::cout << i << "\n";
    
    	mov	eax, DWORD PTR [esi]
    	push	eax
    	call	??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z ; std::basic_ostream<char,std::char_traits<char> >::operator<<
    	push	eax
    	call	??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >
    	add	esi, 4
    	lea	ecx, DWORD PTR _arr$[esp+44]
    	add	esp, 4
    	mov	al, 1
    	cmp	esi, ecx
    	jne	SHORT $LL9@main
    
    $LN27@main:
    


  • Badestrand schrieb:

    Wow, und der VS10-Compiler optimiert gut:

    Ja, die Zuweisungen an die Steuervariablen sind konstant und die Sprünge der Einmal-Schleifen sind zwangsläufig. Daß die nicht teuer werden, hatte ich angenommen.

    Frag ihn mal, was draus wird, wenn Du if(!time(0)) continue reinmachst. Wird daraus ein sinnvoller Sprung, oder fängt er an, mit Variablen zu hanseln?



  • volkard schrieb:

    Frag ihn mal, was draus wird, wenn Du if(!time(0)) continue reinmachst. Wird daraus ein sinnvoller Sprung, oder fängt er an, mit Variablen zu hanseln?

    Sieht sinnvoll aus 👍 Drei Anweisungen für den Aufruf (push, call, Stack justieren) und zwei für's Vergleichen+Springen. Sonst alles gleich.



  • Fein.
    Dann muß ja nurnoch die Kopie des nicht-temporären const-Containers wegfallen. Und da Du nur auto verwendet hast, was mit dem alten (proprietären) typeof auch ginge, wäre das ganze schon seit Jahren möglich gewesen. Wie traurig.



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


Anmelden zum Antworten