Cycles in c++ like in python (range-based for)

2019-06-23 19:43发布

问题:

What is the easiest way to do cycles in c ++ like in python?

for i in range(10): #or range(4, 10, 2) etc
    foo(i)

I mean something simple and one-line like this

for(auto i: range(10)) //or range(4, 10, 2) or range(0.5, 1.0, 0.1) etc
    foo(i);

but not like this:

std::vector<int> v(10);
std::iota(begin(v), end(v), 0);
for(auto i: v) {
    foo(i);
}

Or this

for(auto i: []{vector<size_t> v(10); return iota(begin(v), end(v), 0), v;}() ) {
    foo(i);         
}

Of course, it is not difficult to use these examples or just for(;;) but I hope there is a way to do it briefly and succinctly in python.

回答1:

A Python-like range notion is not provided out-of-the-box, but you could roll your own Range class with a simple iterator, like this:

#include <iostream>

template <typename T>
class Range
{
public:
  class iterator
  {
  public:
    explicit iterator(T val, T stop, T step) : m_val(val), m_stop(stop), m_step(step) { }
    iterator& operator ++ ()
    {
      m_val += m_step;
      if ((m_step > 0 && m_val >= m_stop) ||
          (m_step < 0 && m_val <= m_stop))
      {
        m_val = m_stop;
      }
      return *this;
    }
    iterator operator ++ (int) { iterator retval = *this; ++(*this); return retval; }
    bool operator == (iterator other) const {return m_val == other.m_val;}
    bool operator != (iterator other) const {return !(*this == other);}
    T operator * () const { return m_val; }
  private:
    T m_val, m_stop, m_step;
  };

  explicit Range(T stop)
    : m_start(0), m_stop(stop), m_step(1)
  { }

  explicit Range(T start, T stop, T step = 1)
    : m_start(start), m_stop(stop), m_step(step)
  { }

  iterator begin() const { return iterator(m_start, m_stop, m_step); }
  iterator end() const { return iterator(m_stop, m_stop, m_step); }

private:
  T m_start, m_stop, m_step;
};

template <typename T>
Range<T> range(T stop) { return Range<T>(stop); }

template <typename T>
Range<T> range(T start, T stop, T step = 1) { return Range<T>(start, stop, step); }

int main()
{
  for (auto i : range(10)) { std::cout << " " << i; }
  std::cout << std::endl;
  for (auto i : range(4, 10, 2)) { std::cout << " " << i; }
  std::cout << std::endl;
  for (auto i : range(0.5, 1.0, 0.1)) { std::cout << " " << i; }
  std::cout << std::endl;
}

In order to support range-based for, an iterator type and begin()/end() functions will do the job. (Of course my implementation above is quick and dirty, and could probably be improved.)

You will not get around rolling your own class like that, but once you have it, the usage is very much akin to the Python approach:

for (auto i : range(stop)) { ... }
for (auto i : range(start, stop, step)) { ... }

The example outputs (see live version here):

$ g++ -std=c++11 -o test test.cpp && ./test
 0 1 2 3 4 5 6 7 8 9
 4 6 8
 0.5 0.6 0.7 0.8 0.9 1

If you only need integer ranges, you can also use boost::irange (thanks to Yakk for the reminder).