C++11 обратный диапазон на основе for-loop
есть ли адаптер контейнера, который изменил бы направление итераторов, чтобы я мог перебирать контейнер в обратном порядке с помощью цикла for-loop на основе диапазона?
с явными итераторами я бы преобразовал это:
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)) { ...
есть ли такая вещь или я должен написать ее сам?
6 ответов:
реально увеличить, действительно есть такие переходники:
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'; }
на самом деле, в C++14 это можно сделать с помощью очень нескольких строк кода.
это очень похоже по идее на решение @Paul. Из-за вещей, отсутствующих в C++11, это решение немного излишне раздуто (плюс определение в запахах std). Благодаря C++14 мы можем сделать его намного более читаемым.
ключевым наблюдением является то, что ранжированные циклы for-loops работают, полагаясь на
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
спасибо эстан для указания на это.
в оригинальной реализации примера ответа используется
using namespace std;, что приведет к тому, что любой файл, включая эту реализацию (которая должна быть в заголовочном файле), также импорт всегоstdпространство имен.пересмотрел пример реализации, чтобы предложить
using std::rbegin, std::rendвместо.
Это должно работать в C++11 без 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)) { ... }
работает ли это для вас:
#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; }
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; }
Если не использовать 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функции.
Comments