Vorbereitung für veraltete std :: iterator

76

Am 21. März st stimmte der Normenausschuss der deprecation von genehmigen std::iteratorvorgeschlagen in P0174 :

Die lange Folge von ungültigen Argumenten ist für den Leser viel weniger klar als die bloße typedefAngabe der erwarteten s in der Klassendefinition selbst. Dies ist der Ansatz des aktuellen Arbeitsentwurfs nach dem festgelegten Muster

Vor Die Vererbung von std::iteratorwurde ermutigt, die Langeweile bei der Implementierung des Iterator-Boilerplates zu beseitigen. Die Abwertung erfordert jedoch eines der folgenden Dinge:

  1. Ein Iterator-Boilerplate muss nun alle erforderlichen typedefs enthalten
  2. Algorithmen, die mit Iteratoren arbeiten, müssen jetzt verwendet werden, autoanstatt vom Iterator abhängig zu sein, um Typen zu deklarieren
  3. Loki Astari hat vorgeschlagen , std::iterator_traitsdass aktualisiert werden kann, um zu funktionieren, ohne von zu erbenstd::iterator

Kann mir jemand erklären, welche dieser Optionen ich erwarten sollte, wenn ich benutzerdefinierte Iteratoren mit Blick auf entwerfe Kompatibilität?

Jonathan Mee
quelle
6
@FirstStep Ich würde hoffen, eine Antwort zu bekommen, die nicht auf Meinungen basiert. Wenn das Standardkomitee eine Klasse ablehnt, von der ich nächstes Jahr abhängig bin, würde ich hoffen, dass sie eine Richtung haben, in die sie mich gerade lenken.
Jonathan Mee
2
Nur weil sie es ablehnen, heißt das nicht, dass Sie es eine Weile nicht mehr verwenden können.
Jesper Juhl
4
Die Iteratoren in der Standardbibliothek haben sich für Option 1 entschieden.
Bo Persson
1
@LokiAstari - es ist noch schwächer als das. Formal ist Abwertung ein Hinweis darauf, dass in Zukunft etwas verschwinden könnte. Das ist alles. Beachten Sie, dass die Standard-C-Header in C ++ seit 1998 veraltet sind.
Pete Becker
1
@ JonathanMee - nein, es war nicht umsonst. Es war nicht notwendig, aber es hat Vorteile.
Pete Becker

Antworten:

60

Die besprochenen Alternativen sind klar, aber ich bin der Meinung, dass ein Codebeispiel benötigt wird.

Da es keinen Sprachersatz gibt und Sie sich nicht auf Boost oder Ihre eigene Version der Iterator-Basisklasse verlassen müssen, wird der folgende verwendete Code std::iteratorauf den darunter liegenden Code festgelegt.

Mit std::iterator

template<long FROM, long TO>
class Range {
public:
    // member typedefs provided through inheriting from std::iterator
    class iterator: public std::iterator<
                        std::forward_iterator_tag, // iterator_category
                        long,                      // value_type
                        long,                      // difference_type
                        const long*,               // pointer
                        const long&                // reference
                                      >{
        long num = FROM;
    public:
        iterator(long _num = 0) : num(_num) {}
        iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
        bool operator==(iterator other) const {return num == other.num;}
        bool operator!=(iterator other) const {return !(*this == other);}
        long operator*() {return num;}
    };
    iterator begin() {return FROM;}
    iterator end() {return TO >= FROM? TO+1 : TO-1;}
};

(Code von http://en.cppreference.com/w/cpp/iterator/iterator mit Genehmigung des ursprünglichen Autors).

Ohne std::iterator

template<long FROM, long TO>
class Range {
public:
    class iterator {
        long num = FROM;
    public:
        iterator(long _num = 0) : num(_num) {}
        iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
        bool operator==(iterator other) const {return num == other.num;}
        bool operator!=(iterator other) const {return !(*this == other);}
        long operator*() {return num;}
        // iterator traits
        using difference_type = long;
        using value_type = long;
        using pointer = const long*;
        using reference = const long&;
        using iterator_category = std::forward_iterator_tag;
    };
    iterator begin() {return FROM;}
    iterator end() {return TO >= FROM? TO+1 : TO-1;}
};
Amir Kirsh
quelle
2
@AmirKirsh operator*sollte zurückkehren reference, Sie brauchen eine operator->Rückgabe a pointer, obwohl es für along
Ryan Haining
33

Option 3 ist eine streng typisierende Version von Option 1, da Sie alle gleich schreiben müssen, typedefsaber zusätzlich umbrechen müssen iterator_traits<X>.

Option 2 ist als Lösung unrentabel. Sie können einige Typen ableiten (z. B. referenceist nur decltype(*it)), aber Sie können nicht ableiten iterator_category. Sie können nicht zwischen input_iterator_tagund forward_iterator_tageinfach durch das Vorhandensein von Operationen unterscheiden, da Sie nicht reflexartig prüfen können, ob der Iterator die Multipass-Garantie erfüllt. Außerdem können Sie nicht wirklich zwischen diesen unterscheiden und output_iterator_tagob der Iterator eine veränderbare Referenz liefert. Sie müssen explizit irgendwo angegeben werden.

Damit bleibt Option 1. Ich denke, wir sollten uns nur daran gewöhnen, die gesamte Boilerplate zu schreiben. Zum einen begrüße ich unsere neuen Karpaltunnel-Oberherren.

Barry
quelle
3
Wenn Ihnen das, was std::iteratorSie tun, wirklich gefällt , können Sie trivial Ihre eigene Version schreiben. Das Risiko eines Karpaltunnels ist also stark übertrieben.
TC
4
@ JonathanMee Das macht keinen Sinn.
Barry
7
@ JonathanMee Dude. "Iterator" und "Eingangsiterator" sind nicht gleichwertig .
Barry
7
@ TemplateRex Es war ein Witz. Ungeachtet dessen scheint es dumm zu sein, std::iteratorzugunsten von ... jedem zu schreiben, der jetzt seine eigene Kopie schreibt std::iterator, um dieses Problem trotzdem zu lösen.
Barry
3
verwandt: stackoverflow.com/q/29108958/819272 Es ist ein allgemeinerer Trend im Standard, alberne Basisklassen zu entfernen, die nur typedefs (unary_function etc.) enthalten
TemplateRex