Czy istnieje klasa zakresu w C ++ 11 do użytku z zakresem opartym na pętlach?

101

Piszę to chwilę temu:

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>();
}

A to pozwala mi pisać takie rzeczy:

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

Teraz wiem, że to, co napisałem, może nie jest najlepszym kodem. A może istnieje sposób, aby uczynić go bardziej elastycznym i użytecznym. Ale wydaje mi się, że coś takiego powinno być częścią standardu.

Więc to jest? Czy dodano jakąś nową bibliotekę dla iteratorów dla zakresu liczb całkowitych, czy może ogólny zakres obliczonych wartości skalarnych?

Wszelaki
źródło
17
+1. Chciałbym mieć takie klasy w moich narzędziach. :-)
Nawaz
2
A propos, jaki jest sens pisania rangefunkcji szablonu? Nie dodaje niczego do zastosowania, w którym range_classjest używany. To znaczy range<0,10>()i range_class<0,10>()wyglądają dokładnie tak samo!
Nawaz,
2
@Nawaz: Tak, masz rację. Miałem dziwną wizję, że mógłbym sprawić, by funkcja rozróżniała przypadek dynamiczny i statyczny, ale nie sądzę, aby można to zrobić.
Omnifarious
2
@iammilind: Nawaz zadał to samo pytanie 35 minut przed tobą;)
Sebastian Mach
3
Mówiąc pedantycznie, myślę, że ta implementacja ma błąd polegający na tym, że nie można jej użyć do iteracji w całym zakresie liczb całkowitych. Jeśli podłączysz INT_MIN i INT_MAX jako argumenty szablonu, INT_MAX po zwiększeniu spowoduje przepełnienie, dając INT_MIN i powodując nieskończone pętle. „koniec” w STL ma być „jeden za końcem”, który nie może zmieścić się w samym typie liczby całkowitej, więc nie wiem, czy można to skutecznie zaimplementować dla najszerszego typu liczby całkowitej na danej platformie. W przypadku mniejszych typów liczb całkowitych zawsze możesz użyć szerszego typu wewnętrznie ...
Joseph Garvin

Odpowiedzi:

59

Biblioteka standardowa C ++ nie ma takiej biblioteki, ale Boost.Range ma funkcję boost :: counting_range , co z pewnością kwalifikuje. Możesz także użyć boost :: irange , która jest nieco bardziej ukierunkowana na zakres.

Biblioteka zakresów C ++ 20 pozwoli Ci to zrobić za pośrednictwem view::iota(start, end).

Nicol Bolas
źródło
3
Tak, to jest zdecydowanie natura tego, czego szukałem. Cieszę się, że Boost to zrobił. Przykro mi, że komisja standardowa nie włączyła go z jakiegokolwiek powodu. Byłoby to świetne uzupełnienie funkcji bazującej na zasięgu.
Omnifarious
Ta odpowiedź lepiej odpowiada na moje bezpośrednie pytanie, więc wybiorę ją, mimo że odpowiedź Nawaza jest bardzo dobra.
Omnifarious
6
Ostatnio nastąpił duży postęp w dostosowywaniu zakresów do standardu (N4128). Odwiedź stronę github.com/ericniebler/range-v3, aby zapoznać się z ofertą i wdrożeniem referencyjnym.
Ela782
1
@ Ela782: ... a jednak wygląda na to, że nie zobaczymy tego w C ++ 17, prawda?
einpoklum
1
@Andreas Tak, zakresy stały się TS jakiś czas temu, ale nie sądzę, że kiedykolwiek istniała / była implementacja referencyjna, która znalazłaby się w głównych kompilatorach w std::experimental::rangesprzestrzeni nazw. range-v3był zawsze w pewnym sensie implementacją referencyjną, powiedziałbym. Ale teraz uważam, że podstawowe rzeczy z zakresu również zostały ostatnio przegłosowane w C ++ 20, więc rzeczywiście std::wkrótce je dostaniemy ! :-)
Ela782
47

O ile wiem, w C ++ 11 takiej klasy nie ma.

W każdym razie próbowałem ulepszyć Twoją implementację. Zrobiłem to jako szablon , ponieważ nie widzę żadnej korzyści w tworzeniu go jako szablonu . Wręcz przeciwnie, ma jedną poważną wadę: nie można utworzyć zakresu w czasie wykonywania, ponieważ trzeba znać argumenty szablonu w czasie kompilacji.

//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!

Oto kod:

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_;
};

Kod testowy:

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";
}

Wynik:

10 11 12 13 14 15 16 17 18 19

Demo online .

Nawaz
źródło
3
Lubię to. Myślałem o wersji bez szablonu. I przypuszczam, że dobry kompilator zoptymalizowałby to dobrze w przypadku, gdy wartości są w rzeczywistości stałe. Muszę to przetestować.
Omnifarious
10
@Nawaz: Nadal bym go szablonował na typ integralny :) Proponowałbym również alias iteratordo const_iterator, iteratorwyprowadziłem std::iteratori rangezaimplementowałem cbegini cend. Aha i ... dlaczego iterator::operator++zwraca stałą referencję?
Matthieu M.,
6
@RedX: Dijkstra dobrze opisuje, dlaczego etykietowanie zakresu jest najlepsze jako [begin, end). @OP: +1 za grę słów w pętlach opartych na zasięgu, które nie są kalamburami :-)
Kerrek SB
2
Zaletą wersji bez szablonu jest to, że długości pętli nie muszą być znane w czasie kompilacji. Oczywiście możesz utworzyć szablon typu całkowitego.
CashCow
2
@weeska: To przeciążenie ma zaimplementować przyrost przyrostka, v++który ma zwrócić wartość przed wykonaniem operacji przyrostu. Radzę Ci poznać różnicę między ++ia i++gdzie ijest uznane za int.
Nawaz
13

