Funktionsweise von boost::foreach und typeof



  • //nur zum Textverständnis:
    #define foreach BOOST_FOREACH
    #define typeof BOOST_TYPEOF
    

    Kann jemand anschaulich erklären, wie diese beiden Konstukte intern funktionieren? 'boost::typeof' kann ich mir nur damit erklären, dass das eine Art Makro auf irgendwelche Compiler-spezifischen '__type__of___'s oder dergleichen ist. Aber 'foreach'? Das sieht bei der Benutzung so aus:

    #include <boost/foreach.hpp>
    #include <iostream>
    using namespace boost;
    using namespace std;
    int main()
    {
    	int arr[] = {1, 2, 3};
    	BOOST_FOREACH(int i, arr)
    	{
    		cout << i << endl;
    	}
    	cin.get();
    }
    

  • Mod

    Das man mit einem Makro einen Schleifenkopf generieren kann, sollte klar sein. Dazu kommt dann ähnlich wie bei typeof eine clevere Erkennung des Typs des zweiten Arguments (mittels Makros und einer Menge Templatemetaprogrammierungsmagie). Damit wird dann die zum Typen passende Schleife generiert.

    Für eine sehr vereinfachte Version dieser Typerkennung kannst du dir mal eine Einführung in Templatemetaprogrammierung angucken. Damit kannst du dir die zugrundeliegenden Konzepte klarmachen.



  • Ich glaube, das muss man nicht wissen. Wie foreach genau funktioniert weiß ich nicht. Typeof braucht zumindest Hilfe vom Compilers (Compiler-Erweiterung) oder des Benutzers (Typen manuell registrieren), soweit ich weiß. Der GCC bietet zB eine Erweiterung an ( __typeof__ ), die dann von Boost.Typeof einfach benutzt wird.

    Spätestens mit C++0x wird sich das erledigt haben. Dann haben wir decltype und den for-range loop.

    Gruß,
    SP



  • Nach über 45 Minuten mit Experimenten kenne ich das Problem bei der Sache: Das ändern der Elementvariablen, im Beispiel 'int i'. Man hat ja nicht einfach den Namen, sondern es steht immer der Typ davor. Und es scheint keine Möglichkeit zu geben, eine Referenz darauf zu erhalten:

    for (int i /*hier müsste man die Adresse von i ermitteln*/; ; )
    {
    }
    

    Das ist mir ein Rätsel (wie das meiste in boost).



  • TyRoXx schrieb:

    for (int i /*hier müsste man die Adresse von i ermitteln*/; ; )
    

    Nein, muss man nicht. Die Variable i ist schliesslich nur die Laufvariable, sozusagen die Schnittstelle zum Code im Schleifenrumpf. Als Benutzer ist eine Möglichkeit erforderlich, um das Objekt ansprechen können, über das gerade iteriert wird.

    Für die Implementierung von BOOST_FOREACH ist der erste Parameter nicht wirklich wichtig. Ihm wird ein Wert zugewiesen, der zuerst den Anfang des Containers (Makro-Argument COL ) repräsentiert. Danach wird er aktualisiert. Wie du siehst, passiert das in einer weiteren Schleife, sodass VAR gleich initialisiert wird (ist z.B. zwingend notwendig bei Referenzen).

    Originalcode (Boost 1.36, Datei boost/foreach.hpp):

    #define BOOST_FOREACH(VAR, COL)                                                                 \
        BOOST_FOREACH_PREAMBLE()                                                                    \
        if (boost::foreach_detail_::auto_any_t _foreach_col = BOOST_FOREACH_CONTAIN(COL)) {} else   \
        if (boost::foreach_detail_::auto_any_t _foreach_cur = BOOST_FOREACH_BEGIN(COL)) {} else     \
        if (boost::foreach_detail_::auto_any_t _foreach_end = BOOST_FOREACH_END(COL)) {} else       \
        for (bool _foreach_continue = true;                                                         \
                  _foreach_continue && !BOOST_FOREACH_DONE(COL);                                    \
                  _foreach_continue ? BOOST_FOREACH_NEXT(COL) : (void)0)                            \
            if  (boost::foreach_detail_::set_false(_foreach_continue)) {} else                      \
            for (VAR = BOOST_FOREACH_DEREF(COL); !_foreach_continue; _foreach_continue = true)
    

    Hinter BOOST_FOREACH steckt - wie bei vielen Dingen in Boost - in der Tat ein genialer Mechanismus. 💡

    Edit: Richtigen Code eingefügt



  • Nexus, du hast aber den Code für "BOOST_REVERSE_FOREACH" gepostet -)



  • Danke für die Hilfe, jetzt habe ichs hinbekommen:

    #include <iostream>
    #include <boost/typeof/typeof.hpp>
    using namespace std;
    
    #define foreach(ele, arr) \
    	for (BOOST_TYPEOF(arr[0]) *foreach_parr = arr, \
    		*foreach_end = arr + (sizeof(arr)/sizeof(arr[0])); \
    		foreach_parr != foreach_end; ++foreach_parr) \
    		for (bool foreach_continue = true; foreach_continue; ) \
    			for (ele = *foreach_parr; foreach_continue; foreach_continue = false)
    
    int main()
    {
    	int values[] = {1, 2, 3, 8, 4, 2};
    
    	foreach(int i, values)
    	{
    		cout << i << endl;
    	}
    	cin.get();
        return 0;
    }
    


  • Th69 schrieb:

    Nexus, du hast aber den Code für "BOOST_REVERSE_FOREACH" gepostet -)

    Verdammt! Ich war ein wenig in der Headerdatei am stöbern, da habe ich wohl aus Versehen das falsche Makro kopiert. Aber das Prinzip ist natürlich das Gleiche. 😉
    Ich habs trotzdem mal korrigiert.

    TyRoXx schrieb:

    Danke für die Hilfe, jetzt habe ichs hinbekommen:

    Nicht schlecht! Allerdings fehlt mir die Anbindung an Container, was ich als zentralen Einsatzzweck von BOOST_FOREACH ansehe. :p

    Nein, gut, dass du das hingekriegt hast! In C++0x wäre so ein Makro wie schon erwähnt recht elegant möglich, hier gab es einen Thread dazu.



  • @Nexus/TyRoXx:
    Nicht nur fehlt die Anbindung an Container, auch wird hier BOOST_TYPEOF verwendet (was nicht nötig ist), und "arr" mehrfach ausgewertet.

    Beides "richtig" zu machen ist allerdings ziemlich viel Aufwand. Sieht man ja wie schnell das ausartet, wenn man sich den Original Boost Code ansieht.

    Einer der coolsten Tricks aus der Boost Implementierung:

    // A sneaky way to get the type of the collection without evaluating the expression
    #define BOOST_FOREACH_TYPEOF(COL)                                                               \
        (true ? 0 : boost::foreach_detail_::encode_type(COL, boost::foreach_detail_::is_const_(COL)))
    

    Wunderbar 🙂


Anmelden zum Antworten