c++ 11基于反向范围的for循环

是否有一种容器适配器可以反转迭代器的方向,以便我可以使用基于范围的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)) { ...

有这样的东西吗,还是我必须自己写?

170440 次浏览

这对你有用吗:

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

实际上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';
}

这应该在没有boost的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))
{
...
}

实际上,在c++ 14中,只需几行代码就可以完成。

这与@Paul的解决方案非常相似。由于c++ 11缺少一些东西,这个解决方案有点不必要的臃肿(加上定义std的味道)。多亏了c++ 14,我们可以让它更具可读性。

关键的观察是,基于范围的for循环依赖begin()end()来获取范围的迭代器。多亏了诽谤联盟,人们甚至不需要在std::命名空间中定义他们的自定义begin()end()

下面是一个非常简单的示例解决方案:

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

如果不使用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}; }
< p > 演示。< br > 它不适用于容器/数据类型(如数组),因为容器/数据类型没有begin/rbegin, end/rend函数
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;
}

你可以简单地使用向后迭代的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

如果你可以使用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 < / >强

根据Eric Niebler,此特性将在C + + 20中可用。这可以与<experimental/ranges/range>标头一起使用。然后for语句将看起来像这样:

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

看到DEMO 2

cppreference中获取这个例子。它适用于:

GCC 10.1+带标志-std=c++20

#include <ranges>
#include <iostream>
 

int main()
{
static constexpr auto il = {3, 1, 4, 1, 5, 9};
 

std::ranges::reverse_view rv {il};
for (int i : rv)
std::cout << i << ' ';
 

std::cout << '\n';
 

for(int i : il | std::views::reverse)
std::cout << i << ' ';
}

抱歉,但是在当前的c++(除了c++ 20)中,所有这些解决方案似乎都不如仅仅使用基于索引的for。这里没有什么只是“几行代码”。所以,是的:通过一个简单的内循环进行迭代。这是最好的解决办法。