Pętla for ++ oparta na C ++ 11 w odwrotnym zakresie

321

Czy istnieje adapter kontenera, który odwróciłby kierunek iteratorów, dzięki czemu mogę iterować po kontenerze w odwrotnej kolejności za pomocą pętli for opartej na zakresie?

Za pomocą jawnych iteratorów przekonwertowałbym to:

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

zaangażowany w to:

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

Chcę przekonwertować to:

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

do tego:

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

Czy istnieje coś takiego, czy muszę to sam napisać?

Alex B.
źródło
17
Odwrotny adapter do pojemnika, brzmi interesująco, ale myślę, że będziesz musiał go napisać sam. Nie mielibyśmy tego problemu, gdyby komitet standardowy pospieszał się i dostosował algorytmy oparte na zakresie zamiast jawnych iteratorów.
deft_code
4
@deft_code: „zamiast?” Dlaczego chcesz pozbyć się algorytmów opartych na iteratorach? Są o wiele lepsze i mniej gadatliwy w przypadkach, gdzie nie iteracyjne od begindo end, lub do czynienia z iteratorów strumienia i tym podobne. Algorytmy zakresu byłyby świetne, ale tak naprawdę to tylko cukier syntaktyczny (z wyjątkiem możliwości leniwej oceny) w stosunku do algorytmów iteracyjnych.
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; };Można to ulepszyć (dodając constwersje itp.), ale działa: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;wydruki321
Seth Carnegie
10
@SethCarnegie: I dodać fajną funkcjonalną formę: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}Więc możesz po prostu użyć for(auto &i: reverse_adapt_container(v)) cout << i;iteracji.
Nicol Bolas,
2
@CR: Nie sądzę, że powinno to znaczyć, ponieważ uniemożliwiłoby to zwięzłą składnię dla pętli, w których kolejność ma znaczenie. IMO zwięzłość jest ważniejsza / pożyteczna niż twoje znaczenie semantyczne, ale jeśli nie cenisz zwięzłości, przewodnik po stylu może dać ci jakąkolwiek implikację, jakiej chcesz. Taki parallel_forbyłby cel, z jeszcze silniejszym warunkiem „nie dbam o to, jaki porządek”, gdyby został włączony do normy w jakiejś formie. Oczywiście może mieć również cukier syntaktyczny oparty na zakresie :-)
Steve Jessop

Odpowiedzi:

230

Zwiększ faktycznie ma taki adapter: 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
źródło
90

W rzeczywistości w C ++ 14 można to zrobić za pomocą kilku wierszy kodu.

Jest to bardzo podobny pomysł do rozwiązania @ Paula. Z powodu braków w C ++ 11 to rozwiązanie jest nieco niepotrzebnie wzdęte (plus definiuje się w standardowych zapachach). Dzięki C ++ 14 możemy uczynić go o wiele bardziej czytelnym.

Kluczową obserwacją jest to, że pętle for oparte na zakresie działają w oparciu o begin()i end()w celu uzyskania iteratorów zakresu. Dzięki ADL , jeden nawet nie trzeba określić ich zwyczaj begin()i end()w std :: nazw.

Oto bardzo proste rozwiązanie przykładowe:

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

Działa to jak urok, na przykład:

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

drukuje zgodnie z oczekiwaniami

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

UWAGA std::rbegin() , std::rend()i std::make_reverse_iterator()nie są jeszcze zaimplementowane w GCC-4.9. Piszę te przykłady zgodnie ze standardem, ale nie skompilowałyby się w stabilnym g ++. Niemniej jednak dodawanie tymczasowych kodów pośredniczących dla tych trzech funkcji jest bardzo łatwe. Oto przykładowa implementacja, zdecydowanie niekompletna, ale działa wystarczająco dobrze w większości przypadków:

