foreach( i, Array(1,2,3,4,5) )
-
Ok, break und continue klappen jetzt soweit. Non-const-Container werden jetzt auch nicht mehr kopiert, sondern per Pointer gehalten, auch damit man die Elemente in der Schleife modifizieren kann. Der Code ist teilweise noch mehr oder weniger hässlich und unverständlich, aber er funktionert (Anwendung wie gehabt)
template<typename Elem, typename Container, typename Iter> struct ForeachLoopHelperStruct { Container elems; Container* foreign; Iter cur, end; bool outer_loop_condition; int ds; ForeachLoopHelperStruct( const Container& elems_ ) : elems(elems_) , foreign(NULL) , outer_loop_condition(true) { cur = elems.begin(); end = elems.end(); } ForeachLoopHelperStruct( Elem* elems_, size_t count ) : elems(elems_) , foreign(NULL) , outer_loop_condition(true) { cur = elems; end = elems + count; } ForeachLoopHelperStruct( Container* elems_ ) : elems() , foreign(elems_) , cur(elems_->begin()) , end(elems_->end()) , outer_loop_condition(true) { } void outer_init() { ds = 2; } bool outer_cond() { return cur!=end && ds==2; } void outer_next() { ++cur; } Elem& inner_init() { ds = 0; return *cur; } bool inner_cond() { return ++ds < 2; } }; template<typename T> struct ForeachLoopHelperMakeConst { typedef const T type; }; template<typename T> ForeachLoopHelperStruct<typename T::value_type, T, typename T::iterator> createForeachLoopHelperStruct( T& arr ) { return ForeachLoopHelperStruct<typename T::value_type, T, typename T::iterator>( &arr ); } template<typename T> ForeachLoopHelperStruct<typename ForeachLoopHelperMakeConst<typename T::value_type>::type, T, typename T::const_iterator> createForeachLoopHelperStruct( const T& arr ) { return ForeachLoopHelperStruct< ForeachLoopHelperMakeConst<typename T::value_type>::type, T, typename T::const_iterator>( arr ); } template<typename T, size_t size> ForeachLoopHelperStruct<T,T*,T*> createForeachLoopHelperStruct( T(&arr)[size] ) { return ForeachLoopHelperStruct<T,T*,T*>( arr, size ); } template<typename T> struct ForeachLoopHelperGetRef; template<typename T> struct ForeachLoopHelperGetRef<const T&> { typedef const T& ref; }; template<typename T> struct ForeachLoopHelperGetRef<T&> { typedef T& ref; }; template<typename T> struct ForeachLoopHelperGetRef<const T*> { typedef const T& ref; }; template<typename T> struct ForeachLoopHelperGetRef<T*> { typedef T& ref; }; #define foreach( iter, container ) \ for ( auto a123(createForeachLoopHelperStruct(container)); a123.outer_loop_condition; a123.outer_loop_condition=false ) \ for ( a123.outer_init(); a123.outer_cond(); a123.outer_next() ) \ for ( ForeachLoopHelperGetRef<decltype(*a123.cur)>::ref iter=a123.inner_init(); a123.inner_cond(); ) template<typename T> std::vector<T> Array( T a, T b, T c, T d ) { std::vector<T> elems; elems.push_back( a ); elems.push_back( b ); elems.push_back( c ); elems.push_back( d ); return elems; }
-
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 lieberint 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.