Napisałem bibliotekę wywołaną rangedokładnie w tym samym celu, z wyjątkiem tego, że jest to zakres czasu wykonywania, a pomysł w moim przypadku pochodzi z Pythona. Rozważałem wersję kompilowaną na czas kompilacji, ale moim skromnym zdaniem nie ma żadnej realnej korzyści z uzyskania wersji na czas kompilacji. Bibliotekę można znaleźć na stronie bitbucket i jest ona objęta licencją Boost: zakres . Jest to biblioteka z jednym nagłówkiem, kompatybilna z C ++ 03 i działa jak urok z pętlami opartymi na zakresie w C ++ 11 :)

Cechy :

  • Prawdziwy kontener o swobodnym dostępie ze wszystkimi dzwonkami i gwizdkami!

  • Zakresy można porównać leksykograficznie.

  • Dwie funkcje exist(zwraca wartość bool) i find(zwraca iterator) do sprawdzania istnienia liczby.

  • Biblioteka jest testowana jednostkowo przy użyciu funkcji CATCH .

  • Przykłady podstawowego użycia, praca ze standardowymi kontenerami, praca ze standardowymi algorytmami i praca z zakresem w oparciu o pętle.

Oto jednominutowe wprowadzenie . Na koniec z zadowoleniem przyjmuję wszelkie sugestie dotyczące tej małej biblioteki.

AraK
źródło
Z jednominutowego wprowadzenia wynika, że ​​nie mam dostępu do Wiki. Musisz upublicznić swoją wiki.
Nicol Bolas
@Nicol Bolas Bardzo mi przykro, teraz jest publicznie :)
AraK
Dziękuję za to, to niesamowite. Wydaje mi się, że więcej osób powinno o tym wiedzieć.
Rafael Kitover
5

Okazało się, że jest boost::irangeto znacznie wolniejsze niż kanoniczna pętla liczb całkowitych. Zdecydowałem się więc na następujące, znacznie prostsze rozwiązanie, używając makra preprocesora:

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

Następnie możesz wykonać pętlę w ten sposób:

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

Ten zakres automatycznie zaczyna się od zera. Można go łatwo rozszerzyć, zaczynając od podanej liczby.

user2664470
źródło
7
Zwróć uwagę, że for (RANGE(i, flag? n1: n2))przyniesie to zaskakujące wyniki, ponieważ nie zastosowałeś się do jednej z podstawowych zasad makr niezłych, która polega na umieszczeniu wszystkich parametrów w nawiasach (w tym w tym przypadku b). Twoje podejście nie zapewnia również żadnych korzyści w zakresie wydajności w porównaniu z podejściem innym niż makro, opartym na „obiekcie zakresu” (np . Odpowiedź Nawaza ).
Quuxplusone
2

Oto prostsza forma, która mi odpowiada. Czy jest jakieś ryzyko w moim podejściu?

r_iteratorjest typem, który zachowuje się, w miarę możliwości, jak a long int. Dlatego wiele operatorów, takich jak ==i ++, po prostu przechodzi do long int. „Ujawniam” bazowy long int poprzez konwersje operator long inti operator long int &.

#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;
}

( Edycja: - możemy uczynić metody rangestatycznymi zamiast const.)

Aaron McDaid
źródło
1

To może być trochę za późno, ale właśnie zobaczyłem to pytanie i używam tej klasy od jakiegoś czasu:

#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>;

Stosowanie :

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
źródło
0

próbowałeś użyć

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

W większości przypadków pasuje do rachunku.

Na przykład

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);

}

Zauważ, że printInt można zastąpić OFC przez lambdę w C ++ 0x. Może być jeszcze jedna mała odmiana tego zastosowania (wyłącznie dla random_iterator)

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

Tylko dla iteratora Fwd

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Ajeet Ganga
źródło
Jak byś tego użył? Zgaduję, że użyłbyś lambdy dla funkcji, ale nie jestem pewien.
Omnifarious
1
Powiedziałbym ci, ale będziesz musiał zaakceptować odpowiedź, jeśli uważasz, że jest to właściwy sposób jej użycia. : P Żartuję. Wysłałem już przykład.
Ajeet Ganga,
Możesz tutaj użyć lambdy, więc auto range = myMultiMap.equal_range (klucz); for_each (range.first, range.second, [&] (decltype (* zakres.first) const & item) {// kod idzie tutaj});
CashCow
-3

Możesz łatwo wygenerować rosnącą sekwencję w C ++ 11 używając std :: iota ():

#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;
}
niebieski skorpion
źródło
3
rangeKlasa powinna modelować zakres. Jednak dosłownie go konstruujesz. To strata pamięci i dostępu do pamięci. Rozwiązanie jest wysoce redundantne, ponieważ wektor nie zawiera żadnych prawdziwych informacji poza liczbą elementów i wartością pierwszego elementu (jeśli istnieje).
not-a-user
Tak, to jest bardzo nieefektywne.
Omnifarious