c# IEnumerator in c++



  • knutjelitto schrieb:

    Und: ich find x.begin()/x.end() eigentlich NICHT schön 😉

    Man kann sie aber in Algorithmen einsetzen. Das System ist sehr flexibel und damit schön. Das wirst du mit deinem Konzept nicht erreichen können. Es macht auch keinen Sinn, C++ die C#/Java Syntax aufzwingen zu wollen.



  • Das Beispiel mit begin/end lautet aber in C++11 idealerweise wie folgt (und das finde ich am schönsten):

    std::string x("abc");
    for (auto current : x)
        cout << current << endl;
    

    Bezüglich der allgemeinen Implementierung: Speicher 3 Iteratoren als Instanzvariablen im Enumerator: begin, end und current. Ich würde mich verhaltensmässig an den C++ Iteratoren orientieren und am Anfang gleich current = begin setzen. Und dann halt als move jeweils ++current und return end == current. Merke: Das Verhalten ist dann aber anders als bei .NET. Dort wäre current am Anfang null.



  • /rant/ schrieb:

    Das Beispiel mit begin/end lautet aber in C++11 idealerweise wie folgt (und das finde ich am schönsten):

    std::string x("abc");
    for (auto current : x)
        cout << current << endl;
    

    Bezüglich der allgemeinen Implementierung: Speicher 3 Iteratoren als Instanzvariablen im Enumerator: begin, end und current. Ich würde mich verhaltensmässig an den C++ Iteratoren orientieren und am Anfang gleich current = begin setzen. Und dann halt als move jeweils ++current und return end == current. Merke: Das Verhalten ist dann aber anders als bei .NET. Dort wäre current am Anfang null.

    Ja, ja, sowas in der Art habe ich schon implementiert, ist ja kein Problem.

    Das Problem ist doch aber:

    Wie erzeuge ich aus einem beliebigen iterator-Paar einen Iter<T>?

    also wie komme ich von z.B.
    vector<bum> auf einen Iter<bum> über vector<bum>.begin()/vector<bum>.end():

    vector<bum> vum{ ... bla ...};
    
    Iter<bum> it = create_iter(vum.begin() + 12, vum.begin() + 26);
    


  • vum.begin() erzeugt automatisch einen Iterator vom richtigen Typ. Super, oder?



  • knutjelitto schrieb:

    Das Problem ist doch aber:

    Wie erzeuge ich aus einem beliebigen iterator-Paar einen Iter<T>?

    Wieso musst du überhaupt ein Iter<T> erzeugen? Musst Du doch gar nicht. Trenn Dich von Deiner C#-Denke. Vererbung und Polymorphie ist nicht die einzige Art der Abstraktion, die C++ Dir bietet. Als ich von Java auf C++ umsteig, habe ich auch erstmal zig Interfaces geschrieben, weil ich es von Java gewohnt war. Es gibt aber keinen guten Grund, gleich von Anfang an, den Nutzern eine Indirektion mit virtuellen Funktionen aufzuzwingen.

    Meine Botschaft an Dich: Programmiere C++, nicht C# in C++.

    Wenn Du mit einer anderen Schnittstelle für Iteration experimentieren willst, kannst du einen Wrapper verwenden. So kann das aussehen. Das lässt sich später immer noch per Type Erasure verallgemeinern, wenn du es brauchst. Das würde ich aber optional machen und den Anwendern nicht von vorn herein aufzwingen. Mit Type Erasure kannst du dann z.B. aus einem Funktions-Template wie diesem

    template<class JavaIter>
    void zeig(JavaIter ji)
    {
        while (ji.hasNext()) {
            cout << ji.next() << endl;
        }
    }
    

    so eine Funktion bauen:

    void zeig(any_java_iter<int> ji)
    {
        while (ji.hasNext()) {
            cout << ji.next() << endl;
        }
    }
    

    wobei any_java_iter<int> per Indirektion und Laufzeit-Polymorphie den wahren Typen java_iter_wrapper<vector<int>::iterator> verstecken kann. Iterieren willst du aber eventuell ganz flott und da würde die erforderliche Indirektion zum verstecken des wahren Typs im Wege stehen. Deswegen verwenden wir in C++ meist den wahren Iterator-Typen direkt, ohne ihn zu verstecken.



  • knutjelitto schrieb:

    Die zweite Version sieht für mich irgendwie ``schöner´´ aus

    Irrelevant! Es ist nicht idiomatisch. Wenn Du Außenseiter spielen willst, der die Sprache C++ ganz anders verwerwendet als andere, nur zu. Beschwer Dich dann aber nicht, wenn keiner mit Dir zusammenarbeiten will.



  • Irgendwie so vielleicht:
    http://ideone.com/Frl33B

    Da fehlt noch einiges, zum Beispiel sollte operator * const werden und eine const-Referenz zurückgeben, aber ich komme mit typename, decltype und so noch nicht genug klar um das mal eben hinzukriegen. So ganz verstehe ich aber auch nicht warum man zwei Iteratoren nimmt wenns auch einer tut, zumal ein einzelner Iterator bei Containern, die schwer an ihren end-Iterator rankommen, (wie verketteten Listen) effizienter wäre.

    #include <iostream>
    #include <cassert>
    #include <vector>
    #include <type_traits>
    using namespace std;
    
    template<class C>
    struct Iterator{
    	operator bool() const{
    		return startiterator != enditerator;
    	}
    	const Iterator &operator ++(){
    		assert(*this);
    		startiterator++;
    		return *this;
    	}
    	const Iterator operator ++(int){
    		Iterator other(*this);
    		startiterator++;
    		return other;
    	}
    	typename C::iterator startiterator, enditerator;
    	//typename C::iterator sollte durch decltype(C::begin()) oder so ersetz werden
    	Iterator(C &c) : startiterator(c.begin()), enditerator(c.end()){
    	}
    
    	auto operator *() -> decltype(*startiterator){
    		//hier muss man noch was mit add_const und add_rvalue_reference machen
    		return *startiterator;
    	}
    };
    
    //Am ende den ganzen Spaß nochmal für const-Iterator
    
    int main(){
    	vector<int> v = {42, 37, 128};
    	Iterator<vector<int>> it(v); //kann man noch in eine makeIterator-Funktion tun
    	while (it){
    		cout << *it << ' ';
    		it++;
    	}
    }
    


  • @npw3: So finde ich es besser:

    #include <iterator>
    
    template< typename Iter >
    struct Iterator
    {
    	using underlying_iterator = Iter;
    
    private:
    
        mutable underlying_iterator first,
                              last;
    
    public:
    
    	underlying_iterator begin() { return first; }
    	underlying_iterator end() { return last; }
    
        operator bool() const
        {
            return first != last;
        }
    
        Iterator& operator++()
        {
            ++first;
            return *this;
        }
    
        Iterator const& operator++() const
        {
            ++first;
            return *this;
        }
    
        Iterator operator++(int) const
        {
            Iterator other(*this);
            ++first;
            return other;
        }
    
        explicit Iterator( underlying_iterator first,
    					   underlying_iterator last ):
        	first{first},
        	last{last} {}
    
    	template< typename Container >
        Iterator(Container& c) :
        	Iterator{ std::begin(c), std::end(c) } {}
    
        decltype(*first) operator*()
        {
            return *first;
        }
    
        decltype(*first) operator*() const
        {
            return *first;
        }
    };
    
    #include <vector>
    #include <iostream>
    
    int main()
    {
        std::vector<int> const v_const = {43, 38, 129};
    
        Iterator<std::vector<int>::const_iterator> it_const = v_const;
    
        while (it_const)
            std::cout << *it_const++ << ' ';
    }
    

    (Ideone)

    Das hat auch den Vorteil, dass ein Iterator als Argument von begin()/end() gültig ist, sowie dass Iterator<iterator> const dieselben Möglichkeiten hat wie Iterator<const_iterator> - man kann also einen Iterator<iterator> bequem als Klassenmember halten.

    //hier muss man noch was mit add_const und add_rvalue_reference machen

    Muss man das? Wieso?



  • Das ist dann aber eher eine Range als ein Iterator.

    Gibt es auch schon in ähnlicher Form als boost::iterator_range .



  • Nexus schrieb:

    Das ist dann aber eher eine Range als ein Iterator.

    Ist mir schon klar - darauf wollte ich ja hinaus. 🙂

    Gibt es auch schon in ähnlicher Form als boost::iterator_range.

    Ja, kenne ich.

    Edit: Bei näherer Betrachtung ist das Design hässlich. const-correctness sehe ich auch nirgends..



  • Hier noch ein paar Kommentare zu dem, was man hier gerade sehen konnte:

    @nwp3:

    • Ich finde es unschön, dass Dein Klassentemplate einen Containertypen als Templateargument statt eines Iterator-Typen erwartet. So hast du bei rohen Arrays z.B. je Größe N verschiedene Typen ( Iterator<T[N]> ). Es gibt auch noch "scary iterators", also Iteratoren gleichen Typs von verschiedenartigen Containern. Zum Beispiel interessiert dich beim Iterieren nicht mehr, as für ein Allokator verwendet wurde, also sollte davon auch nix im Typ zu sehen sein. Andernfalls kann der Template-Bloat unnötig groß werden.
    • Dein operator bool ist nicht explicit
    • Du verwendest c.begin(), c.end() statt begin(c), end(c). Klappt dann auch gar nicht auf rohen Arrays

    @Sone:

    • operator bool ist bei dir auch nicht explicit
    • Im Wesentlichen ist das sehr ähnlich zu dem, was ich vorher schon verlinkt hatte -- mal von der Schnittstelle des "Iterators" abgesehen ( hasNext statt operator bool , etc etc etc)
    • Du qualifizierst begin und end und schaltest damit ADL aus. Das ist ungeschickt. Deswegen wirst du u.a. "using std::begin" in dem finden, was ich verlinkte.

    gruß,
    kk



  • Du qualifizierst begin und end und schaltest damit ADL aus. Das ist ungeschickt.

    Wieso? std::begin findet es trotzdem durch SFINAE.

    operator bool ist bei dir auch nicht explicit

    Das ist Absicht. Wieso sollte ich bool(iter) in einer Bedingung stehen haben?

    Wie gesagt, ich finde mein Design nicht sehr hübsch, auf den zweiten Blick. Aber nicht aus diesen Gründen.



  • Sone schrieb:

    Du qualifizierst begin und end und schaltest damit ADL aus. Das ist ungeschickt.

    Wieso? std::begin findet es trotzdem durch SFINAE.

    Die Begründung hast du beim Zitat weggelassen. Es geht um ADL. Dadurch, dass du begin und end mit std:: qualifizierst, beschränkst du Dich auf begin / end -Funktionen, die im std -Namensraum deklariert wurden. Damit sind andere begin / end Funktionen, die woanders deklariert wurden und ohne Qualifizierung über ADL hätten gefunden werden können, für Dich unerreichbar. Interessant wird das für Typen, die keine begin/end-Elementfunktionen besitzen, aber freie begin/end-Funktionen imselben Namensraum anbieten.

    Sone schrieb:

    operator bool ist bei dir auch nicht explicit

    Das ist Absicht. Wieso sollte ich bool(iter) in einer Bedingung stehen haben?

    Da liegt bei dir wohl ein Missverständnis vor. Ein explicit operator bool() zwingt Dich nicht in Bedingungen, bool(iter) zu schreiben. Es unterdrückt lediglich diese blöden Situationen(*), die man sich früher nur mit dem "safe bool"-Idiom vom Hals halten konnte. Das C++11 Feature "explicit conversion operators" gibt es genau aus diesem Grund. Du wirst feststellen, dass std::unique_ptr und std::shared_ptr auch so einen explicit operator bool() haben. Boolsche Kontexte wie bei if , while , && u.s.w. kommen auch mit einem explicit operator bool() klar.

    (* blöd:

    deiniterator << 3; // upps, das kompiliert ja sogar!
    

    )



  • @nwp3, @Sone:

    Vielen dank, ist so im groben das, was ich mir zuerst auch so zurecht gefrickelt hatte 🙂

    @krümelkacker:
    Man kann eben nicht so leicht über seinen Schatten springen. Ich mach' jetzt seit über 10 Jahren in C# und OOP sitzt einem da tief in den Knochen. Generika sind in C# eher mittel zum Zweck und eine ordentliche Grundlage um OO zu programmieren. Habe in der Zwischenzeit was implementiert, was meinem Bedürfnis nach Objectorientierung entgegenkommt. Ich wusste nicht, das sowas in der Art ``type erasure´´ heißt (falls ich das richtig verstanden habe). Das ist alles andere als produktionsreifer Code, über die grossen 3/5 habe ich mir im zusammenspiel mit unique_ptr<> auch noch nicht wirklich Gedanken gemacht, aber hey C#/Windows@work & C++/Linux/Embedded@home und wenn mir das auf meinem Mikrocontroller Probleme macht wird's halt verbessert. 😉

    Habe hier 'nen Link zu 'nem ähnlich alten Knochen wie mich gefunden:
    http://www.artima.com/cppsource/type_erasure.html
    Der sieht das ganze auch eher entspannt.

    #pragma once
    
    #include <memory>
    
    namespace kp
    {
    	namespace lang
    	{
    		template<typename T>
    		class Iter
    		{
    		public:
    			typedef T value_type;
    			typedef T& reference;
    
    			class IterImpl;
    
    			Iter(IterImpl* impl) : m_impl(impl)
    			{
    			}
    
    			template<typename container>
    			static Iter<T> create(container& cont)
    			{
    				return Iter(new ContainerIter<container>(cont.begin(), cont.end()));
    			}
    
    			template<typename container>
    			static Iter<T> create(container& cont, typename container::iterator begin, typename container::iterator end)
    			{
    				return Iter(new ContainerIter<container>(begin, end));
    			}
    
    			operator bool() const
    			{
    				return m_impl->valid();
    			}
    
    			reference operator*()
    			{
    				return m_impl->current();
    			}
    
    			reference operator->()
    			{
    				return m_impl->current();
    			}
    
    			bool operator++()
    			{
    				return m_impl->next();
    			}
    
    			class IterImpl
    			{
    			public:
    				virtual bool valid() const = 0;
    				virtual reference current() = 0;
    				virtual bool next() = 0;
    				virtual ~IterImpl() {}
    			};
    
    			template<typename container>
    			class ContainerIter : public IterImpl
    			{
    			public:
    				typedef typename container::iterator iterator;
    
    				ContainerIter(iterator begin, iterator end)
    					: m_init(false), m_begin(begin), m_end(end)
    				{
    				}
    
    				bool valid() const override
    				{
    					return m_init && m_current != m_end;
    				}
    
    				reference current() override
    				{
    					return *m_current;
    				}
    
    				bool next() override
    				{
    					if (m_init && m_current != m_end)
    					{
    						++m_current;
    					}
    					else
    					{
    						m_current = m_begin;
    						m_init = true;
    					}
    					return m_current != m_end;
    				}
    			private:
    				bool m_init;
    				iterator m_begin;
    				iterator m_end;
    				iterator m_current;
    			};
    		private:
    			std::unique_ptr<IterImpl> m_impl;
    		};
    	}
    }
    


  • Oh? Ich dachte, in Bedingungen wie bei if -Statements muss das Argument implizit nach bool konvertiert werden. Vielen Dank für die Aufklärung.

    Die Begründung hast du beim Zitat weggelassen. Es geht um ADL. Dadurch, dass du begin und end mit std:: qualifizierst, beschränkst du Dich auf begin/end-Funktionen, die im std-Namensraum deklariert wurden. Damit sind andere begin/end Funktionen, die woanders deklariert wurden und ohne Qualifizierung über ADL hätten gefunden werden können, für Dich unerreichbar. Interessant wird das für Typen, die keine begin/end-Elementfunktionen besitzen, aber freie begin/end-Funktionen imselben Namensraum anbieten.

    Ach, Quatsch! Da habe ich was missverstanden! Ich dachte, du meinst das begin/end , welches ich als Memberfunktion deklariert habe...
    Mir ist durchaus klar, was es mit dem ADL-Problem von std::swap und std::begin / end auf sich hat. Ich vergesse es nur immer wieder.

    Dass ich das begin / end im Konstruktor von Iterator unqualifiziert nutzen sollte, habe ich übersehen, auch Danke dafür, korrigiere ich gleich.



  • @knutjelitto: Wie gesagt, du versuchst zwei orthogonale Dinge aufeinmal zu machen. Wie gesagt, es ist besser, diese zwei Dinge zu trennen. Einmal ein leichtgewichtiges Objekt, was zwei C++-Iteratoren (ohne Type Erasure) kapselt und eine Schnittstelle anbietet, die dir besser gefällt (ich rate aber immer noch davon ab, schau dir mal Boost.Range an) und zum anderen willst Du ggf. (aber nicht oft!) den dynamischen Typen (und damit die Info, was für ein Container das war) verstecken. Das lässt sich separat lösen. Auch bei dir sollte der Konvertierungsoperator explicit sein. Da könnte man sogar glatt eine Compiler-Warnung draus machen. Wenn Du im C++11-Modus schreibst

    ...
      operator bool() const { ...
      ...
    

    dann sollte da höchstwahrscheinlich wirklich

    ...
      explicit operator bool() const { ...
      ...
    

    stehen. Dann überlädst du operator++ noch auf eine Art, die nichts mit dem zu tun hat, was man von operator++ normalerweise erwartet. Von operator++ erwartet man nicht einen boolschen Wert zurück.

    Versuch mal Type Erasure als Ergänzung (nicht Modifikation) zu dem von mir verlinkten Code-Beispiel zu schreiben, also ein neues Klassen-Template any_java_iter , so dass ein any_java_iter<int> ein java_iter_wrapper<std::vector<int>::iterator> kapseln kann. -- Aber es sieht eigentlich so aus, als ob du damit keine Schwierigkeiten mehr haben solltest. 🙂

    Sone schrieb:

    Vielen Dank für die Aufklärung.

    No problem. 🙂


  • Mod

    Sone schrieb:

    Oh? Ich dachte, in Bedingungen wie bei if -Statements muss das Argument implizit nach bool konvertiert werden. Vielen Dank für die Aufklärung.

    Kapitel 4 Absatz 3 Satz 2 f.



  • @krümelkacker:

    explicit - ich fummel hier im Augenblick mit MSVC++ rum, da kann man nicht so wirklich C++11 Sachen machen 🙂

    Operator++ - war 'ne Kurzschlusshandlung. Habe mich am gebrauch orientiert, und bisher nur

    if (++it) ...
    

    oder

    while (++it) ...
    

    verwendet.

    Habe den Code erstmal so runtergeschrieben. Ist noch nicht wirklich so strukturiert, wie ich das haben möchte. Und es zweistufig zu machen ist wohl 'ne gute Idee.



  • camper schrieb:

    Kapitel 4 Absatz 3 Satz 2 f.

    Ich war zuerst völlig verwirrt, aber dann habe ich selbst nachgeschlagen:

    §6.4 schrieb:

    The value of a condition that is an expression is the value of the expression, contextually converted to bool for statements other than switch

    Und dann... §4/3:

    Certain language constructs require that an expression be converted to a Boolean value. An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (8.5).

    Hätte ich auch selbst nachschlagen können :p



  • upps, habe statt auf edit einfach auf zitieren geklickt. hier gibt's nix zu sehen, habe nur auf seite 2 einen hinweis bzgl scary iterators hinzugefügt.


Anmelden zum Antworten