¿Existe una clase de rango en C ++ 11 para usar con bucles for basados ​​en rango?

101

Me encontré escribiendo esto hace un poco:

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

Y esto me permite escribir cosas como esta:

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

Ahora, sé que lo que escribí quizás no sea el mejor código. Y tal vez haya una forma de hacerlo más flexible y útil. Pero me parece que algo como esto debería haberse incluido en el estándar.

¿Por lo que es? ¿Se agregó algún tipo de biblioteca nueva para iteradores en un rango de enteros, o tal vez un rango genérico de valores escalares calculados?

De todo género
fuente
17
+1. Me gustaría tener tales clases en mis utilidades. :-)
Nawaz
2
Por cierto, ¿de qué sirve escribir rangela función de plantilla? No agrega nada al uso en el que range_classse usa. Quiero decir, ¡ range<0,10>()y range_class<0,10>()lucir exactamente igual!
Nawaz
2
@Nawaz: Sí, tienes razón. Tuve una visión extraña de que podía hacer que la función se encargara de diferenciar entre el caso dinámico y estático, pero no creo que se pueda hacer.
Omnifarious
2
@iammilind: Nawaz hizo la misma pregunta 35 minutos antes que usted;)
Sebastian Mach
3
Para ser pedante, creo que esta implementación tiene un error, que es que no se puede usar para iterar en todo el rango de enteros. Si conecta INT_MIN e INT_MAX como sus argumentos de plantilla, INT_MAX cuando se incrementa, se desbordará y dará INT_MIN y provocará bucles infinitos. Se supone que "end" en STL es "uno más allá del final", lo que no puede caber dentro del tipo de entero en sí, por lo que no sé si esto se puede implementar de manera eficiente para el tipo de entero más ancho en una plataforma determinada. Para tipos enteros más pequeños, siempre puede hacer que use un tipo más amplio internamente ...
Joseph Garvin

Respuestas:

59

La biblioteca estándar de C ++ no tiene uno, pero Boost.Range tiene boost :: counting_range , que ciertamente califica. También puede usar boost :: irange , que tiene un alcance un poco más centrado.

La biblioteca de rango de C ++ 20 le permitirá hacer esto a través de view::iota(start, end).

Nicol Bolas
fuente
3
Sí, esa es definitivamente la naturaleza de lo que estaría buscando. Me alegro de que Boost lo haya hecho. Me entristece que el comité estándar no lo haya incluido por alguna razón. Habría sido un gran complemento para la función range-base-for.
Omnifarious
Esta respuesta responde mejor a mi pregunta directa, por lo que la elegiré, aunque la respuesta de Nawaz es muy buena.
Omnifarious
6
Últimamente se ha avanzado mucho para conseguir rangos dentro del estándar (N4128). Consulte github.com/ericniebler/range-v3 para ver una propuesta y una implementación de referencia.
Ela782
1
@ Ela782: ... y, sin embargo, parece que no lo veremos en C ++ 17, ¿verdad?
einpoklum
1
@Andreas Sí, los rangos se convirtieron en un TS hace un tiempo, pero no creo que haya / alguna vez haya una implementación de referencia que se haya convertido en los principales compiladores bajo el std::experimental::rangesespacio de nombres. range-v3Siempre fue una especie de implementación de referencia, diría yo. Pero ahora creo que las cosas del rango básico también se votaron recientemente en C ++ 20, ¡así que de hecho lo tendremos std::pronto! :-)
Ela782
47

Hasta donde yo sé, no existe tal clase en C ++ 11.

De todos modos, intenté mejorar su implementación. Lo hice sin plantilla , ya que no veo ninguna ventaja en lo que es template . Por el contrario, tiene una desventaja importante: que no puede crear el rango en tiempo de ejecución, ya que necesita conocer los argumentos de la plantilla en el tiempo de compilación.

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

Aquí está el código:

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

Código de prueba:

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

Salida:

10 11 12 13 14 15 16 17 18 19

Demostración en línea .

Nawaz
fuente
3
Me gusta. Pensé en una versión sin plantilla. Y supongo que un buen compilador lo optimizaría bien en el caso de que los valores sean realmente constantes. Tendré que probar eso.
Omnifarious
10
@Nawaz: Todavía lo modelaría, en el tipo integral :) También propondría un alias iteratorpara const_iterator, tener iteratorderivado std::iteratory tener rangeimplementado cbeginy cend. Ah, y ... ¿por qué iterator::operator++devuelve una referencia constante ?
Matthieu M.
6
@RedX: Dijkstra tiene un buen escrito sobre por qué el etiquetado de rango es mejor como [begin, end). @OP: +1 para el juego de palabras en bucles basados ​​en rango que no es un juego de palabras :-)
Kerrek SB
2
La ventaja de la versión sin plantilla es que no es necesario conocer la longitud de los bucles en el momento de la compilación. Por supuesto, puede hacer que el tipo de número entero tenga una plantilla.
CashCow
2
@weeska: Se supone que esa sobrecarga implementa el incremento de postfijo v++que se supone que devuelve el valor antes de que tuviera lugar la operación de incremento. Te aconsejo que explores la diferencia entre ++iy i++dónde ise declara estar int.
Nawaz
13

