5

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
  • 13
  • 116
  • 190
NikBond
  • 743
  • 1
  • 5
  • 20
  • 2
    I don't think there's any shorthand in C++ for this. Just use the traditional `for (i = 0; i < 10; i++)` – Barmar Nov 01 '16 at 21:32
  • _@NikBond_ I hope the solution from the duplicate is simple enough for you. Was just a 10 secs google query. – πάντα ῥεῖ Nov 01 '16 at 21:33
  • @Barmar You learn something new every day ;) – πάντα ῥεῖ Nov 01 '16 at 21:35
  • @πάνταῥεῖ: That answer doesn't seem applicable to this task; it'd require you to explicitly write out `{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}` to loop over it. – user2357112 Nov 01 '16 at 21:39
  • @πάνταῥεῖ my question is not about initializer_list, so i dont think that this is duplicate. – NikBond Nov 01 '16 at 21:40
  • @NikBond What's the purpose of your comments like `#or range(4, 10, 2)` and `//or range(4, 10, 2) or range(0.5, 1.0, 0.1) etc` then? – πάντα ῥεῖ Nov 01 '16 at 21:43
  • @πάνταῥεῖ: The arguments are start, stop (exclusive), and step. For example, `range(4, 10, 2)` is a lazy sequence whose elements are 4, 6, and 8, stopping before 10. – user2357112 Nov 01 '16 at 21:45
  • @user2357112 OK, I see. I'm not well versed enough with python. – πάντα ῥεῖ Nov 01 '16 at 21:46
  • 1
    A quick search suggests [`boost::irange`](http://www.boost.org/doc/libs/1_62_0/libs/range/doc/html/range/reference/ranges/irange.html), also mentioned in the second answer to [this question](http://stackoverflow.com/questions/1977339/c-range-xrange-equivalent-in-stl-or-boost). – user2357112 Nov 01 '16 at 21:48
  • @πάνταῥεῖ, i mean this https://docs.python.org/3/library/functions.html#func-range python function and it does not seem to initializer_list. Thanks. – NikBond Nov 01 '16 at 21:50

1 Answers1

2

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).

mindriot
  • 5,413
  • 1
  • 25
  • 34
  • The floating point one is a bad idea, unless you used a sentinal with `>=`. Oh wait you stored m_stop in the iterator! – Yakk - Adam Nevraumont Nov 01 '16 at 22:50
  • You can get an efficiency boost by templating `m_step` separately, permitting stateless increment-by-one cases. I am leery about `m_stop`; I get its use for floats and steps, but ... – Yakk - Adam Nevraumont Nov 01 '16 at 22:55
  • Mentioning `boost;:irange` would improve this answer. – Yakk - Adam Nevraumont Nov 01 '16 at 23:42
  • @Yakk I agree about the floating-point case being at least dangerous – the example actually shows it: 1.0 is only printed because of floating-point rounding issues. I included floats on purpose, though, because Python offers floating-point ranges. – mindriot Nov 02 '16 at 09:17
  • Thanks for the `irange` hint, I edited the answer. I agree that `m_stop` (and `m_step`) can be avoided in the special case of integer step-1 ranges, but I shall leave template specialization for such optimizations as an exercise to the reader. ;-) – mindriot Nov 02 '16 at 09:21