C ++ 11 обратный цикл на основе диапазона for

321

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

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

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

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

Алекс Б
источник
17
Адаптер обратного контейнера, звучит интересно, но я думаю, вам придется написать это самостоятельно. У нас не было бы этой проблемы, если бы Комитет по стандартизации поторопился бы и адаптировал алгоритмы на основе диапазона вместо явных итераторов.
deft_code
4
@deft_code: "вместо?" Почему вы хотите избавиться от алгоритмов на основе итераторов? Они намного лучше и менее многословны для случаев, когда вы не выполняете итерацию с beginпо endили для работы с потоковыми итераторами и т.п. Алгоритмы диапазона были бы хороши, но они действительно просто синтаксический сахар (за исключением возможности ленивой оценки) по сравнению с алгоритмами итератора.
Николь Болас
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; };Это может быть улучшено (добавление constверсий и т. д.), но это работает: vector<int> v {1, 2, 3}; reverse_adapter<decltype(v)> ra; for (auto& i : ra) cout << i;печатает321
Сет Карнеги
10
@SethCarnegie: И добавить хорошую функциональную форму: template<typename T> reverse_adapter<T> reverse_adapt_container(T &c) {return reverse_adapter<T>(c);}так что тогда вы можете просто использовать for(auto &i: reverse_adapt_container(v)) cout << i;для итерации.
Николь Болас
2
@CR: Я не думаю, что это должно означать это, потому что это сделало бы его недоступным в качестве краткого синтаксиса для циклов, где порядок имеет значение. IMO краткость важнее / полезнее, чем ваше семантическое значение, но если вы не цените краткость c, ваше руководство по стилю может дать ему любой смысл, который вы хотите. Это что-то вроде того parallel_for, с еще более сильным условием «мне все равно, какой порядок», если бы оно было включено в стандарт в той или иной форме. Конечно, он также может иметь синтаксический сахар на основе диапазона :-)
Стив Джессоп

Ответы:

230

На самом деле подталкивания имеет такой адаптер: 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
источник
91

На самом деле, в 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());
}
Приксо НАИ
источник
35
Несколько строк кода? Прости меня, но это больше десяти :-)
Джонни
4
На самом деле, это 5-13, в зависимости от того, как вы считаете строки:) Обходных путей там быть не должно, так как они являются частью библиотеки. Спасибо, что напомнили мне, кстати, этот ответ необходимо обновить для последних версий компилятора, где все лишние строки не нужны вообще.
Приксо NAI
2
Я думаю, что вы забыли forward<T>в своей reverseреализации.
SnakE
3
Хм, если вы помещаете это в заголовок, вы находитесь using namespace stdв заголовке, что не очень хорошая идея. Или я что-то упустил?
17
3
На самом деле, вы не должны писать «используя <что-нибудь>;» в области видимости файла в заголовке. Вы можете улучшить вышесказанное, переместив объявления использования в область действия функций begin () и end ().
Крис Хартман,
23

Это должно работать в 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))
{
    ...
}
Пол Фульц II
источник
58
Добавление IIRC чего-либо в пространство имен std - это приглашение к эпическому провалу.
БКС,
35
Я не уверен в нормативном значении «эпического сбоя», но перегрузка функции в stdпространстве имен имеет неопределенное поведение в соответствии с 17.6.4.2.1.
Кейси
9
Это в C ++ 14, видимо , под этим именем.
HostileFork говорит, что не доверяйте SE
6
@MuhammadAnnaqeeb К несчастью, при этом происходит точное совпадение. Вы не можете скомпилировать оба определения. Кроме того, компилятору не требуется, чтобы определение отсутствовало в C ++ 11 и появлялось только в C ++ 14 (спецификация ничего не говорит о том, чего нет в пространстве имен std ::, только о том, что есть). Так что это будет очень вероятный сбой компиляции в соответствии со стандартом компилятора C ++ 11 ... гораздо более вероятно, чем если бы это было какое-то случайное имя, которого не было в C ++ 14! И, как указывалось, это «неопределенное поведение» ... поэтому отсутствие компиляции - не самое страшное, что может быть.
HostileFork говорит, что не доверяйте SE
2
@HostileFork Нет конфликта имен, оно make_reverse_iteratorотсутствует в stdпространстве имен, поэтому оно не будет конфликтовать с его версией для C ++ 14.
Пол Фульц II
11

Это работает для вас:

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

например:

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
Можете ли вы объяснить более подробно ваш ответ?
Мостафиз
это обратный диапазон базового цикла C ++ 11 класса тамплат
Хан Лау
4

Если вы можете использовать диапазон 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 << " ";   
}

Смотрите ДЕМО 2

PW
источник
Обновление: ranges::viewпространство имен было переименовано в ranges::views. Итак, пользуйтесь ranges::views::reverse.
nac001
2

Если не использовать 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}; }

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

iammilind
источник
0

Вы можете просто использовать, 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
Catriel
источник