C ++ 11 de bucle for basado en rango inverso

321

¿Hay un adaptador de contenedor que invierta la dirección de los iteradores para que pueda iterar sobre un contenedor en reversa con un bucle for basado en rango?

Con iteradores explícitos convertiría esto:

for (auto i = c.begin(); i != c.end(); ++i) { ...

dentro de esto:

for (auto i = c.rbegin(); i != c.rend(); ++i) { ...

Quiero convertir esto:

for (auto& i: c) { ...

a esto:

for (auto& i: std::magic_reverse_adapter(c)) { ...

¿Existe tal cosa o tengo que escribirla yo mismo?

Alex B
fuente
17
Un adaptador de contenedor inverso, suena interesante, pero creo que tendrá que escribirlo usted mismo. No tendríamos este problema si el comité estándar se apurara y adaptara algoritmos basados ​​en rango en lugar de iteradores explícitos.
deft_code
44
@deft_code: "en lugar de?" ¿Por qué querrías deshacerte de los algoritmos basados ​​en iteradores? Son mucho mejores y menos detallados para los casos en los que no se itera desde o beginhacia end, o para tratar con iteradores de flujo y similares. Los algoritmos de rango serían geniales, pero en realidad son solo azúcar sintáctica (a excepción de la posibilidad de una evaluación diferida) sobre los algoritmos iteradores.
Nicol Bolas
17
@deft_code template<typename T> class reverse_adapter { public: reverse_adapter(T& c) : c(c) { } typename T::reverse_iterator begin() { return c.rbegin(); } typename T::reverse_iterator end() { return c.rend(); } private: T& c; };Se puede mejorar (agregar constversiones, etc.) pero funciona: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;impresiones321
Seth Carnegie
10
@SethCarnegie: Y para agregar una buena forma funcional: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}entonces puedes usar for(auto &i: reverse_adapt_container(v)) cout << i;para iterar.
Nicol Bolas
2
@CR: No creo que deba significar eso, porque eso haría que no esté disponible como una sintaxis concisa para los bucles donde el orden sí importa. En mi opinión, la concisión es más importante / útil que su significado semántico, pero si no valora la concisión de su guía de estilo puede darle la implicación que desee. Para eso parallel_forsería, con una condición aún más fuerte de "No me importa qué orden", si se incorporara al estándar de alguna forma. Por supuesto, también podría tener un azúcar sintáctico basado en rango :-)
Steve Jessop

Respuestas:

230

En realidad Boost tiene como adaptador: boost::adaptors::reverse.

#include <list>
#include <iostream>
#include <boost/range/adaptor/reversed.hpp>

int main()
{
    std::list<int> x { 2, 3, 5, 7, 11, 13, 17, 19 };
    for (auto i : boost::adaptors::reverse(x))
        std::cout << i << '\n';
    for (auto i : x)
        std::cout << i << '\n';
}
kennytm
fuente
91

En realidad, en C ++ 14 se puede hacer con muy pocas líneas de código.

Esta es una idea muy similar a la solución de @ Paul. Debido a las cosas que faltan en C ++ 11, esa solución está un poco innecesariamente hinchada (además de definirse en olores estándar). Gracias a C ++ 14 podemos hacerlo mucho más legible.

La observación clave es que los bucles for basados ​​en rango funcionan confiando en begin()y end()para adquirir los iteradores del rango. Gracias a ADL , uno ni siquiera necesita definir su costumbre begin()y end()en el espacio de nombres std ::.

Aquí hay una solución de muestra muy simple:

// -------------------------------------------------------------------
// --- Reversed iterable

template <typename T>
struct reversion_wrapper { T& iterable; };

template <typename T>
auto begin (reversion_wrapper<T> w) { return std::rbegin(w.iterable); }

template <typename T>
auto end (reversion_wrapper<T> w) { return std::rend(w.iterable); }

template <typename T>
reversion_wrapper<T> reverse (T&& iterable) { return { iterable }; }

Esto funciona como un encanto, por ejemplo:

template <typename T>
void print_iterable (std::ostream& out, const T& iterable)
{
    for (auto&& element: iterable)
        out << element << ',';
    out << '\n';
}

int main (int, char**)
{
    using namespace std;

    // on prvalues
    print_iterable(cout, reverse(initializer_list<int> { 1, 2, 3, 4, }));

    // on const lvalue references
    const list<int> ints_list { 1, 2, 3, 4, };
    for (auto&& el: reverse(ints_list))
        cout << el << ',';
    cout << '\n';

    // on mutable lvalue references
    vector<int> ints_vec { 0, 0, 0, 0, };
    size_t i = 0;
    for (int& el: reverse(ints_vec))
        el += i++;
    print_iterable(cout, ints_vec);
    print_iterable(cout, reverse(ints_vec));

    return 0;
}

imprime como se esperaba

4,3,2,1,
4,3,2,1,
3,2,1,0,
0,1,2,3,

NOTA std::rbegin() , std::rend()y std::make_reverse_iterator()aún no se implementan en GCC-4.9. Escribo estos ejemplos de acuerdo con el estándar, pero no se compilan en g ++ estable. Sin embargo, agregar stubs temporales para estas tres funciones es muy fácil. Aquí hay una implementación de muestra, definitivamente no completa pero funciona lo suficientemente bien para la mayoría de los casos:

// --------------------------------------------------
template <typename I>
reverse_iterator<I> make_reverse_iterator (I i)
{
    return std::reverse_iterator<I> { i };
}

// --------------------------------------------------
template <typename T>
auto rbegin (T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}

// const container variants

template <typename T>
auto rbegin (const T& iterable)
{
    return make_reverse_iterator(iterable.end());
}

template <typename T>
auto rend (const T& iterable)
{
    return make_reverse_iterator(iterable.begin());
}
Prikso NAI
fuente
35
¿Pocas líneas de código? Perdóname pero eso es más de diez :-)
Jonny
44
En realidad, son 5-13, dependiendo de cómo cuente las líneas:) Las soluciones alternativas no deberían estar allí, ya que son parte de la biblioteca. Gracias por recordarme, por cierto, esta respuesta debe actualizarse para las versiones recientes del compilador, donde no se necesitan todas las líneas adicionales.
Prikso NAI
2
Creo que te olvidaste forward<T>en tu reverseimplementación.
SnakE
3
Hm, si pones esto en un encabezado, estás using namespace stden un encabezado, lo cual no es una buena idea. ¿O me estoy perdiendo algo?
estan
3
En realidad, no deberías escribir "using <anything>;" en el alcance del archivo en un encabezado. Puede mejorar lo anterior moviendo las declaraciones de uso al ámbito de la función para begin () y end ().
Chris Hartman
23

