C ++ 11 зворотний діапазон на основі циклу


321

Чи є адаптер контейнера, який би змінював напрямок ітераторів, щоб я міг перебирати контейнер у зворотному напрямку з циклом для циклу?

З явними ітераторами я би перетворив це:

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
Seth Carnegie

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 лаконічність важливіша / корисніша за ваше семантичне значення, але якщо ви не цінуєте стислість ofc, ваш посібник зі стилів може надати йому будь-які наслідки, які ви хочете. Це таке, що 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';
}

90

Насправді в C ++ 14 це можна зробити за допомогою дуже небагато рядків коду.

Це за ідеєю дуже схоже на рішення @ Пола. Через те, що не вистачає на C ++ 11, це рішення трохи надмірно роздувається (плюс визначається в std запахах). Завдяки C ++ 14 ми можемо зробити його набагато читабельнішим.

Ключове спостереження полягає в тому , що діапазон на основі для-петель робота, спираючись на 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, залежно від того, як ви рахуєте рядки:) Робочих навколо не повинно бути там, оскільки вони є частиною бібліотеки. Дякую, що нагадали, btw, цю відповідь потрібно оновити для останніх версій компілятора, де всі зайві рядки взагалі не потрібні.
Prikso NAI

2
Я думаю, ви забули forward<T>у своїй reverseреалізації.
SnakE

3
Гм, якщо ви помістите це в заголовок, ви using namespace stdв заголовку, що не є хорошою ідеєю. Або я щось пропускаю?
estan

3
Насправді, ви не повинні писати "за допомогою <що-небудь>;" в області файлу в заголовку. Ви можете покращити вищесказане, перемістивши використовувані декларації в область функцій для початку () та кінця ().
Кріс Хартман

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

58
IIRC додавання чого-небудь до простору імен std - це запрошення до epic fail.
BCS

35
Я не впевнений у нормативному значенні "епічного провалу", але перевантаження функції в stdпросторі імен має невизначене поведінку на 17.6.4.2.1.
Кейсі


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
Ви можете пояснити, будь ласка, детальніше вашої відповіді?
Мостафіз

це зворотний контур на основі діапазону С ++ 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;
}

Див. DEMO 1 .

Примітка. Відповідно до Еріка Ніблера , ця функція буде доступна на C ++ 20 . Це можна використовувати із <experimental/ranges/range>заголовком. Тоді forтвердження буде виглядати приблизно так:

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

Див. DEMO 2


Оновлення: 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функцій.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.