C ++ 11 обратный цикл на основе диапазона for - PullRequest
296 голосов
/ 17 декабря 2011

Есть ли контейнерный адаптер, который бы изменял направление итераторов, чтобы я мог перебирать контейнер в обратном направлении с помощью цикла for-based?

С явными итераторами я бы преобразовал это:

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

в это:

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

Я хочу преобразовать это:

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

к этому:

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

Есть ли такая вещь, или я должен написать это сам?

Ответы [ 8 ]

217 голосов
/ 17 декабря 2011

На самом деле Boost имеет такой адаптер: 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';
}
76 голосов
/ 25 января 2015

На самом деле, в C ++ 14 это можно сделать с помощью всего лишь нескольких строк кода.

По идее это очень похоже на решение @ Paul.Из-за того, что в C ++ 11 чего-то не хватает, это решение немного раздуто (плюс определение в запахах std).Благодаря C ++ 14 мы можем сделать его намного более читабельным.

Ключевое наблюдение заключается в том, что циклические циклы for работают, полагаясь на begin() и end() для получения итераторов диапазона.,Благодаря ADL даже не нужно определять их пользовательские begin() и end() в пространстве имен std ::.

Вот очень простое решение:

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

Это работает как брелок, например:

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

печатает как положено

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

ПРИМЕЧАНИЕ std::rbegin(), std::rend() и std::make_reverse_iterator() еще не реализованы в GCC-4.9.Я пишу эти примеры в соответствии со стандартом, но они не будут компилироваться в стабильный g ++.Тем не менее, добавить временные заглушки для этих трех функций очень просто.Вот пример реализации, определенно не завершен , но работает достаточно хорошо для большинства случаев:

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

ОБНОВЛЕНИЕ 22 октября 2017

Благодаря estan для указания на это.

Исходная реализация примера ответа использует using namespace std;, что приведет к тому, что любой файл, включая эту реализацию (который должен быть в заголовочном файле), также импортируетцелое std пространство имен.

Пересмотрен пример реализации для предложения using std::rbegin, std::rend вместо.

22 голосов
/ 23 января 2013

Это должно работать в C ++ 11 без повышения:

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))
{
    ...
}
11 голосов
/ 17 декабря 2011

Работает ли это для вас:

#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;
}
6 голосов
/ 29 апреля 2016
    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);
    }

например:

    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;
    }
4 голосов
/ 28 января 2019

Если вы можете использовать диапазон v3 , вы можете использовать адаптер обратного диапазона ranges::view::reverse, который позволяет просматривать контейнер в обратном направлении.

Минимальный рабочий пример:

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

См. ДЕМО 1 .

Примечание: Согласно Эрик Ниблер , эта функция будетбыть доступным в C ++ 20 .Это можно использовать с заголовком <experimental/ranges/range>.Тогда выражение for будет выглядеть так:

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

См. DEMO 2

2 голосов
/ 03 апреля 2015

Если не использовать C ++ 14, то ниже я найду самое простое решение.

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

Демо .
Он не работает для контейнеров / типов данных (таких как массив), у которых нет функций begin/rbegin, end/rend.

0 голосов
/ 19 ноября 2018

Вы можете просто использовать BOOST_REVERSE_FOREACH, который повторяет в обратном направлении.Например, код

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

создает следующий вывод:

4

3

2

1

0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...