foreach mit Referenzen



  • Da die Entwicklung bzw. Standardisierung von c++0x bzw. seine range-based for Schleife noch bis mind. 2011 andauern wird, wie ich befürchte, hab ich mich mal nach Alternativen umgesehen.
    Zum einen fielen mir Makros ein/auf, doch ich bin kein Fan davon, obwohl mich das BOOST_FOREACH Makro echt schwer beeindruckt hat, was ich wohl auch zukünftig nutzen werde, bis c++ endlich mal von Haus aus modern wird.

    Doch trotz alledem wollte ich mir auch gerne mal etwas eigenes basteln, und möglichst auf Typkontrolle beharren, also was läge da näher als Templates.
    Gesagt getan:

    #include <iostream>
    #include <vector>
    #include <string>
    #include <cstdio>
    
    namespace rs {
        template <typename T> void print (const std::vector<T> & v, std::string flag = "%d") {
            unsigned int counter = 1;
    
            std::string output, org_flag = flag;
            flag += ", ";
            for (T var; each(var, v); ++counter) {
                if (counter >= v.size()) {
                    flag = org_flag;
                }
    
                char temp[100];
                sprintf(temp, flag.c_str(), var);
                output += temp;
            }
            std::cout << "\n[" << output << "]\n";
        }
    
        template <typename T> bool each (T & var, const std::vector<T> & v) {
            static unsigned short index = 0;
    
            var = v[index++];
            if (index > v.size()) {
                index = 0;
    
                return false;
            }
    
            return true;
        }
    }
    
    int main() {
        std::vector<int> v = { 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    
        rs::print(v);
    
        for (int var; rs::each(var, v);) {
            std::cout << var << "\n";
    
            ++var;
        }
    
        rs::print(v);
    }
    

    Allerdings kann ich diese Variable

    var
    

    lediglich als read-only verwenden, die erhöhung

    ++var
    

    bringt nichts, außer dass der Wert von var erhöht wird, aber nicht der entsprechende Vektor - Member (v[0], v[1] ... etc.), dazu müsste ich var wohl jedesmal mit dem derzeitigen member vom Vektor initialisieren, um eine vordefinierte Referenz zu haben, jedenfalls funktioniert folgendes reibungslos:

    int &foo = v[0];
    std::cout << "vor deklarierte Referenz:" <<  foo;
    ++foo;
    std::cout << "\nvor deklarierte Referenz und v[0]:" << foo << " : " << v[0] << "\n";
    

    sowohl foo als auch v[0] sind erhöht worden.

    Ich frage mich, wie ich, ähnlich wie in BOOST oder in der kommenden c++0x'er for Schleifen Version eine read-only und eine veränderliche Variante von var hinbekomme.
    Z.B. lässt sich ja mit

    for (int & x : v)
    

    bzw. in BOOST

    BOOST_FOREACH (int & x, v)
    

    x nun im Schleifenkörper beliebig verändern, so das diese Veränderungen auch Einfluss auf die jeweilige Memebervariablen vom Vektor haben.

    Dieses Prinzip würde ich nur allzugern "kopieren" bzw. übernehmen in meine derzeitige Variante, wenn möglich ohne viel Code in/an die Anweisung

    for (int var; each(var, v);)
    

    zu stecken.

    Würde mich über Kritik und Hinweise(n) sehr freuen.



  • Mir ist nicht ganz klar, warum du in print nicht schlicht einen stringstream benutzt. Oder gleich eine Funktion daraus machst, die einen string zurückgibt, den man auch anderweitig benutzen könnte. Oder eine Funktion, die das ganze in einen Stream schreibt. Oder, wenn syntaktischer Zucker das Ziel ist, einen Wrappertyp, um für ihn spezialisierte Operatoren zielsicher aussuchen zu können, etwa

    #include <cstddef>
    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <vector>
    
    template<typename container_t>
    class container_formatter_type {
    public:
      container_formatter_type(container_t const &data) : data_(data) { }
    
      container_t const &get() const { return data_; }
    
    private:
      container_t const &data_;
    };
    
    template<typename container_t>
    std::ostream &operator<<(std::ostream &out,
                             container_formatter_type<container_t> const &what) {
    
      out << "[ ";
    
      if(!what.get().empty()) {
        typename container_t::const_iterator e = what.get().end();
        --e;
        std::copy(what.get().begin(),
                  e,
                  std::ostream_iterator<typename container_t::value_type>(out, ", "));
        out << *e << ' ';
      }
    
      return out << ']';
    }
    
    template<typename container_t>
    container_formatter_type<container_t> container_formatter(container_t const &c) {
      return c;
    }
    
    template<typename T, std::size_t N>
    std::size_t array_size(T(&)[N]) { return N; }
    
    int main() {
      int data[] = { 3, 1, 4, 1, 5, 9 };
    
      std::vector<int> v(data, data + array_size(data));
    
      std::cout << container_formatter(v) << std::endl;
    }
    

    Jedenfalls etwas anderes, als im Backend ausgerechnet auf sprintf zurückzugreifen, was die ganze Typsicherheitsidee völlig ad absurdum führt und nur mit Basistypen überhaupt funktionieren kann.

    Was each angeht, da ließe sich womöglich mit std::tr1::ref etwas drehen, aber deine each-Funktion ist weder thread- noch exceptionsicher. Ich würde ich hüten, etwas derartiges tatsächlich zu verwenden.

    Das beste, was ich da so aus dem Stand anbieten kann, ist etwas in dieser Art:

    #include <algorithm>
    #include <iterator>
    #include <iostream>
    #include <vector>
    
    template<typename container_t>
    class ref_iter {
    public:
      typedef container_t                        container_type;
      typedef typename container_type::iterator  iterator;
      typedef typename container_type::reference reference;
    
      ref_iter(container_type &data)
        : data_(data),
          p_(data.begin()) { }
    
      operator reference() { return *p_; }
      bool advance() {
        if(p_ == data_.end()) { return false; }
        ++p_;
        return true;
      }
    
    private:
      container_type &data_;
      iterator p_;
    };
    
    int main() {
      int data[] = { 3, 1, 4, 1, 5, 9 };
    
      std::vector<int> v(data, data + 6);
    
      for(ref_iter<std::vector<int> > r(v); r.advance(); ) {
        ++r;
      }
    
      std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
    
      return 0;
    }
    

    ...aber das funktioniert nur eingeschränkt. Beispielsweise lassen Methoden des eigentlichen Wertes sich ohne Cast nicht aus der ref_iter-Instanz aufrufen. Einfach Iteratoren zu benutzen, scheint mir eigentlich die schönere Lösung zu sein; für eine je nach Geschmack etwas hübschere Syntax würde ich mich auf solche Scherze eher nicht einlassen.



  • [quote="rswhite"]

    template <typename T>
    bool each (T & var, const std::vector<T> & v) {
        static unsigned short index = 0;
        var = v[index++];
        if (index > v.size()) {
            index = 0;
            return false;
        }
        return true;
    }
    

    Eine static Variable? Sehr unschön, IMO.

    rswhite schrieb:

    Würde mich über Kritik und Hinweise(n) sehr freuen.

    Dein each ist wegen der static -Variablen nicht Thread-sicher. Auch wenn man eine Schleife mal mit break abbricht, klappt's mit der nächsten Schleife nicht mehr.

    Ich benutze BOOST_FOREACH nicht. Das ist mir zuviel schwarze Magie 🙂
    Aber ich freu mich schon auf C++0x.

    kk



  • Das klingt ja beinahe so, als müsste ich weiter mit der nun experimentellen etwas moderneren, aber immer noch vorsteinzeitlichen methode

    for (auto iter = v.begin(); iter != v.end(); ++iter)
    

    arbeiten. Ich glaub ich werd bis zur Geburtsstunde des modernen c++ erstmal bei Python bleiben.

    Danke jedenfalls für die Hinweise vorallen mit der Thread Sicherheit in Bezug auf die static variable, daran hatte ich gar keinen Gedanken verschwendet.

    die print Methode war ein schlichtes herum experimentieren die eig. der volllständigkeitshalber mit gepostet wurde, aber danke auch da für die Kritik 🙂



  • Musst du nicht, mach einfach ein

    #define foreach_e BOOST_FOREACH
    #define foreach(NAME,CONTAINER)  foreach_e(auto& NAME,CONTAINER)
    ...
    foreach(p,parts)...
    

    und gut ist.

    Bei dem Komfort von foreach statt BOOST_FOREACH muss man allerdings darauf achten, dass man die eigenen Header nach andern Headern, die ebenfalls foreach verwenden (GTK+), einbindet. Das dürfte aber kaum ein Problem darstellen.



  • Jetzt muss ich aber doch mal eine Lanze für C++ brechen; insbesondere, wo ausgerechnet Python als Alternative genannt wurde (furchtbare Sprache, das).

    Ich empfand und empfinde das Range-Konzept durch Iteratoren, wie es in C++ üblich ist, immer als äußerst flexibel. Konstrukte wie

    for(int &x : c) { ... }
    

    mögen ja hübsch aussehen, aber sie sind eigentlich doch ziemlich starr. Was, wenn ich nur die erste Hälfte eines Containers verarbeiten will? Was, wenn ich gerade einen Stream bearbeite? Wenn ich einen eigenen Container entwickle und will, dass dieser mit Standardalgorithmen verwendbar ist?

    Wenn du Iteratoren als "steinzeitlich" betrachtest, beschleicht mich der Verdacht, dass du nie wirklich mit ihnen gearbeitet hast. Ich für meinen Teil warte jedenfalls gespannter auf Lambda-Ausdrücke als auf for(int &x : c).



  • seldon schrieb:

    Jetzt muss ich aber doch mal eine Lanze für C++ brechen; insbesondere, wo ausgerechnet Python als Alternative genannt wurde (furchtbare Sprache, das).

    Ich empfand und empfinde das Range-Konzept durch Iteratoren, wie es in C++ üblich ist, immer als äußerst flexibel. Konstrukte wie

    for(int &x : c) { ... }
    

    mögen ja hübsch aussehen, aber sie sind eigentlich doch ziemlich starr. Was, wenn ich nur die erste Hälfte eines Containers verarbeiten will? Was, wenn ich gerade einen Stream bearbeite? Wenn ich einen eigenen Container entwickle und will, dass dieser mit Standardalgorithmen verwendbar ist?

    Wenn du Iteratoren als "steinzeitlich" betrachtest, beschleicht mich der Verdacht, dass du nie wirklich mit ihnen gearbeitet hast. Ich für meinen Teil warte jedenfalls gespannter auf Lambda-Ausdrücke als auf for(int &x : c).

    Nicht die Iteratoren ansich sind meiner Meinung steinzeitlich, aber dieses ellenlange konstrukt ist das, was wirklich viel schreibarbeit ausmacht. Durch

    auto
    

    wird diese schreckliche ausdehnung des codes ja gott sei dank erheblich minimiert, früher war ja

    std::vector<int>::iterator iter;
    

    noch von Nöten.

    Was hast du denn eigentlich gegen Python? Eine so schlanke & funktionale Sprache.
    Gezwungene Einrückung, keine Semikolons oder allzuviele Klammern, sei es "{ }" oder "( )".
    bei

    if (foo()) {
        anweisung();
    }
    
    // oder
    
    for (int i = 0; i < 5; i++) {
        anweisung();
    }
    

    würde meiner Meinung nach

    if foo():
        anweisung()
    
    // oder
    
    for i in range(0, 5):
        anweisung()
    

    absolut reichen, und genau das ermöglicht ja Python, es erspart Klammerhäufungen und Semikolons.
    Zudem ist die Sprache nicht so überladen wie C++ und wesentlich moderner 🙂



  • Naja, das mit der erzwungenen Einrückung und fehlenden Klammern ist mindestens Geschmacks- und zum großen Teil wohl auch Gewohnheitssache. Ich finde die fehlenden Klammern ziemlich unübersichtlich, und ich bin der Ansicht, dass ich besser als ein Interpreter weiß, wie mein Code organisiert gehört. Aber das ist nur Syntax und dementsprechend von untergeordneter Bedeutung.

    Was du mit "nicht so überladen" und "modern" meinst, musst du mir zunächst erklären, so wie du es schreibst, sind das zunächst nur Buzzwörter; damit sollen sich die Sales-Leute herumschlagen. Funktional ist Python nicht, sondern hauptsächlich objektorientiert, und schlank...naja, die meisten Benchmarks behaupten etwas ganz anderes, was für eine interpretierte Sprache aber auch nicht weiter ungewöhnlich ist.

    Mein Hauptproblem mit Python ist das völlig verkorkste Typsystem. Wenn mir jemand zu Laufzeit die Schnittstelle meiner Objekte verändern kann, kann ich mich nirgendwo mehr auf diese Schnittstelle verlassen, was umfassende Fehlerbehandlung so gut wie unmöglich und Debugging zu einer echten Qual macht. Irgendwelchen Kleinkram mag man damit noch zusammenhacken können, aber sobald das Projekt eine gewisse Größe erreicht und die Stabilität der Komponenten an Bedeutung gewinnt, ist das meines Erachtens völlig ausgeschlossen.

    Aber zurück zu C++, immerhin sind wir ja im C++-Forum. Die Bedeutung des Schlüsselworts "auto" wird auch erst im nächsten Standard umdefiniert, und dann kriegst du ja auch deine Schleifensyntax. Bis dahin gibt es für solche Dinge typedef.

    Insgesamt ist das neue auto und damit verbundene neue Sprachelemente wie decltype auch eher im Zusammenhang mit Funktionsvorlagen interessant, etwa

    template<typename T, typename U>
    auto add(T const &t, U const &u) -> decltype(t + u) { return t + u; }
    

    "add" jetzt natürlich nur als einfaches Beispiel. Wenn etwa T int und U double ist, will man da nicht int zurückgeben, und auf die Art kann man die Auswahl dem Compiler überlassen.



  • seldon schrieb:

    Konstrukte wie

    for(int &x : c) { ... }
    

    mögen ja hübsch aussehen, aber sie sind eigentlich doch ziemlich starr. Was, wenn ich nur die erste Hälfte eines Containers verarbeiten will?

    for(int &x : make_pair(iter_anfang,iter_mitte))
    { ... }
    

    seldon schrieb:

    Was, wenn ich gerade einen Stream bearbeite?

    Was soll damit sein?

    seldon schrieb:

    Wenn ich einen eigenen Container entwickle und will, dass dieser mit Standardalgorithmen verwendbar ist?

    Mach doch. Du wirst ja sicherlich <iterator> dazu einbinden, um Deinen Iteratoren die entsprechende Iterator-Kategorie zu verpassen. Die Funktionstemplates std::begin und std::end (siehe §26.6.5) werden u.a. durch den <iterator> Header sichtbar. Wenn Dein Container also begin/end-Methoden anbietet und diese std::begin/end Funktionen sichtbar sind, wird auch automatisch die for-loop-Syntax mit Deinem Container funktionieren.

    seldon schrieb:

    Wenn du Iteratoren als "steinzeitlich" betrachtest, beschleicht mich der Verdacht, dass du nie wirklich mit ihnen gearbeitet hast.

    Du wirst Doch wohl zugeben, dass das mit begin() und end() ein bissel nervig ist, oder?

    seldon schrieb:

    Ich für meinen Teil warte jedenfalls gespannter auf Lambda-Ausdrücke als auf for(int &x : c).

    Och, das ist eigentlich alles ganz nett, was C++0x bietet.



  • Krümelkacker schrieb:

    for(int &x : make_pair(iter_anfang,iter_mitte))
    { ... }
    

    Ah, das ist praktisch zu wissen. Welches Iterator-Konzept wird dafür verlangt?

    Krümelkacker schrieb:

    Du wirst Doch wohl zugeben, dass das mit begin() und end() ein bissel nervig ist, oder?

    Eigentlich hat es mich nie wirklich gestört. Das wird mich aber nicht daran hindern, den syntaktischen Zucker des neuen Standards auch zu benutzen.

    Ich wollte dieses Syntaxkonstrukt nicht schlecht machen, ich bin lediglich der Ansicht, dass semantische Änderungen wichtiger sind.

    Krümelkacker schrieb:

    Och, das ist eigentlich alles ganz nett, was C++0x bietet.

    Ich hab noch nicht alles davon im Detail studiert (das werde ich tun, wenn der Standard fertig ist), aber für Einiges ist "ganz nett" eine ziemliche Untertreibung. 😉



  • rswhite schrieb:

    Zudem ist die Sprache nicht so überladen wie C++ und wesentlich moderner 🙂

    🙄

    seldon schrieb:

    Mein Hauptproblem mit Python ist das völlig verkorkste Typsystem.

    🙄



  • seldon schrieb:

    Krümelkacker schrieb:

    for(int &x : make_pair(iter_anfang,iter_mitte))
    { ... }
    

    Ah, das ist praktisch zu wissen. Welches Iterator-Konzept wird dafür verlangt?

    Nun, das wird in diesem Fall automatisch nach

    {
      auto&& range = make_pair(iter_anfang,iter_mitte);
      for (auto it = begin(range), en = end(range);
           it != en; ++it)
      {
        int &x = *it;
        { ... }
      }
    }
    

    umgewandelt, wobei die Bezeichner ( range , it , en ) wahrscheinlich in Wirklichkeit Doppel-Unterstriche beinhalten werden. Das entsprechende begin / end wird per ADL gefunden. In diesem Fall sind das begin / end aus <utility> , welches einfach first / second aus dem pair extrahiert. Wenn ich mich nicht irre, funktioniert das hier schon für Input-Iteratoren.

    seldon schrieb:

    Krümelkacker schrieb:

    Och, das ist eigentlich alles ganz nett, was C++0x bietet.

    Ich hab noch nicht alles davon im Detail studiert (das werde ich tun, wenn der Standard fertig ist), aber für Einiges ist "ganz nett" eine ziemliche Untertreibung. 😉

    Ja, gebe ich zu. 🙂

    kk



  • seldon schrieb:

    Was du mit "nicht so überladen" und "modern" meinst, musst du mir zunächst erklären, so wie du es schreibst, sind das zunächst nur Buzzwörter; damit sollen sich die Sales-Leute herumschlagen.

    Python bietet im Gegensatz zu C++ wenige oder weniger Mittel mit einer vielfachen Möglichkeits Anwendung an.
    Da du so auf Erkärungen beharrst, hier ein Beispiel:
    Ich möchte eine Liste aus den Inhalten eines Datei.
    Python

    v = list()
    with open('test.dat') as f:
        v = [x for x in f.readlines()]
    
    print(v)
    
    std::fstream f;
    f.open("test.dat", std::ios::in);
    
    std::vector<std::string> v;
    if (f.good()) {
    	while (f.eof() == false) {
    		std::string temp;
    
    		getline(f, temp);
    		v.push_back(temp);
    	}
    }
    
    f.close();
    
    for (std::vector<std::string>::const_iterator iter = v.begin(); iter != v.end(); ++iter) {
    //for (auto iter = v.begin(); iter != v.end(); ++iter) {
    	std::cout << *iter << std::endl;
    }
    

    ich sehe einen gewissen vorteil was Python betrifft.
    Aber auch das z.B. Vectoren etc. nicht zum Sprachkern sondern zur STL gehören ist für mich nicht ganz nachvollziehbar.
    Das und vieles andere ist für mich Beweis genug, das Python Schlanker und moderner ist.
    Was "moderner" angeht, wirst du mir aufgrund des 10 Jahre alten Standards von c++ nicht widersprechen können. Was die Typkontrolle in Python angeht, kann ich dir allerdings nur beipflichten.

    Aber genug von der Diskussion ob C++ oder Python oder sonst was besser ist, eine perfekte Programmiersprache gibt es (leider) nicht.

    P.S: ich lese mich gerade in D rein, bislang gefällt es mir ganz gut, C++ Abklatsch in Modern, auch wenn ich bislang nicht allzuviele libs dafür sehe^^

    Was deine restlichen Argumente des neuen Standards betreffend angeht, stimme ich dir diesbezüglich zu, dass es natürlich noch weit mehr und interessantere Konstrukte in c++0x geben wird.



  • krümelkacker schrieb:

    {
      auto&& range = make_pair(iter_anfang,iter_mitte);
      for (auto it = begin(range), en = end(range);
           it != en; ++it)
      {
        int &x = *it;
        { ... }
      }
    }
    

    umgewandelt, wobei die Bezeichner ( range , it , en ) wahrscheinlich in Wirklichkeit Doppel-Unterstriche beinhalten werden. Das entsprechende begin / end wird per ADL gefunden. In diesem Fall sind das begin / end aus <utility> , welches einfach first / second aus dem pair extrahiert. Wenn ich mich nicht irre, funktioniert das hier schon für Input-Iteratoren.

    Werden dann std::begin/std::end benutzt, falls im Namensraum des angegebenen Ranges keine eigenen Funktionen definiert sind? Ich denke da an ein ähnliches Muster, wie es in Effective C++ für swap vorgeschlagen wird:

    {
      using std::swap;
      swap(foo, bar);
    }
    

    wo, wenn sich im Namensraum von foo/bar ein swap befindet, dieses benutzt wird, und ansonsten std::swap.

    @rswhite: In deinem Beispiel würde am Ende des Vektors noch ein leerer String eingeschoben. Besser so:

    std::deque<std::string> lines;
    
    {
      std::ifstream in("datei.txt");
      std::string line;
    
      while(std::getline(in, line)) {
        lines.push_back(line);
      }
    }
    
    std::copy(lines.begin(), lines.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
    

    Ist für mein Verständnis auch übersichtlicher. (deque hier deshalb, weil ich vorher nicht weiß, wie viele Zeilen in der Datei sind, und durch die Wiederverwendung von line spart man sich eine Menge Rumgewurste auf dem Heap)

    Allerdings sind die Streams und ihre Supportklassen in der C++-Standardbibliothek eher auf Token-Verarbeitung ausgelegt. Beispielsweise geht folgendes:

    std::istringstream in("3 1 4 1 5 9 2 6");
    std::istream_iterator<int> iter_end;
    std::vector<int> pi_ziffern(std::istream_iterator<int>(in), iter_end);
    

    (dass iter_end eine eigene Zeile kriegt, liegt an einer Syntax-Eigenheit, nach der pi_ziffern sonst als Funktionsdeklaration geparst würde). Dabei könnte in ebensogut eine Datei voller Zahlen, std::cin, oder sonst irgendein Eingabestrom sein. Netzwerksocket, beispielsweise.

    Ich denke nicht, dass C++ sich hier zu verstecken braucht.

    Ansonsten - die Container sind in der Standardbibliothek, weil es schlicht keinen Grund gab, sie tiefer in der Sprache zu verwurzeln. Die Standardbibliothek muss eh von jedem Compiler implementiert werden. Ich vermute, dass str und list in Python eingebaute Datentypen sind, weil der Interpreter sie intern verwendet und sie daher von vornherein vorhanden sind.

    Und ob "neuer" gleich "moderner" bedeutet, sei mal dahingestellt. COBOLs letzter Standard stammt beispielsweise aus dem Jahr 2002, mit so modernen Neuerungen wie Booleans und Fließkommazahlen. Was aber richtig ist, ist, dass es ein paar Dinge gibt, die ich gern in der Standardbibliothek sähe (und bald auch sehe), etwa Unicode-Strings und Nebenläufigkeitskonstrukte, die ich mir bisher aus anderer Quelle besorgen muss.

    Zu D kann ich nicht viel sagen.



  • <ot>

    rswhite schrieb:

    P.S: ich lese mich gerade in D rein, bislang gefällt es mir ganz gut

    Was liest Du denn? Ich denke, ich habe das meiste von dem, was man über D frei im Netz auf Anhieb finden kann, auch gelesen. Das schließt zB den Dr Dobbs Artikel "The Case for D" ein. Aber so richtig umgehauen hat mich bisher gar nichts davon. Wenn Du einen Link Tipp hast, her damit. 😉

    </ot>


Log in to reply