Esiste una classe di intervallo in C ++ 11 da utilizzare con i cicli for basati su intervallo?


101

Mi sono ritrovato a scrivere questo solo un po 'fa:

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

E questo mi permette di scrivere cose come questa:

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

Ora, so che quello che ho scritto forse non è il miglior codice. E forse c'è un modo per renderlo più flessibile e utile. Ma mi sembra che qualcosa del genere avrebbe dovuto far parte dello standard.

Quindi è così? È stata aggiunta una sorta di nuova libreria per gli iteratori su un intervallo di numeri interi o forse un intervallo generico di valori scalari calcolati?


17
+1. Mi piacerebbe avere tali classi nelle mie utilità. :-)
Nawaz

2
A proposito, qual è lo scopo di scrivere la rangefunzione template? Non aggiunge nulla all'utilizzo in cui range_classviene utilizzato. Voglio dire, range<0,10>()e range_class<0,10>()sembra esattamente lo stesso!
Nawaz

2
@Nawaz: Sì, hai ragione. Ho avuto una strana visione che avrei potuto fare in modo che la funzione gestisse la differenziazione tra il caso dinamico e statico, ma non penso che possa essere fatto.
Omnifario

2
@iammilind: Nawaz ha posto la stessa domanda 35 minuti prima di te;)
Sebastian Mach

3
Per essere pedante, penso che questa implementazione abbia un bug, ovvero che non puoi usarla per iterare sull'intero intervallo di numeri interi. Se si inserisce INT_MIN e INT_MAX come argomenti del modello, INT_MAX quando incrementato darà un overflow a INT_MIN e causerà loop infiniti. "end" in STL dovrebbe essere "uno oltre la fine" che non può entrare nel tipo intero stesso, quindi non so se questo può effettivamente essere implementato in modo efficiente per il tipo intero più ampio su una data piattaforma. Per i tipi interi più piccoli puoi sempre utilizzare un tipo più ampio internamente ...
Joseph Garvin,

Risposte:


59

La libreria standard C ++ non ne ha una, ma Boost.Range ha boost :: counting_range , che certamente si qualifica. Potresti anche usare boost :: irange , che è un po 'più mirato.

La libreria di intervallo di C ++ 20 ti consentirà di farlo tramite view::iota(start, end).


3
Sì, questa è decisamente la natura di ciò che cercherò. Sono contento che Boost l'abbia fatto. Sono triste che il comitato standard non lo abbia incluso per qualsiasi motivo. Sarebbe stato un ottimo complemento alla funzionalità di base della gamma.
Omnifario

Questa risposta risponde meglio alla mia domanda diretta, quindi la sceglierò, anche se la risposta di Nawaz è molto buona.
Omnifario

6
Ultimamente sono stati fatti molti progressi per inserire le gamme nello standard (N4128). Vedi github.com/ericniebler/range-v3 per una proposta e un'implementazione di riferimento.
Ela782

1
@ Ela782: ... eppure sembra che non lo vedremo in C ++ 17, giusto?
einpoklum

1
@Andreas Sì, gli intervalli sono diventati un TS qualche tempo fa, ma non credo che ci sia / sia mai stata un'implementazione di riferimento che sia entrata nei principali compilatori sotto lo std::experimental::rangesspazio dei nomi. range-v3è sempre stata una specie di implementazione di riferimento, direi. Ma ora credo che anche le cose di base dell'intervallo siano state recentemente votate in C ++ 20, quindi lo introdurremo std::presto! :-)
Ela782

47

Per quanto ne so, non esiste una classe del genere in C ++ 11.

Comunque, ho cercato di migliorare la tua implementazione. L'ho reso non modello , poiché non vedo alcun vantaggio nel renderlo modello . Al contrario, ha uno svantaggio principale: che non è possibile creare l'intervallo in fase di esecuzione, poiché è necessario conoscere gli argomenti del modello in fase di compilazione stessa.

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

Ecco il codice:

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

Codice di prova:

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

Produzione:

10 11 12 13 14 15 16 17 18 19

Demo onine .


3
Mi piace. Ho pensato a una versione senza modello. E suppongo che un buon compilatore lo ottimizzerebbe bene nel caso in cui i valori siano effettivamente costanti. Lo dovrò testare.
Omnifarious

10
@Nawaz: sarei ancora template che, sul tipo integrale :) Mi piacerebbe anche propongo di alias iteratora const_iterator, avere iteratorderivano da std::iteratore hanno rangeimplementare cbegine cend. Oh e ... perché iterator::operator++restituisce un riferimento const ?
Matthieu M.

6
@ RedX: Dijkstra ha una buona recensione sul perché l'etichettatura della gamma è la migliore [begin, end). @OP: +1 per il gioco di parole sui loop basati sulla gamma che non è un gioco di
parole

