C++ 11基于反向范围的for-loop

Ale*_*x B 309 c++ c++11 ranged-loops

是否有一个容器适配器可以反转迭代器的方向,所以我可以反向迭代一个容器,基于范围的for循环?

使用显式迭代器,我会将其转换为:

for (auto i = c.begin(); i != c.end(); ++i) { ...
Run Code Online (Sandbox Code Playgroud)

进入这个:

for (auto i = c.rbegin(); i != c.rend(); ++i) { ...
Run Code Online (Sandbox Code Playgroud)

我想转换这个:

for (auto& i: c) { ...
Run Code Online (Sandbox Code Playgroud)

对此:

for (auto& i: std::magic_reverse_adapter(c)) { ...
Run Code Online (Sandbox Code Playgroud)

有这样的事情还是我必须自己写?

ken*_*ytm 221

实际上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';
}
Run Code Online (Sandbox Code Playgroud)

  • 我只是厌倦了看到答案以这样或那样的方式提升。 (24认同)
  • 即使您不喜欢到处看到增长,对于 2011 年来说,这也是一个非常合适的答案。 (3认同)

Pri*_*NAI 82

实际上,在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 }; }
Run Code Online (Sandbox Code Playgroud)

这就像一个魅力,例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

按预期打印

4,3,2,1,
4,3,2,1,
3,2,1,0,
0,1,2,3,
Run Code Online (Sandbox Code Playgroud)

注意 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());
}
Run Code Online (Sandbox Code Playgroud)

2017年10月22日更新

感谢estan指出这一点.

原始答案示例实现使用begin(),这将导致包括此实现(必须在头文件中)的任何文件也导入整个end()命名空间.

修改了示例实现以提出建议begin().

  • 几行代码?请原谅我,但那是十多岁:-) (31认同)
  • 实际上,它是5-13,具体取决于您如何对行进行计数:)解决方法不应该存在,因为它们是库的一部分。谢谢您提醒我,顺便说一句,此答案需要针对最新的编译器版本进行更新,因为根本不需要所有多余的行。 (3认同)
  • 嗯,如果将其放在标题中,则说明您在标题中使用了名称空间std,这不是一个好主意。还是我错过了什么? (3认同)
  • 实际上,你不应该写"使用<anything>;" 在标题中的文件范围.您可以通过将using声明移动到begin()和end()的函数作用域来改进上述内容. (3认同)
  • 我想你在`reverse`实现中忘了`forward <T>`. (2认同)
  • @ReDucTor可以通过将reversion_wrapper更改为“T”成员然后在“reverse”中使用“std::forward”来修复。然后右值将通过值捕获,左值将通过引用捕获。 (2认同)

Pau*_* II 22

这应该在没有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))
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • IIRC向命名空间std添加任何内容是对史诗失败的邀请. (58认同)
  • 我不确定"史诗失败"的规范含义,但是在`std`命名空间中重载函数的每个17.6.4.2.1都有未定义的行为. (34认同)
  • 它以[C++ 14显然](http://en.cppreference.com/w/cpp/iterator/make_reverse_iterator)的名字命名. (9认同)
  • @MuhammadAnnaqeeb不幸的是,这样做完全碰撞了.您无法使用这两个定义进行编译.另外,编译器不需要在C++ 11下定义*not*,并且只出现在C++ 14下(规范没有说明std :: namespace中的*not*,只是什么是).因此,在符合标准的C++ 11编译器下,这很可能是编译失败...比在C++ 14中*不是*的随机名称更有可能!正如所指出的那样,它是"未定义的行为"......所以未能编译并不是它可能做的最糟糕的事情. (6认同)
  • @HostileFork没有名称冲突,`make_reverse_iterator`不在`std`命名空间中,因此它不会与C++ 14版本冲突. (2认同)

P.W*_*P.W 14

如果您可以使用range 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;
}
Run Code Online (Sandbox Code Playgroud)

演示 1

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

for (auto const& e : view::reverse(intVec)) {
       std::cout << e << " ";   
}
Run Code Online (Sandbox Code Playgroud)

演示 2

  • 更新:“ranges::view”命名空间已重命名为“ranges::views”。因此,使用“ranges::views::reverse”。 (3认同)

Arl*_*len 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;
}
Run Code Online (Sandbox Code Playgroud)


Sor*_*ush 11

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 << ' ';
}
Run Code Online (Sandbox Code Playgroud)


Ice*_*ire 11

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


小智 6

    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);
    }
Run Code Online (Sandbox Code Playgroud)

例如:

    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;
    }
Run Code Online (Sandbox Code Playgroud)