foreach( i, Array(1,2,3,4,5) )
-
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.
-
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 mitBOOST_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