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

Posted

技术标签:

【中文标题】C++11 反向基于范围的 for 循环【英文标题】:C++11 reverse range-based for-loop 【发布时间】:2011-12-17 04:29:25 【问题描述】:

是否有容器适配器可以反转迭代器的方向,以便我可以使用基于范围的 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 的用途,如果它以某种形式被纳入标准,那么“我不在乎什么顺序”的条件会更强。当然它也可以有一个基于范围的语法糖:-) 【参考方案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> arr1, 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】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

以上是关于C++11 反向基于范围的 for 循环的主要内容,如果未能解决你的问题,请参考以下文章

C++11 基于范围的for循环

在 Qt 中正确使用 C++11 基于范围的 for 循环

[C++11]基于范围的for循环

如何让 C++0x / C++11 风格的基于范围的 for 循环与 clang 一起使用?

C++11新特性:14—— C++11 for循环(基于范围的循环)详解

C++11之基于范围的for循环