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



  • Hi,

    ich versuche gerade (unter Verwendung von C++0x), Folgendes hinzubekommen:

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

    Hat zufällig jemand eine Idee? Mein Problem ist, dass ich mit dem "Array"-Aufruf zwar prima einen vector mit den entsprechenden Elementen zurückgeben kann, allerdings müsste ich den vector im ersten Teil der for-Schleife zwischenspeichern (weil ja "begin" und "end" Objekt-bezogen sind); da muss aber schon der Iterator hin.

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



  • zwei iddeen, um zwischenvariablen zu speichern. aber hier nur als zeiger oder non-const-ref.

    #define foreach(iter,cont) for(decltype(cont)* geheim=&cont;geheim;geheim=0) for(auto i=geheim->begin();i!=geheim->end;++i)
    

    oder um den compiler nicht mit der schleife zu verwirren, daß er aus versehen da noch zwei asm-befehle erzeugt:
    template<typename T>

    struct IfConstructionHolder
    {
    	T& data;
    	IfConstructionHolder(T& _data)
    	:data(_data){
    	}
    	operator bool(){
    		return false;
    	}
             T* operator->(){
                return &data;
             }
    };
    #define foreach(iter,cont) if(IfConstructionHolder<decl_type(cont)> geheim(cont));else for(auto i=geheim->begin();i!=geheim->end;++i)
    

    eine kopie könntest du holen per const-ref? oder sowas?



  • Badestrand schrieb:

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

    sicher?

    #include <iostream>
    using namespace std;
    
    #define foreach(iter,cont) cout<<#iter<<endl; cout<<#cont<<endl;
    
    int main()
    {
    	foreach(a,foo(1,2,3));
    

    ausgabe

    a
    foo(1,2,3)
    


  • volkard schrieb:

    sicher?

    Jetzt nicht mehr.. Muss ich gleich mal ein paar Sachen austesten.

    Deinen Vorschlag probiere ich gleich mal aus, danke. Mittlerweile hat bei mir jetzt aber auch was geklappt (Kommata statt Minuse sind natürlich schöner, wird vielleicht noch):

    #define foreach(iter,container)  if(auto asd=(container).get()) {} else for ( auto iter=asd.elems.begin(); iter!=asd.elems.end(); ++iter )
    
    template<typename T>
    struct Array
    {
    	Array& operator-( T t )  {elems.push_back( t );  return *this;}
    	Array<T> get()           {return *this;}
    	operator void*()         {return false;}
    
    	std::vector<T> elems;
    };
    
    int main()
    {
        foreach( i, Array<int>()-1-2-3-4-5 )
            std::cout << *i << "\n";
    }
    


  • Geilo, funktioniert. Das mit der doppelten for-Schleife war 'ne echt gute Idee.

    #define foreach(iter,container)  for (auto cont=container; cont.size(); cont.clear())  for (auto iter=cont.begin(); iter!=cont.end(); ++iter)
    
    template<typename T> std::vector<T> Array( T a, T b, T c )
    {
    	std::vector<T> elems;
    	elems.push_back( a );
    	elems.push_back( b );
    	elems.push_back( c );
    	return elems;
    }
    
    int main()
    {
    	foreach( i, Array(1,2,3) )
    		std::cout << *i << "\n";
    }
    

    Jetzt muss ich nur noch 'nen paar Überladungen schreiben 🤡



  • mir würde es besser gefallen, eine Range zu ziehen und laufen zu lassen, statt eines Iterators. dann ist auch das eingangsproblem und der untzerschied zwischen schon bestehenden und temporären containern auf ganz natürliche weise weg.

    #define foreach(iter,container) for(auto i=getContent(container);!i.empty();i.pop())
    


  • mir würde es besser gefallen, eine Range zu ziehen und laufen zu lassen, statt eines Iterators.

    Stimmt! Das mache ich dann aber morgen..



  • Ich konnte nicht aufhören :):

    #include <iostream>
    #include <vector>
    #include <string>
    #include <set>
    
    template<typename Elem, typename Container, typename Iter>
    struct ForeachLoopHelperStruct
    {
    	enum
    	{
    		RUN,
    		SOON_QUIT,
    		REALLY_QUIT
    	} state;
    
    	Container elems;
    	Iter      beg, end;
    
    	bool      outer_loop_condition;
    	bool      ds;
    
    	ForeachLoopHelperStruct( const Container& elems_ )
    		: elems(elems_)
    		, beg(elems.begin())
    		, end(elems.end())
    		, outer_loop_condition(true)
    		, state(elems.size()? RUN : REALLY_QUIT),
    		ds(true)
    	{
    	}
    
    	ForeachLoopHelperStruct( Elem* elems_, size_t count )
    		: elems(elems_)
    		, beg(elems)
    		, end(elems+count)
    		, outer_loop_condition(true)
    		, state(count? RUN : REALLY_QUIT),
    		ds(true)
    	{
    	}
    
    	Iter first()
    	{
    		return beg++;
    	}
    
    	bool valid()
    	{
    		return state != REALLY_QUIT;
    	}
    
    	Iter next()
    	{
    		if ( state == SOON_QUIT )
    		{
    			state = REALLY_QUIT;
    			return end;
    		}
    
    		Iter ret = beg;
    		if ( ++beg == end )
    			state = SOON_QUIT;
    		return ret;
    	}
    };
    
    template<typename T>
    ForeachLoopHelperStruct<typename T::value_type, T, typename T::iterator> createForeachLoopHelperStruct( const T& arr )
    {
    	return ForeachLoopHelperStruct<typename T::value_type, T, typename T::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;
    };
    
    #define foreach(iter,container)                                                                                    \
    for ( auto a(createForeachLoopHelperStruct(container)); a.outer_loop_condition; a.outer_loop_condition=false )	   \
    	for ( auto bal=a.first(); a.valid(); bal=a.next(), a.ds=true )                                                 \
    		for ( ForeachLoopHelperGetRef<decltype(*bal)>::ref iter=*bal; a.ds; a.ds=!a.ds )
    
    template<typename T> std::vector<T> Array( T a, T b, T c )
    {
        std::vector<T> elems;
        elems.push_back( a );
        elems.push_back( b );
        elems.push_back( c );
        return elems;
    }
    
    int main()
    {
    	std::set<std::string> l;
    	l.insert( "d" );
    	l.insert( "e" );
    	foreach ( i, l )
    		std::cout << i << std::endl;
    	std::cout << "\n";
    
    	int arr[] = { 12, 13, 14, 15 };
    	foreach ( i, arr )
    		++i;
    	foreach ( i, arr )
    		std::cout << i << std::endl;
    	std::cout << "\n";
    
    	foreach( i, Array(1,2,3) )
    		std::cout << i << "\n";
    	std::cout << "\n";
    
    	const std::vector<std::string> k = Array<std::string>( "a", "b", "c" );
    	foreach ( i, k )
    		std::cout << i << "\n";
    	std::cout << "\n";
    }
    

    Das doofe ist nur: Hier wird zur Zeit noch jeder übergebene Container kopiert. Ich hab aber auch echt keine Ahnung, ob/wie man feststellen kann, ob da ein temporäres Objekt übergeben wird (das man kopieren muss) oder ein "normales".

    Achso, und der Code ist getestet mit der Beta von VS10.

    edit: volkard, in einem Code von dir weiter oben speicherst du ja in IfConstructionHolder die Referenz des Containers. Ist das denn gültig wenn der nur temporär erzeugt wird?



  • Das doofe ist nur: Hier wird zur Zeit noch jeder übergebene Container kopiert.

    In der äußersten Schleife nur eine referenz an den container binden, falls das geht. falls nicht, eine const-referenz. die const-referenz auf ein temp hält das temp lebendig, bis die referenz stirbt. weiß nur nicht, ob man irgendwie mit SFINAE oder so berechnen kann, ob man eine non-const-referenz ziehen kann. müßte aber eigentlich mit ner überladung gehen.

    problem: Die echte Schleife muß leider die innerste sein, sonst klappen break und continue nicht. Dadurch kann man keine Schleife reinfumeln, die nur die referenz iter erzeugt. Und mit if gehts auch nicht, weil ein falsewert in den Nutzdaten sein kann.

    edit: oh, es geht doch. die innere schleife erkennt ja break oder continue daran, ob die bedingung nochmal ausgewertet wurde, dadrin kann man noch sachen zuweisen und damit die äußere steuern.



  • Badestrand schrieb:

    edit: volkard, in einem Code von dir weiter oben speicherst du ja in IfConstructionHolder die Referenz des Containers. Ist das denn gültig wenn der nur temporär erzeugt wird?

    Nein. Ist ein Fehler von mir. Die ref, die das temp-Objekt am leben halten sollte, lebt nur solange wie der funktionsaufruf. Deswegen muß eine echte ref in der äußeren Schleife genommen werden.



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


Anmelden zum Antworten