Gibt es in C ++ 11 eine Bereichsklasse zur Verwendung mit bereichsbasierten Schleifen?

101

Ich habe das erst vor kurzem geschrieben:

template <long int T_begin, long int T_end>
class range_class {
 public:
   class iterator {
      friend class range_class;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return iterator(T_begin); }
   iterator end() const { return iterator(T_end); }
};

template <long int T_begin, long int T_end>
const range_class<T_begin, T_end>
range()
{
   return range_class<T_begin, T_end>();
}

Und das erlaubt mir, solche Dinge zu schreiben:

for (auto i: range<0, 10>()) {
    // stuff with i
}

Jetzt weiß ich, dass das, was ich geschrieben habe, vielleicht nicht der beste Code ist. Und vielleicht gibt es eine Möglichkeit, es flexibler und nützlicher zu machen. Aber es scheint mir, als hätte so etwas Teil des Standards sein sollen.

So ist es? Wurde eine neue Bibliothek für Iteratoren über einen Bereich von Ganzzahlen oder einen generischen Bereich von berechneten Skalarwerten hinzugefügt?

Allgegenwärtig
quelle
17
+1. Ich möchte solche Klassen in meinen Dienstprogrammen haben. :-)
Nawaz
2
Was bringt es übrigens, eine rangeVorlagenfunktion zu schreiben ? Es fügt der Verwendung, in der range_classes verwendet wird , nichts hinzu . Ich meine range<0,10>()und range_class<0,10>()sehe genauso aus!
Nawaz
2
@Nawaz: Ja, du hast recht. Ich hatte eine seltsame Vision, dass ich die Funktion dazu bringen könnte, zwischen dem dynamischen und dem statischen Fall zu unterscheiden, aber ich glaube nicht, dass dies möglich ist.
Omnifarious
2
@iammilind: Nawaz stellte die gleiche Frage 35 Minuten vor Ihnen;)
Sebastian Mach
3
Um pedantisch zu sein, denke ich, dass diese Implementierung einen Fehler hat, nämlich dass Sie sie nicht verwenden können, um über den gesamten ganzzahligen Bereich zu iterieren. Wenn Sie INT_MIN und INT_MAX als Vorlagenargumente einstecken, führt INT_MAX beim Inkrementieren zu einem Überlauf von INT_MIN und verursacht Endlosschleifen. "end" in der STL soll "one after the end" sein, das nicht in den Integer-Typ selbst passt, daher weiß ich nicht, dass dies für den breitesten Integer-Typ auf einer bestimmten Plattform tatsächlich effizient implementiert werden kann. Für kleinere ganzzahlige Typen können Sie intern immer einen breiteren Typ verwenden ...
Joseph Garvin

Antworten:

59

Die C ++ - Standardbibliothek hat keine, aber Boost.Range hat boost :: count_range , was sicherlich qualifiziert. Sie können auch boost :: irange verwenden , das etwas fokussierter ist.

Mit der Bereichsbibliothek von C ++ 20 können Sie dies über tun view::iota(start, end).

Nicol Bolas
quelle
3
Ja, das ist definitiv die Natur dessen, wonach ich suchen würde. Ich bin froh, dass Boost es getan hat. Ich bin traurig, dass das Standardkomitee es aus irgendeinem Grund nicht aufgenommen hat. Es wäre eine großartige Ergänzung zur Range-Base-for-Funktion gewesen.
Omnifarious
Diese Antwort beantwortet meine direkte Frage besser und ich werde sie wählen, obwohl Nawaz 'Antwort sehr gut ist.
Omnifarious
6
In letzter Zeit wurden große Fortschritte erzielt, um Bereiche in den Standard (N4128) aufzunehmen. Unter github.com/ericniebler/range-v3 finden Sie einen Vorschlag und eine Referenzimplementierung.
Ela782
1
@ Ela782: ... und doch scheinen wir es in C ++ 17 nicht zu sehen, oder?
Einpoklum
1
@Andreas Ja, Bereiche haben es vor einiger Zeit in einen TS geschafft, aber ich glaube nicht, dass es jemals eine Referenzimplementierung gab / gab, die es in die wichtigsten Compiler unter dem std::experimental::rangesNamespace geschafft hat. range-v3war immer eine Art Referenzimplementierung, würde ich sagen. Aber jetzt glaube ich, dass das grundlegende Sortiment kürzlich auch in C ++ 20 gewählt wurde, also werden wir es tatsächlich std::bald bekommen! :-)
Ela782
47

Soweit ich weiß, gibt es in C ++ 11 keine solche Klasse.

Wie auch immer, ich habe versucht, Ihre Implementierung zu verbessern. Ich habe es ohne Vorlage erstellt , da ich keinen Vorteil darin sehe , es als Vorlage zu erstellen . Im Gegenteil, es hat einen großen Nachteil: Sie können den Bereich nicht zur Laufzeit erstellen, da Sie die Vorlagenargumente zur Kompilierungszeit selbst kennen müssen.