2
Il vantaggio della versione senza modello è che non è necessario conoscere la lunghezza dei cicli in fase di compilazione. Ovviamente potresti rendere modello il tipo intero.
CashCow

2
@weeska: Quel sovraccarico dovrebbe implementare l'incremento di suffisso v++che dovrebbe restituire il valore prima che abbia avuto luogo l'operazione di incremento. Ti consiglio di esplorare la differenza tra ++ie i++dove isi dichiara di essere int.
Nawaz

13

Ho scritto una libreria chiamata rangeesattamente per lo stesso scopo tranne che è un intervallo di tempo di esecuzione e l'idea nel mio caso è venuta da Python. Ho considerato una versione in fase di compilazione, ma a mio modesto parere non vi è alcun vantaggio reale nel guadagnare la versione in fase di compilazione. Puoi trovare la libreria su bitbucket, ed è sotto Boost License: Range . È una libreria a una sola intestazione, compatibile con C ++ 03 e funziona come un fascino con i cicli for basati su intervallo in C ++ 11 :)

Caratteristiche :

  • Un vero contenitore ad accesso casuale con tutti i campanelli e fischietti!

  • Gli intervalli possono essere confrontati lessicograficamente.

  • Due funzioni exist(restituisce bool) e find(restituisce l'iteratore) per verificare l'esistenza di un numero.

  • La libreria è testata dall'unità utilizzando CATCH .

  • Esempi di utilizzo di base, lavoro con contenitori standard, lavoro con algoritmi standard e lavoro con cicli basati su intervallo.

Ecco un'introduzione di un minuto . Infine, accolgo con favore qualsiasi suggerimento su questa piccola biblioteca.


L'introduzione di un minuto dice che non ho accesso al Wiki. Devi rendere pubblica la tua wiki.
Nicol Bolas

@Nicol Bolas Mi dispiace davvero, ora è pubblico :)
AraK

Grazie per questo, è fantastico. Sento che più persone dovrebbero saperlo.
Rafael Kitover

5

Ho scoperto che boost::irangeera molto più lento del ciclo intero canonico. Quindi ho optato per la seguente soluzione molto più semplice utilizzando una macro del preprocessore:

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

Quindi puoi eseguire il ciclo in questo modo:

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

Questo intervallo parte automaticamente da zero. Potrebbe essere facilmente esteso per iniziare da un dato numero.


7
Nota che for (RANGE(i, flag? n1: n2))produrrà risultati sorprendenti, perché non sei riuscito a seguire una delle regole di base delle macro non malvagie, che è quella di mettere tra parentesi tutti i tuoi parametri (inclusi, in questo caso, b). Il tuo approccio inoltre non fornisce alcun vantaggio in termini di prestazioni rispetto all'approccio non macro, basato su "oggetto intervallo" (ad esempio, la risposta di Nawaz ).
Quuxplusone

2

Ecco un modulo più semplice che funziona bene per me. Ci sono rischi nel mio approccio?

r_iteratorè un tipo che si comporta, per quanto possibile, come un file long int. Pertanto molti operatori come ==e ++, passano semplicemente al file long int. "Espongo" il long int sottostante tramite le conversioni operator long inte 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;
}

( Modifica: - possiamo rendere i metodi di rangestatic invece di const.)


1

Potrebbe essere un po 'tardi, ma ho appena visto questa domanda e sto usando questo corso da un po' di tempo:

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

Utilizzo:

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

0

hai provato a usare

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

Il più delle volte si adatta al conto.

Per esempio

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

}

Nota che printInt può OFC essere sostituito con un lambda in C ++ 0x. Anche un'altra piccola variazione di questo utilizzo potrebbe essere (rigorosamente per random_iterator)

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

Per Fwd solo iteratore

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

Come lo useresti? Immagino che useresti un lambda per la funzione, ma non ne sono sicuro.
Omnifario

1
Te lo direi, ma dovrai accettare la risposta se pensi che sia il modo giusto di usarla. : P Scherzando. Ha già pubblicato l'esempio.
Ajeet Ganga

Puoi usare un lambda qui, quindi auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (decltype (* range.first) const & item) {// code goes here});
CashCow

-3

Puoi facilmente generare una sequenza crescente in C ++ 11 usando 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;
}

3
La rangeclasse modellerà la gamma. Comunque lo stai letteralmente costruendo. Questo è uno spreco di memoria e accessi alla memoria. La soluzione è altamente ridondante, perché il vettore non contiene informazioni reali ad eccezione del numero di elementi e del valore del primo elemento (se esiste).
non utente

Sì, questo è molto inefficiente.
Omnifarious
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.