Esto debería funcionar en C ++ 11 sin impulso:

namespace std {
template<class T>
T begin(std::pair<T, T> p)
{
    return p.first;
}
template<class T>
T end(std::pair<T, T> p)
{
    return p.second;
}
}

template<class Iterator>
std::reverse_iterator<Iterator> make_reverse_iterator(Iterator it)
{
    return std::reverse_iterator<Iterator>(it);
}

template<class Range>
std::pair<std::reverse_iterator<decltype(begin(std::declval<Range>()))>, std::reverse_iterator<decltype(begin(std::declval<Range>()))>> make_reverse_range(Range&& r)
{
    return std::make_pair(make_reverse_iterator(begin(r)), make_reverse_iterator(end(r)));
}

for(auto x: make_reverse_range(r))
{
    ...
}
Paul Fultz II
fuente
58
El IIRC que agrega algo al espacio de nombres estándar es una invitación al fracaso épico.
BCS
35
No estoy seguro sobre el significado normativo de "error épico", pero sobrecargar una función en el stdespacio de nombres tiene un comportamiento indefinido según 17.6.4.2.1.
Casey
99
Está en C ++ 14 aparentemente , bajo este nombre.
HostileFork dice que no confíes en SE
66
@MuhammadAnnaqeeb Lo desafortunado es que hacerlo choca exactamente. No puede compilar con ambas definiciones. Además, no se requiere que el compilador tenga la definición no presente en C ++ 11 y solo aparezca en C ++ 14 (la especificación no dice nada sobre lo que no está en el espacio de nombres std ::, solo lo que está). Por lo tanto, este sería un error de compilación muy probable en un compilador de C ++ 11 compatible con los estándares ... ¡mucho más probable que si se tratara de un nombre aleatorio que no estaba en C ++ 14! Y como se señaló, es un "comportamiento indefinido" ... así que no compilar no es lo peor que podría hacer.
HostileFork dice que no confíes en SE
2
@HostileFork No hay colisión de nombres, make_reverse_iteratorno está en el stdespacio de nombres, por lo que no chocará con la versión de C ++ 14.
Paul Fultz II
11