//your version
auto x = range<m,n>(); //m and n must be known at compile time

//my version
auto x = range(m,n);  //m and n may be known at runtime as well!

Hier ist der Code:

class range {
 public:
   class iterator {
      friend class range;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return begin_; }
   iterator end() const { return end_; }
   range(long int  begin, long int end) : begin_(begin), end_(end) {}
private:
   iterator begin_;
   iterator end_;
};

Testcode:

int main() {
      int m, n;
      std::istringstream in("10 20");
      if ( in >> m >> n ) //using in, because std::cin cannot be used at coliru.
      {
        if ( m > n ) std::swap(m,n); 
        for (auto i : range(m,n)) 
        {
             std::cout << i << " ";
        }
      }
      else 
        std::cout <<"invalid input";
}

Ausgabe:

10 11 12 13 14 15 16 17 18 19

Onine Demo .

Nawaz
quelle
3
Ich mag das. Ich dachte an eine Nicht-Template-Version. Und ich nehme an, ein guter Compiler würde es gut optimieren, wenn die Werte tatsächlich konstant sind. Ich muss das testen.
Omnifarious
10
@Nawaz: Ich würde es immer noch als Vorlage für den integralen Typ verwenden :) Ich würde auch vorschlagen, einen Alias iteratorzu verwenden const_iterator, von iteratorabzuleiten std::iteratorund zu rangeimplementieren cbeginund cend. Oh und ... warum iterator::operator++gibt eine konstante Referenz zurück?
Matthieu M.
6
@ RedX: Dijkstra hat eine gute Beschreibung, warum die Bereichsbeschriftung am besten ist [begin, end). @OP: +1 für das Wortspiel auf bereichsbasierten Schleifen, das kein Wortspiel ist :-)
Kerrek SB
2
Der Vorteil der Nicht-Vorlagenversion besteht darin, dass die Länge Ihrer Schleifen beim Kompilieren nicht bekannt sein muss. Sie können den Integer-Typ natürlich auch als Vorlage verwenden.
CashCow
2
@weeska: Diese Überladung soll ein Postfix-Inkrement implementieren, v++das den Wert zurückgeben soll, bevor die Inkrementoperation stattgefunden hat. Ich würde Ihnen raten, den Unterschied zwischen ++iund i++wo ierklärt wird, zu sein int.
Nawaz
13

Ich habe eine Bibliothek geschrieben, die rangegenau den gleichen Zweck hat, außer dass es sich um einen Laufzeitbereich handelt, und die Idee in meinem Fall kam von Python. Ich habe eine Version zur Kompilierungszeit in Betracht gezogen, aber meiner bescheidenen Meinung nach gibt es keinen wirklichen Vorteil, die Version zur Kompilierungszeit zu erhalten. Sie finden die Bibliothek auf bitbucket und sie befindet sich unter Boost License: Range . Es ist eine Ein-Header-Bibliothek, die mit C ++ 03 kompatibel ist und wie ein Zauber mit bereichsbasierter for-Schleife in C ++ 11 funktioniert :)

Eigenschaften :

  • Ein wahrer Direktzugriffsbehälter mit allem Schnickschnack!

  • Bereiche können lexikographisch verglichen werden.

  • Zwei Funktionen exist(gibt bool zurück) und find(gibt den Iterator zurück), um die Existenz einer Zahl zu überprüfen.

  • Die Bibliothek wird mit CATCH getestet .

  • Beispiele für die grundlegende Verwendung, Arbeiten mit Standardcontainern, Arbeiten mit Standardalgorithmen und Arbeiten mit einem auf Schleifen basierenden Bereich.

Hier ist eine einminütige Einführung . Schließlich begrüße ich jeden Vorschlag zu dieser winzigen Bibliothek.

AraK
quelle
Die einminütige Einführung besagt, dass ich keinen Zugriff auf das Wiki habe. Sie müssen Ihr Wiki öffentlich machen.
Nicol Bolas
@Nicol Bolas Es tut mir wirklich leid, es ist jetzt öffentlich :)
AraK
Danke dafür, es ist unglaublich. Ich habe das Gefühl, dass mehr Leute davon wissen sollten.
Rafael Kitover
5

Ich fand, dass boost::irangedas viel langsamer war als die kanonische Ganzzahlschleife. Deshalb habe ich mich mit einem Präprozessor-Makro für die folgende viel einfachere Lösung entschieden:

#define RANGE(a, b) unsigned a=0; a<b; a++

Dann können Sie folgende Schleife ausführen:

for(RANGE(i, n)) {
    // code here
}

Dieser Bereich beginnt automatisch bei Null. Es kann leicht erweitert werden, um von einer bestimmten Nummer auszugehen.

