Iterieren über variable container Typen



  • Hallo,
    sagen wir mal ich möchte eine kleine Funktion haben, welche über verschiedene Container Typen iterieren kann, sodass ich z.B. einen Vector oder List als Parameter angebe, und diese Funktion den Inhalte der Container ausgibt.
    Mit dem keyword auto in einer for-loop kriege ich es hin, würde jedoch gerne verstehen wie es auch ohne dieses funktioniert 🙂
    Zuerst habe ich versucht einfach mit einem Index basierten for-loop immer "container.at(i)" bzw. "container[i]" aufzurufen, doch für List z.b. ist dieses ja nicht vorhanden.
    Mit Iteratoren bekomme ich das jedoch auch nicht richtig hin, weil ich ja beim Erzeugen des Iterators irgendwie angeben müsste ob dieser für List, Deque etc. ist, das aber mit meinem typename C irgendwie immer zu Compile Fehlern führt. Außerdem gibt es in C++ ja nicht wie z.B. in Java Wildcards, sodass ich für meine Templates angeben könnte, dass diese die Funktion at() implementieren müssen o.Ä., weshalb ich etwas auf dem Schlauch stehe.

    Hier mal etwas wie ich es mir vorstelle:

    template<typename C>
    void print(C& container){
        for(auto& a: container){
            std::cout << a << " ";
        }
    }
    
    int main(){
        //getting containers from somewhere......
    
        //printing them
        print(myvector);
        print(mydeque);
        print(mylist);
    }
    

    Für eine Erklärung wäre ich sehr dankbar 🙂



  • akustiker schrieb:

    das aber mit meinem typename C irgendwie immer zu Compile Fehlern führt.

    Jo, also, typeof, _typeof_, decltype oder sowas brauchste eigentlich.

    for(_typeof_(C)::iterator i=container.begin(),e=container.end();i!=e;++e)
    


  • Ah cool, das keyword kannte ich nocht nicht, danke dir 🙂

    D.h. _typeof_ funktioniert dann mit template parametern und liefer zur Laufzeit den Typ der da dann drinsteckt?

    Hm, wenn ich das schreibe kommt "error: argument to decltype must be an expression". und mit _typeof_ klappt es auch nicht 😃

    for(decltype(C)::iterator it = container.begin(); it != container.end(); i++){
            std::cout << *it << " ";
        }
    


  • So geht es mit typename (sollte auch unter C++98 funktionieren):

    for (typename C::iterator it = container.begin(); it != container.end(); ++it) {
    

    Was soll typeof sein? Ist das die GCC Erweiterung bevor es decltype gab?



  • akustiker schrieb:

    Ah cool, das keyword kannte ich nocht nicht, danke dir 🙂
    D.h. _typeof_ funktioniert dann mit template parametern und liefer zur Laufzeit den Typ der da dann drinsteckt?

    Jau - es macht irgendwie immer genau das, was es machen soll. 🤡

    Aber:

    _typeof_
    typeof
    decltype
    

    😃



  • sebi707 schrieb:

    Was soll typeof sein? Ist das die GCC Erweiterung bevor es decltype gab?

    Jup.



  • Dazu muss man decltype auf einen Ausdruck anwenden, nicht auf einen Typ wie im Beispiel von volkard. So wäre die richtige Lösung mit decltype :

    for (decltype(container.begin()) it = container.begin(); it != container.end(); ++it) {
    


  • Habe ein bischen rumprobiert und so klappt es auch bei mir hehe

    typedef decltype(container.begin()) myiterator;
    
        for(myiterator it = container.begin(); it != container.end(); it++){
            std::cout << *it << " ";
        }
    

    decltype scheint gerade bei template zeugs recht praktisch zu sein glaube ich. Es sieht für mich quasi so aus wie das Gegenstück zu auto, dass man dem Typ quasi wieder rausbekommt aus etwas.

    Danke euch!



  • akustiker schrieb:

    Hm, wenn ich das schreibe kommt "error: argument to decltype must be an expression".

    Ach, sorry, den typeof brauchte ich, damit ich ein foreach(-Makro) bauen konnte, dem man nur den containernamen reinstecken muss.

    for(typeof(cont.begin())=cont.begin()…
    

    Bei Dir reicht sebi707s Vorgehen.



  • Mal eine andere evtl. etwas blöse Frage:
    Wieso benutzen fast alle pre-increments in for-loop mit iteratoren (sprich ++it statt it++) ? Müsste so nicht das erste Element übersprungen werden, da beim ersten Durchlauf direkt inkrementiert wird?

    C++ newb checkin in... 😃



  • akustiker schrieb:

    Mal eine andere evtl. etwas blöse Frage:
    Wieso benutzen fast alle pre-increments in for-loop mit iteratoren (sprich ++it statt it++) ? Müsste so nicht das erste Element übersprungen werden, da beim ersten Durchlauf direkt inkrementiert wird?
    C++ newb checkin in... 😃

    Nee, der Unterschied ist NUR, wenn Du den Wert von ++i oder i++ sofort anschaust.

    int i=5;
    int j=++i;//j=6
    //i=6!!!
    
    int i=5;
    int j=i++;//j=5
    //i=6!!!
    

    Also sowas machen nur Verbrecher:

    if(++i==17)
    

    Warum nicht einfach

    ++i;
    if(i==17)
    

    Und beachte mal:

    i++;
    if(i==17)
    

    macht GENAU das gleiche!

    Deswegen isses egal, ob man ++i oder i++ schreibt, solange man den Wert davon nicht sofort verwendet. Und das machst Du doch nicht, denn Du bist kein Verbrecher, oder? Nach dem Semikolon am Anweisungsende (oder nem anderen sequence point) hat i den richtigen Wert und nur den kriegen die anderen zu sehen.

    Außerdem, vielleicht die andere Verwirrnis:

    for(init;conf;iter)
       cmd;
    

    ist

    {
       init;
       while(cond)
       {
          cmd;
          iter;
       }
    

    Beachte, daß das iter erst am Ende der Schleife steht, nach dem ersten Durchlauf erst.

    Also nix mit Überspringen. Keine Gefahr.

    Der Grund für ++i statt i++ ist, daß wenn das Erzeugen von Iteratoren teuer wäre (was es in der Praxis eigentlich nie ist), dann w+rde ++i schneller sein als i++, weil nur eine Referenz zurückgegeben werden muss statt einer ganzen Kopie des Iterators.



  • Üblicherweise übergibt man nicht den ganzen container, sondern nur den anfangs- und enditerator. So kann man auch präziser den Bereich auswählen, der bearbeitet werden soll.
    @akustiker: der letzte teil der for-schleife wird für sich nach jeden schleifendurchlauf abgearbeitet, sodass in beiden fällen der iterator nach jeden durchgang erhöht wird. Allerdings kann es sein, je nach implementatiom des iterators, dass die post-variante langsamer ist. Bei einem post-inkrement wird der iterator erhoht und der alte iterator zurückgegeben, welcher somit kopiert werden muss.



  • Ah alles klar, das macht Sinn.

    Danke 🙂



  • roflo schrieb:

    Üblicherweise übergibt man nicht den ganzen container, sondern nur den anfangs- und enditerator. So kann man auch präziser den Bereich auswählen, der bearbeitet werden soll.

    Was sich hoffentlich mit Ranges ändern wird, da ich es super nervig finde überall .begin() und .end() zu schreiben.



  • sebi707 schrieb:

    roflo schrieb:

    Üblicherweise übergibt man nicht den ganzen container, sondern nur den anfangs- und enditerator. So kann man auch präziser den Bereich auswählen, der bearbeitet werden soll.

    Was sich hoffentlich mit Ranges ändern wird, da ich es super nervig finde überall .begin() und .end() zu schreiben.

    +1 👍



  • volkard schrieb:

    akustiker schrieb:

    das aber mit meinem typename C irgendwie immer zu Compile Fehlern führt.

    Jo, also, typeof, _typeof_, decltype oder sowas brauchste eigentlich.

    for(_typeof_(C)::iterator i=container.begin(),e=container.end();i!=e;++e)
    

    Wieso inkrementierst du den end-of iterator? Typo?



  • sebi707 schrieb:

    roflo schrieb:

    Üblicherweise übergibt man nicht den ganzen container, sondern nur den anfangs- und enditerator. So kann man auch präziser den Bereich auswählen, der bearbeitet werden soll.

    Was sich hoffentlich mit Ranges ändern wird, da ich es super nervig finde überall .begin() und .end() zu schreiben.

    gibt es nicht sone range member function schon bei multimaps oder so? meine da irgendwas gelesen zu haben. Gibt dann nen pair mit first = begin() und second = end()



  • Sewing schrieb:

    Wieso inkrementierst du den end-of iterator? Typo?

    Jau.


Log in to reply