C ++ 11 for-loop basato su intervallo inverso


321

Esiste un adattatore contenitore che potrebbe invertire la direzione degli iteratori in modo che io possa scorrere su un contenitore al contrario con for-loop basato su intervallo?

Con gli iteratori espliciti convertirei questo:

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

in questo:

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

Voglio convertire questo:

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

a questa:

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

C'è una cosa del genere o devo scriverla da solo?


17
Un adattatore per contenitore inverso sembra interessante, ma penso che dovrai scriverlo da solo. Non avremmo questo problema se il comitato standard si affrettasse e adatterà algoritmi basati su range invece di iteratori espliciti.
deft_code

4
@deft_code: "invece di?" Perché vorresti sbarazzarti degli algoritmi basati su iteratore? Sono molto meglio e meno prolissa per i casi in cui non si fanno iterare da begina end, o per trattare con iteratori flusso e simili. Gli algoritmi di intervallo sarebbero fantastici, ma in realtà sono solo zucchero sintattico (tranne la possibilità di una valutazione pigra) rispetto agli algoritmi iteratori.
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; };Può essere migliorato (aggiungendo constversioni, ecc.) ma funziona: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;stampe321
Seth Carnegie

10
@SethCarnegie: E per aggiungere un bel modulo funzionale: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}Quindi puoi semplicemente usarlo for(auto &i: reverse_adapt_container(v)) cout << i;per iterare.
Nicol Bolas,

2
@CR: Non penso che dovrebbe significare questo, perché ciò non lo renderebbe disponibile come sintassi sintetica per i loop in cui l'ordine conta. IMO la concisione è più importante / utile del tuo significato semantico, ma se non apprezzi la concisione della tua guida di stile, puoi darle qualunque cosa tu voglia. È un po 'quello che parallel_forsarebbe, con una condizione "Non mi interessa quale ordine" ancora più forte, se fosse incorporato nello standard in qualche forma. Ovviamente potrebbe anche avere uno zucchero sintattico basato sulla gamma :-)
Steve Jessop

Risposte:


230

In realtà Boost ha tale adattatore: 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';
}

91

In realtà, in C ++ 14 può essere fatto con poche righe di codice.

Questa è un'idea molto simile alla soluzione di @ Paul. A causa di elementi mancanti in C ++ 11, questa soluzione è un po 'inutilmente gonfia (oltre a definire gli odori standard). Grazie a C ++ 14 possiamo renderlo molto più leggibile.

L'osservazione chiave è che i for-loop basati sulla gamma funzionano basandosi su begin()e end()per acquisire gli iteratori della gamma. Grazie ad ADL , non è nemmeno necessario definire la propria personalizzazione begin()e end()nello spazio std :: namespace.

Ecco una soluzione molto semplice di esempio:

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

Funziona come un incantesimo, ad esempio:

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

stampa come previsto

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

NOTA std::rbegin() , std::rend()e std::make_reverse_iterator()non sono ancora implementati in GCC-4.9. Scrivo questi esempi secondo lo standard, ma non verranno compilati in g ++ stabile. Tuttavia, l'aggiunta di stub temporanei per queste tre funzioni è molto semplice. Ecco un'implementazione di esempio, sicuramente non completa ma funziona abbastanza bene per la maggior parte dei casi:

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

35
Poche righe di codice? Perdonami ma sono più di dieci :-)
Jonny il

4
In realtà, è 5-13, a seconda di come contate le righe:) I work-around non dovrebbero essere lì, in quanto fanno parte della libreria. Grazie per avermelo ricordato, tra l'altro, questa risposta deve essere aggiornata per le recenti versioni del compilatore, dove non sono necessarie tutte le righe extra.
Prikso NAI,

2
Penso che ti sei dimenticato forward<T>della tua reverseimplementazione.
SnakE

3
Se lo metti in un'intestazione, sei using namespace stdin un'intestazione, il che non è una buona idea. Oppure mi sfugge qualcosa?
estan

3
In realtà, non dovresti scrivere "using <anything>;" nell'ambito del file in un'intestazione. È possibile migliorare quanto sopra spostando le dichiarazioni di utilizzo nell'ambito della funzione per begin () e end ().
Chris Hartman,

23

Questo dovrebbe funzionare in C ++ 11 senza boost:

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))
{
    ...
}

58
IIRC che aggiunge qualcosa allo spazio dei nomi std è un invito al fallimento epico.
BCS,

35
Non sono sicuro del significato normativo di "fallimento epico", ma il sovraccarico di una funzione nello stdspazio dei nomi ha un comportamento indefinito per 17.6.4.2.1.
Casey,


6
@MuhammadAnnaqeeb La cosa spiacevole è che farlo si scontra esattamente. Non è possibile compilare con entrambe le definizioni. Inoltre, non è necessario che il compilatore abbia la definizione non presente in C ++ 11 e appaia solo in C ++ 14 (la specifica non dice nulla su ciò che non è nello spazio std :: namespace, proprio quello che è). Quindi questo sarebbe molto probabilmente un errore di compilazione sotto un compilatore C ++ 11 conforme agli standard ... molto più probabile che se fosse un nome casuale che non era in C ++ 14! E come sottolineato, è "comportamento indefinito" ... quindi non riuscire a compilare non è il peggio che potrebbe fare.
HostileFork dice di non fidarsi di SE

2
@HostileFork Non c'è collisione di nomi, make_reverse_iteratornon è nello stdspazio dei nomi, quindi non si scontrerà con la versione C ++ 14 di esso.
Paul Fultz II,

11

Per te funziona:

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

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

per esempio:

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

1
puoi per favore spiegare più in dettaglio la tua risposta?
Mostafiz,

si tratta di un tamplate inverso di classe C ++ 11 classe tamplate
Khan Lau,

4

Se è possibile utilizzare range v3 , è possibile utilizzare l'adattatore range inverso ranges::view::reverseche consente di visualizzare il contenitore al contrario.

Un esempio minimo di lavoro:

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

Vedi DEMO 1 .

Nota: secondo Eric Niebler , questa funzione sarà disponibile in C ++ 20 . Questo può essere usato con l' <experimental/ranges/range>intestazione. Quindi la fordichiarazione sarà simile a questa:

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

Vedi DEMO 2


Aggiornamento: lo ranges::viewspazio dei nomi è stato rinominato in ranges::views. Quindi usa ranges::views::reverse.
nac001,

2

Se non utilizzo C ++ 14, allora trovo di seguito la soluzione più semplice.

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

Demo .
Non funziona per i contenitori / tipi di dati (come array), che non ha begin/rbegin, end/rendfunzioni.


0

Potresti semplicemente usare ciò BOOST_REVERSE_FOREACHche scorre indietro. Ad esempio, il codice

#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 il seguente output:

4

3

2

1

0
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.