// --------------------------------------------------
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
źródło
35
Kilka linii kodu? Wybacz mi, ale to już ponad dziesięć :-)
Jonny
4
W rzeczywistości jest to 5-13, w zależności od liczenia linii:) Obejścia nie powinny tam być, ponieważ są częścią biblioteki. Dzięki, że mi przypomniałeś, ta odpowiedź musi zostać zaktualizowana dla najnowszych wersji kompilatora, w których wszystkie dodatkowe wiersze nie są wcale potrzebne.
Prikso NAI,
2
Myślę, że zapomniałeś o forward<T>swojej reverserealizacji.
SnakE
3
Hm, jeśli umieścisz to w nagłówku, znajdziesz się using namespace stdw nagłówku, co nie jest dobrym pomysłem. A może coś mi brakuje?
estan
3
W rzeczywistości nie powinieneś pisać „używając <wszystko>;” w zakresie pliku w nagłówku. Można poprawić powyższe, przenosząc deklaracje użycia do zakresu funkcji dla funkcji begin () i end ().
Chris Hartman
23

Powinno to działać w C ++ 11 bez doładowania:

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
źródło
58
IIRC dodawanie czegokolwiek do przestrzeni nazw std jest zaproszeniem do epickiej porażki.
BCS
35
Nie jestem pewien co do normatywnego znaczenia „epickiego niepowodzenia”, ale przeciążenie funkcji w stdprzestrzeni nazw ma niezdefiniowane zachowanie według 17.6.4.2.1.
Casey
9
Widocznie jest w C ++ 14 , pod tą nazwą.
HostileFork mówi: nie ufaj SE
6
@MuhammadAnnaqeeb Niefortunne jest to, że robi to dokładnie koliduje. Nie można skompilować z obiema definicjami. Ponadto kompilator nie musi mieć definicji, która nie będzie obecna w C ++ 11 i będzie wyświetlana tylko w C ++ 14 (specyfikacja nie mówi nic o tym, czego nie ma w przestrzeni nazw std ::, po prostu co jest). Byłoby to bardzo prawdopodobne niepowodzenie kompilacji w zgodnym ze standardami kompilatorze C ++ 11 ... znacznie bardziej prawdopodobne niż w przypadku losowej nazwy, której nie ma w C ++ 14! I jak już wspomniano, jest to „niezdefiniowane zachowanie” ... więc brak kompilacji nie jest najgorszym, co może zrobić.
HostileFork mówi: nie ufaj
2
@HostileFork Nie ma kolizji nazw, make_reverse_iteratornie ma w stdprzestrzeni nazw, więc nie będzie kolidować z wersją C ++ 14.
Paul Fultz II
11

Czy to działa dla Ciebie:

#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
źródło
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);
}

na przykład:

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
źródło
1
czy możesz wyjaśnić więcej szczegółów swojej odpowiedzi?
Mostafiz
jest to tabliczka klasy C ++ 11 z odwróconym zakresem bazowym
Khan Lau
4

Jeśli możesz użyć zakresu v3 , możesz użyć adaptera zakresu zwrotnego, ranges::view::reversektóry umożliwia przeglądanie kontenera w odwrotnej kolejności.

Minimalny działający przykład:

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

Zobacz DEMO 1 .

Uwaga: Według Erica Nieblera ta funkcja będzie dostępna w C ++ 20 . Można tego użyć z <experimental/ranges/range>nagłówkiem. Wtedy foroświadczenie będzie wyglądać następująco:

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

Zobacz DEMO 2

PW
źródło
Aktualizacja: ranges::viewprzestrzeń nazw została zmieniona na ranges::views. Więc użyj ranges::views::reverse.
nac001
2

Jeśli nie używam C ++ 14, znajdę poniżej najprostszego rozwiązania.

#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 .
Nie działa dla kontenerów / typów danych (takich jak tablica), które nie mają begin/rbegin, end/rendfunkcji.

iammilind
źródło
0

Możesz po prostu użyć tej BOOST_REVERSE_FOREACHiteracji do tyłu. Na przykład kod

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

generuje następujące dane wyjściowe:

4

3

2

1

0
Catriel
źródło