Escribí una biblioteca llamada rangepara exactamente el mismo propósito, excepto que es un rango de tiempo de ejecución, y la idea en mi caso vino de Python. Consideré una versión en tiempo de compilación, pero en mi humilde opinión, no hay ninguna ventaja real para ganar la versión en tiempo de compilación. Puede encontrar la biblioteca en bitbucket, y está bajo Boost License: Range . Es una biblioteca de un encabezado, compatible con C ++ 03 y funciona a la perfección con bucles for basados ​​en rangos en C ++ 11 :)

Caracteristicas :

  • ¡Un verdadero contenedor de acceso aleatorio con todas las campanas y silbidos!

  • Los rangos se pueden comparar lexicográficamente.

  • Dos funciones exist(devuelve bool) y find(devuelve iterador) para comprobar la existencia de un número.

  • La biblioteca se prueba unitariamente mediante CATCH .

  • Ejemplos de uso básico, trabajar con contenedores estándar, trabajar con algoritmos estándar y trabajar con rangos basados ​​en bucles for.

Aquí hay una introducción de un minuto . Finalmente, agradezco cualquier sugerencia sobre esta pequeña biblioteca.

AraK
fuente
La introducción de un minuto dice que no tengo acceso al Wiki. Necesitas hacer pública tu wiki.
Nicol Bolas
@Nicol Bolas Lo siento mucho, ahora es público :)
AraK
Gracias por esto, es asombroso. Siento que más gente debería saberlo.
Rafael Kitover
5

Descubrí que boost::irangeera mucho más lento que el ciclo de enteros canónicos. Así que me decidí por la siguiente solución mucho más simple usando una macro de preprocesador:

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

Entonces puedes hacer un bucle como este:

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

Este rango comienza automáticamente desde cero. Se podría ampliar fácilmente para empezar desde un número determinado.

usuario2664470
fuente
7
Tenga en cuenta que for (RANGE(i, flag? n1: n2))producirá resultados sorprendentes, porque no siguió una de las Reglas básicas de las macros no malvadas, que es poner entre paréntesis todos sus parámetros (incluido, en este caso, b). Su enfoque tampoco proporciona ningún beneficio de rendimiento sobre el enfoque no macro basado en "objeto de rango" (por ejemplo, la respuesta de Nawaz ).
Quuxplusone
2

Aquí hay una forma más simple que me está funcionando muy bien. ¿Existe algún riesgo en mi enfoque?

r_iteratores un tipo que se comporta, en la medida de lo posible, como un long int. Por lo tanto, muchos operadores, como ==y ++, simplemente pasan a long int. 'Expongo' el int largo subyacente a través de las conversiones operator long inty 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;
}

( Editar: podemos hacer que los métodos sean rangeestáticos en lugar de const.)

Aaron McDaid
fuente
1

Esto puede ser un poco tarde, pero acabo de ver esta pregunta y he estado usando esta clase por un tiempo:

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

Uso:

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;
}
Uno de uno
fuente
0

has intentado usar

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

La mayoría de las veces se ajusta a los requisitos.

P.ej

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

}

Tenga en cuenta que printInt puede reemplazarse OFC con una lambda en C ++ 0x. También podría ser una pequeña variación más de este uso (estrictamente para random_iterator)

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

Para iterador solo de Fwd

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Ajeet Ganga
fuente
¿Cómo usarías esto? Supongo que usarías una lambda para la función, pero no estoy seguro.
Omnifarious
1
Te lo diría, pero tendrás que aceptar la respuesta si crees que es la forma correcta de usarla. : P Es broma. Ya publicó el ejemplo.
Ajeet Ganga
Puede usar una lambda aquí para que auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (decltype (* range.first) const & item) {// el código va aquí});
CashCow
-3

Puede generar fácilmente una secuencia creciente en 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;
}
escorpión azul
fuente
3
La rangeclase modelará el rango. Sin embargo, literalmente lo estás construyendo. Eso es una pérdida de memoria y accesos a la memoria. La solución es altamente redundante, porque el vector no contiene información real excepto por el número de elementos y el valor del primer elemento (si existe).
no es un usuario
Sí, esto es muy ineficiente.
Omnifarious