user2664470
quelle
7
Beachten Sie, dass dies zu for (RANGE(i, flag? n1: n2))überraschenden Ergebnissen führt, da Sie eine der Grundregeln für nicht böse Makros nicht befolgt haben, nämlich alle Ihre Parameter in Klammern zu setzen (in diesem Fall auch b). Ihr Ansatz bietet auch keinen Leistungsvorteil gegenüber dem nicht auf Makros basierenden Ansatz "Range Object" (z. B. Nawaz 'Antwort ).
Quuxplusone
2

Hier ist eine einfachere Form, die für mich gut funktioniert. Gibt es irgendwelche Risiken in meinem Ansatz?

r_iteratorist ein Typ, der sich so weit wie möglich wie a verhält long int. Daher gehen viele Operatoren wie ==und ++einfach auf die long int. Ich 'exponiere' das zugrunde liegende Long Int über die operator long intund operator long int &Conversions.

#include <iostream>
using namespace std;

struct r_iterator {
        long int value;
        r_iterator(long int _v) : value(_v) {}
        operator long int () const { return value; }
        operator long int& ()      { return value; }
        long int operator* () const { return value; }
};
template <long int _begin, long int _end>
struct range {
        static r_iterator begin() {return _begin;}
        static r_iterator end  () {return _end;}
};
int main() {
        for(auto i: range<0,10>()) { cout << i << endl; }
        return 0;
}

( Bearbeiten: - Wir können die Methoden der rangestatischen anstelle von const machen.)

Aaron McDaid
quelle
1

Das mag etwas spät sein, aber ich habe gerade diese Frage gesehen und benutze diese Klasse jetzt schon eine Weile:

#include <iostream>
#include <utility>
#include <stdexcept>

template<typename T, bool reverse = false> struct Range final {
    struct Iterator final{
        T value;
        Iterator(const T & v) : value(v) {}
        const Iterator & operator++() { reverse ? --value : ++value; return *this; }
        bool operator!=(const Iterator & o) { return o.value != value; }
        T operator*() const { return value; }
    };
    T begin_, end_;
    Range(const T & b, const T & e)  : begin_(b), end_(e) {
        if(b > e) throw std::out_of_range("begin > end");
    }

    Iterator begin() const { return reverse ? end_ -1 : begin_; }
    Iterator end() const { return reverse ? begin_ - 1: end_; }

    Range() = delete;
    Range(const Range &) = delete;
};

using UIntRange = Range<unsigned, false>;
using RUIntRange = Range<unsigned, true>;

Verwendung :

int main() {
    std::cout << "Reverse : ";
    for(auto i : RUIntRange(0, 10)) std::cout << i << ' ';
    std::cout << std::endl << "Normal : ";
    for(auto i : UIntRange(0u, 10u)) std::cout << i << ' ';
    std::cout << std::endl;
}
OneOfOne
quelle
0

hast du versucht mit

template <class InputIterator, class Function>
   Function for_each (InputIterator first, InputIterator last, Function f);

Meistens passt die Rechnung.

Z.B

template<class T> void printInt(T i) {cout<<i<<endl;}
void test()
{
 int arr[] = {1,5,7};
 vector v(arr,arr+3);

 for_each(v.begin(),v.end(),printInt);

}

Beachten Sie, dass printInt OFC in C ++ 0x durch ein Lambda ersetzt werden kann. Eine weitere kleine Variation dieser Verwendung könnte sein (ausschließlich für random_iterator).

 for_each(v.begin()+5,v.begin()+10,printInt);

Für Fwd nur Iterator

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Ajeet Ganga
quelle
Wie würden Sie das nutzen? Ich vermute, Sie würden ein Lambda für die Funktion verwenden, aber ich bin mir nicht sicher.
Omnifarious
1
Würde es dir sagen, aber du wirst die Antwort akzeptieren müssen, wenn du denkst, dass es der richtige Weg ist, sie zu benutzen. : P Scherz. Das Beispiel wurde bereits gepostet.
Ajeet Ganga
Sie können hier ein Lambda verwenden, also auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (decltype (* range.first) const & item) {// Code geht hierher});
CashCow
-3

Mit std :: iota () können Sie in C ++ 11 ganz einfach eine aufsteigende Sequenz generieren:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

template<typename T>
std::vector<T> range(T start, T end)
{
  std::vector<T> r(end+1-start, T(0));
  std::iota(r.begin(), r.end(), T(start));//increasing sequence
  return r;
}

int main(int argc, const char * argv[])
{
  for(auto i:range<int>(-3,5))
    std::cout<<i<<std::endl;

  return 0;
}
blauer Skorpion
quelle
3
Die rangeKlasse soll den Bereich modellieren. Sie konstruieren es jedoch buchstäblich. Das ist eine Verschwendung von Speicher und Speicherzugriffen. Die Lösung ist hochgradig redundant, da der Vektor außer der Anzahl der Elemente und dem Wert des ersten Elements (falls vorhanden) keine realen Informationen enthält.
Nicht-Benutzer
Ja, das ist sehr ineffizient.
Omnifarious