【问题标题】:C++11 reverse range-based for-loopC++11 反向基于范围的 for 循环
【发布时间】:2021-04-08 08:47:51
【问题描述】:

是否有容器适配器可以反转迭代器的方向,以便我可以使用基于范围的 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:“而不是?”为什么要摆脱基于迭代器的算法?对于不从begin 迭代到end 或处理流迭代器等的情况,它们要好得多,也不那么冗长。范围算法会很棒,但它们实际上只是迭代器算法的语法糖(除了延迟评估的可能性)。
  • @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
  • @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; 进行迭代。
  • @C.R:我不认为它应该是这个意思,因为这会使它无法作为一种简洁的语法用于顺序很重要的循环。 IMO 简洁性比您的语义含义更重要/更有用,但是如果您不重视 c 的简洁性,您的样式指南可以给出您想要的任何含义。这就是parallel_for 的用途,如果它以某种形式被纳入标准,那么“我不在乎什么顺序”的条件会更强。当然它也可以有一个基于范围的语法糖 :-)

标签: c++ c++11 ranged-loops


【解决方案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,
    

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

    【讨论】:

    • 几行代码?请原谅我,但那已经超过十个了 :-)
    • 实际上,它是 5-13,具体取决于您如何计算行数:) 解决方法不应该存在,因为它们是库的一部分。感谢您提醒我,顺便说一句,此答案需要针对最近的编译器版本进行更新,其中根本不需要所有额外的行。
    • 我认为你在 reverse 实现中忘记了 forward&lt;T&gt;
    • 嗯,如果你把它放在一个标题中,你就是 using namespace std 在一个标题中,这不是一个好主意。还是我错过了什么?
    • 实际上,你不应该写“使用 ;”在标头中的文件范围内。您可以通过将 using 声明移动到 begin() 和 end() 的函数范围来改进上述内容。
    【解决方案3】:

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

    【讨论】:

    • IIRC 将任何内容添加到命名空间 std 是对史诗般失败的邀请。
    • 我不确定“史诗般的失败”的规范含义,但在 std 命名空间中重载函数具有未定义的行为,每 17.6.4.2.1。
    • 它在C++14 apparently,在这个名字下。
    • @MuhammadAnnaqeeb 不幸的是,这样做会发生冲突。您不能同时使用这两种定义进行编译。另外,编译器不需要定义 not 出现在 C++11 下,并且只出现在 C++14 下(规范没有说明什么是 not 在 std:: 命名空间中,就是这样)。因此,在符合标准的 C++11 编译器下,这很可能是编译失败...比在 C++14 中 不是 的随机名称更有可能!正如所指出的,这是“未定义的行为”......所以编译失败并不是最糟糕的情况。
    • @HostileFork 没有名称冲突,make_reverse_iterator 不在std 命名空间中,因此不会与它的 C++14 版本冲突。
    【解决方案4】:

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

    DEMO 1

    注意:根据Eric Niebler,此功能将在C++20中可用。这可以与&lt;experimental/ranges/range&gt; 标头一起使用。那么for 语句将如下所示:

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

    DEMO 2

    【讨论】:

    • 更新:ranges::view 命名空间已重命名为 ranges::views。所以,使用ranges::views::reverse
    【解决方案5】:

    这个例子来自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 << ' ';
    }
    

    【讨论】:

      【解决方案6】:

      这对你有用吗:

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

        【讨论】:

        • 你能解释一下你的答案吗?
        • 这是一个基于范围的反向循环 C++11 类模板
        【解决方案8】:

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

        【讨论】:

        • 索引或简单的rbegin()/rend()
        【解决方案9】:

        如果不使用 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 函数的容器/数据类型(如数组)。

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-11-03
            • 1970-01-01
            • 2013-01-04
            • 1970-01-01
            相关资源
            最近更新 更多