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


是否有这样的事情,或者我必须自己写吗?

评论

反向容器适配器听起来很有趣,但是我认为您必须自己编写它。如果标准委员会匆忙改编基于范围的算法而不是显式迭代器,那么我们就不会有这个问题。

@deft_code:“而不是?”为什么要摆脱基于迭代器的算法?在您不从头到尾进行迭代的情况下,或者在处理流迭代器之类的情况下,它们要好得多,而冗长得多。范围算法会很棒,但实际上,它只是迭代器算法上的语法糖(除了延迟评估的可能性以外)。

@deft_code template 类reverse_adapter {public:reverse_adapter(T&c):c(c){} typename T :: reverse_iterator begin(){return c.rbegin(); }类型名称T :: reverse_iterator end(){return c.rend(); }私人:T&c; };可以进行改进(添加const版本等),但是它可以工作:vector v {1,2,3}; reverse_adapter ra; for(auto&i:ra)cout << i;打印321

@SethCarnegie:并添加一个很好的功能形式:template reverse_adapter reverse_adapt_container(T&c){return reverse_adapter (c);}因此,您可以使用for(auto&i:reverse_adapt_container(v ))cout << i;进行迭代。

@ C.R:我不认为这是应该的,因为那样会使它在顺序很重要的循环中无法作为简洁的语法使用。 IMO的简洁性比您的语义更重要/有用,但是如果您不重视简洁性,则样式指南可以根据需要提供任何含义。如果以某种形式将其并入标准中,则这就是parallel_for的用途,它具有更强的“我不在乎什么顺序”条件。当然,它也可以具有基于范围的语法糖:-)

#1 楼

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


#2 楼

实际上,在C ++ 14中,只需几行代码即可完成。

这与@Paul的解决方案非常相似。由于C ++ 11中缺少某些内容,因此该解决方案有点不必要地过大(加上在std气味中定义)。感谢C ++ 14,我们可以使它更具可读性。

主要观察到的是,基于范围的for循环通过依赖begin()end()来获取范围的迭代器而工作。多亏了ADL,甚至不需要在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,


注意GCC-4.9中尚未实现std::rbegin()std::rend()std::make_reverse_iterator()。我根据标准编写了这些示例,但它们无法在稳定的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());
}


评论


几行代码?原谅我,但已超过十个:-)

–珍妮
16-4-27的1:54

实际上,它是5到13,具体取决于您如何计算行数:)解决方法不应该存在,因为它们是库的一部分。谢谢您提醒我,顺便说一句,这个答案需要针对最新的编译器版本进行更新,因为根本不需要所有多余的行。

– Prikso NAI
16-4-27的12:37

我认为您在反向实现中忘记了

–蛇
16-11-25在12:11

嗯,如果将其放在标题中,则说明您在标题中使用名称空间std,这不是一个好主意。还是我错过了什么?

– Estan
17-10-21在9:46

实际上,您不应该编写“使用;”。在标头中的文件范围内。您可以通过将using声明移到begin()和end()的函数范围中来改进上述内容。

–克里斯·哈特曼(Chris Hartman)
18年4月22日在17:56

#3 楼

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


评论


IIRC向名称空间std添加任何内容是史诗般失败的邀请。

–BCS
2013年9月18日在1:33

我不确定“ epic fail”的规范含义,但是根据17.6.4.2.1,重载std名称空间中的函数具有未定义的行为。

–卡西
2014年6月6日15:34

这个名字显然是在C ++ 14中。

– HostileFork说不信任SE
2014年12月14日23:42

@MuhammadAnnaqeeb不幸的是,这样做确实发生了冲突。您不能同时使用这两个定义进行编译。另外,编译器不需要在C ++ 11下不存在定义,而仅在C ++ 14下出现(规范没有说明什么不是std ::名称空间中的内容,而是什么)。因此,在符合标准的C ++ 11编译器下,这很可能会导致编译失败……比它是C ++ 14中没有的某个随机名称更有可能!并指出,这是“未定义的行为” ...因此,不进行编译并不是它可能要做的最坏的事情。

– HostileFork说不信任SE
2015年2月28日在4:26



@HostileFork没有名称冲突,make_reverse_iterator不在std名称空间中,因此它不会与C ++ 14版本冲突。

– Paul Fultz II
15年7月29日在21:04

#4 楼

对您有用吗?

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


#5 楼

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


评论


能否请您详细说明答案?

– Mostafiz
16-4-29的3:27

这是一个基于范围的反向循环C ++ 11类模板

–刘汗
16年5月13日在9:01



#6 楼

如果可以使用范围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;
}


请参阅DEMO1。

注意:根据Eric Niebler的要求,此功能将在C ++ 20中可用。可以与<experimental/ranges/range>标头一起使用。然后,for语句将如下所示:

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


请参见DEMO 2

评论


更新:range :: view命名空间已重命名为range :: views。因此,请使用range :: views :: reverse。

–nac001
19/12/3在20:58

#7 楼

如果不使用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函数。

#8 楼

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