Esto funciona para tí:

#include <iostream>
#include <list>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/iterator_range.hpp>

int main(int argc, char* argv[]){

  typedef std::list<int> Nums;
  typedef Nums::iterator NumIt;
  typedef boost::range_reverse_iterator<Nums>::type RevNumIt;
  typedef boost::iterator_range<NumIt> irange_1;
  typedef boost::iterator_range<RevNumIt> irange_2;

  Nums n = {1, 2, 3, 4, 5, 6, 7, 8};
  irange_1 r1 = boost::make_iterator_range( boost::begin(n), boost::end(n) );
  irange_2 r2 = boost::make_iterator_range( boost::end(n), boost::begin(n) );


  // prints: 1 2 3 4 5 6 7 8 
  for(auto e : r1)
    std::cout << e << ' ';

  std::cout << std::endl;

  // prints: 8 7 6 5 4 3 2 1
  for(auto e : r2)
    std::cout << e << ' ';

  std::cout << std::endl;

  return 0;
}
Arlen
fuente
7
template <typename C>
struct reverse_wrapper {

    C & c_;
    reverse_wrapper(C & c) :  c_(c) {}

    typename C::reverse_iterator begin() {return c_.rbegin();}
    typename C::reverse_iterator end() {return c_.rend(); }
};

template <typename C, size_t N>
struct reverse_wrapper< C[N] >{

    C (&c_)[N];
    reverse_wrapper( C(&c)[N] ) : c_(c) {}

    typename std::reverse_iterator<const C *> begin() { return std::rbegin(c_); }
    typename std::reverse_iterator<const C *> end() { return std::rend(c_); }
};


template <typename C>
reverse_wrapper<C> r_wrap(C & c) {
    return reverse_wrapper<C>(c);
}

p.ej:

int main(int argc, const char * argv[]) {
    std::vector<int> arr{1, 2, 3, 4, 5};
    int arr1[] = {1, 2, 3, 4, 5};

    for (auto i : r_wrap(arr)) {
        printf("%d ", i);
    }
    printf("\n");

    for (auto i : r_wrap(arr1)) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}
Khan Lau
fuente
1
¿podría explicar más detalles de su respuesta?
Mostafiz
este es un tamplate de clase C ++ 11 de bucle de base de rango inverso
Khan Lau
4

Si puede usar el rango v3 , puede usar el adaptador de rango inverso ranges::view::reverseque le permite ver el contenedor al revés.

Un ejemplo de trabajo mínimo:

#include <iostream>
#include <vector>
#include <range/v3/view.hpp>

int main()
{
    std::vector<int> intVec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto const& e : ranges::view::reverse(intVec)) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;

    for (auto const& e : intVec) {
        std::cout << e << " ";   
    }
    std::cout << std::endl;
}

Ver DEMO 1 .

Nota: Según Eric Niebler , esta función estará disponible en C ++ 20 . Esto se puede usar con el <experimental/ranges/range>encabezado. Entonces la fordeclaración se verá así:

for (auto const& e : view::reverse(intVec)) {
       std::cout << e << " ";   
}

Ver DEMO 2

PW
fuente
Actualización: se ranges::viewha cambiado el nombre del espacio de nombres ranges::views. Entonces, use ranges::views::reverse.
nac001
2

Si no usa C ++ 14, entonces encuentro a continuación la solución más simple.

#define METHOD(NAME, ...) auto NAME __VA_ARGS__ -> decltype(m_T.r##NAME) { return m_T.r##NAME; }
template<typename T>
struct Reverse
{
  T& m_T;

  METHOD(begin());
  METHOD(end());
  METHOD(begin(), const);
  METHOD(end(), const);
};
#undef METHOD

template<typename T>
Reverse<T> MakeReverse (T& t) { return Reverse<T>{t}; }

Demostración .
No funciona para los contenedores / tipos de datos (como la matriz), que no tiene begin/rbegin, end/rendfunciones.

iammilind
fuente
0

Simplemente podría usar el BOOST_REVERSE_FOREACHque itera hacia atrás. Por ejemplo, el código

#include <iostream>
#include <boost\foreach.hpp>

int main()
{
    int integers[] = { 0, 1, 2, 3, 4 };
    BOOST_REVERSE_FOREACH(auto i, integers)
    {
        std::cout << i << std::endl;
    }
    return 0;
}

genera el siguiente resultado:

4

3

2

1

0
Catriel
fuente