C++1Y equivalent of python 'range'?

458 views
Skip to first unread message

Matthew Woehlke

unread,
Feb 12, 2014, 6:44:59 PM2/12/14
to std-dis...@isocpp.org
Is there a C++1Y equivalent of this python snippet:

for x in range(y):

...? I'm thinking something like:

for (auto x : ???(y))

What is the expression to use in the above? Is it part of STL, and/or
has anyone made such a proposal?

--
Matthew

David Krauss

unread,
Feb 12, 2014, 7:36:32 PM2/12/14
to std-dis...@isocpp.org
The Python range() constructs a list container without lazy semantics. (I just checked by constructing a huge range and doing nothing with it.) So the C++ equivalent would be to define a function returning a std::vector<std::size_t> . Not terribly interesting.

I had thought that Python list comprehensions allowed working with a generator to avoid putting the whole range in memory first, but apparently you need to use coroutines and yield to do it. I could be wrong though.

To get a C++ range-for to loop with a generator, just define a class whose begin returns an InputIterator interface to the desired generator, and end can peek inside the generator to evaluate the termination condition.

Iterator adapters for generators aren’t part of any proposal I know, but I’m not following this stuff. As of the current working draft, there’s only std::generate(ForwardIterator first, ForwardIterator last, Generator gen) which fills a range. You can probably find something in Boost, shouldn’t be too hard to roll your own.

Richard Smith

unread,
Feb 12, 2014, 8:30:20 PM2/12/14
to std-dis...@isocpp.org
On Wed, Feb 12, 2014 at 4:36 PM, David Krauss <pot...@gmail.com> wrote:
The Python range() constructs a list container without lazy semantics. (I just checked by constructing a huge range and doing nothing with it.) So the C++ equivalent would be to define a function returning a std::vector<std::size_t> . Not terribly interesting.

I had thought that Python list comprehensions allowed working with a generator to avoid putting the whole range in memory first, but apparently you need to use coroutines and yield to do it. I could be wrong though.

xrange(...) does that. range(...) does the same in Python 3 onwards.
 
To get a C++ range-for to loop with a generator, just define a class whose begin returns an InputIterator interface to the desired generator, and end can peek inside the generator to evaluate the termination condition.

Iterator adapters for generators aren’t part of any proposal I know, but I’m not following this stuff. As of the current working draft, there’s only std::generate(ForwardIterator first, ForwardIterator last, Generator gen) which fills a range. You can probably find something in Boost, shouldn’t be too hard to roll your own.

On Feb 13, 2014, at 7:44 AM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:

Is there a C++1Y equivalent of this python snippet:

 for x in range(y):

...? I'm thinking something like:

 for (auto x : ???(y))

What is the expression to use in the above? Is it part of STL, and/or has anyone made such a proposal?

--
 

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Matthew Woehlke

unread,
Feb 12, 2014, 8:43:11 PM2/12/14
to std-dis...@isocpp.org
On 2014-02-12 19:36, David Krauss wrote:
> On Feb 13, 2014, at 7:44 AM, Matthew Woehlke wrote:
>> Is there a C++1Y equivalent of this python snippet:
>>
>> for x in range(y):
>>
>> ...? I'm thinking something like:
>>
>> for (auto x : ???(y))
>>
>> What is the expression to use in the above? Is it part of STL,
>> and/or has anyone made such a proposal?
>
> The Python range() constructs a list container without lazy
> semantics. So the C++ equivalent would be to define a function
> returning a std::vector<std::size_t> . Not terribly interesting.

Well, sure, but that wasn't really the point (if it helps, pretend I
wrote 'xrange' instead). I expect a C++ implementation would return
something like a magic iterator, such that the total storage is
~3*sizeof(T) where T is (in the previous example) decltype(y).

> I had thought that Python list comprehensions allowed working with a
> generator to avoid putting the whole range in memory first

(OT: xrange does that.)

> To get a C++ range-for to loop with a generator, just define a class
> whose begin returns an InputIterator interface to the desired
> generator, and end can peek inside the generator to evaluate the
> termination condition.

That's roughly what I ended up doing. The thing is, this:

for (T i = 0, end = func(); i < end; ++i)

...is so insanely common (probably *the* most common for loop), and also
not very easy to write with AAA¹:

auto const end = func();
for (auto i = decltype<end>{0}; i < end; ++i)

...that, even though I can (and did) create my own version easily
enough, it feels like the standard library really ought to offer a way
to write it Python-style using a C++11 range-based for loop:

for (auto const i : index_range(func()))

(See how much prettier and easy to write that is?)

Aside: Apparently template type deduction doesn't allow index_range to
be a class. I had to make it a free function returning an index_range_t.
(Since of course having to specify the type defeats the purpose.)

Note that the reason I got started on this was because I'd seen one too
many places where someone wrote:

for (int i = 0, end = func() /* etc... */)

...where the return type of func() is e.g. size_t / long long /
something else that shouldn't be coerced into 'int'. (Which is also a
great reason to use AAA... except that it's overly awkward in this
instance.)


http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/)

--
Matthew

Ahmed Charles

unread,
Feb 12, 2014, 8:58:09 PM2/12/14
to std-dis...@isocpp.org


Boost allows you to do:

for (auto i : boost::counting_range(0, 10)) std::cout << i << "\n";

which prints 1 through 9.

Unfortunately, it doesn't have a special case where the beginning is defaulted to 1, but then again, you can just write:

template <class T>
auto xrange(T t) { return counting_range(T(), t); }

--
Ahmed Charles

David Krauss

unread,
Feb 12, 2014, 9:33:38 PM2/12/14
to std-dis...@isocpp.org

On Feb 13, 2014, at 9:43 AM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:

(OT: xrange does that.)

Sorry, I don’t really know Python.

That's roughly what I ended up doing. The thing is, this:

 for (T i = 0, end = func(); i < end; ++i)

...is so insanely common (probably *the* most common for loop), and also not very easy to write with AAA¹:

 auto const end = func();
 for (auto i = decltype<end>{0}; i < end; ++i)

Still not particularly elegant, but this also works:

  for ( auto end = func(), i = decltype(end){ 0 }; i != end; ++ i )

This relies on a bit of a loophole in [dcl.spec.auto] §7.1.6.4/8, because it says "the type of each declared variable is determined,” which as opposed to referring only to the types of the initializers, suggests that the name end is already associated with a type after the syntactic end of its initializer. Anyway, Clang and GCC accept it.

It would be nice to see common_type/?: semantics applied to auto deduction, and I’m sure it’s been carefully considered but rejected. :(

...that, even though I can (and did) create my own version easily enough, it feels like the standard library really ought to offer a way to write it Python-style using a C++11 range-based for loop:

 for (auto const i : index_range(func()))

(See how much prettier and easy to write that is?)

Aside: Apparently template type deduction doesn't allow index_range to be a class. I had to make it a free function returning an index_range_t. (Since of course having to specify the type defeats the purpose.)

Yep, all sounds about right.

Note that the reason I got started on this was because I'd seen one too many places where someone wrote:

 for (int i = 0, end = func() /* etc... */)

...where the return type of func() is e.g. size_t / long long / something else that shouldn't be coerced into 'int'. (Which is also a great reason to use AAA... except that it's overly awkward in this instance.)

Another style that eliminates the risk is brace-initialization:

 for (int i { 0 }, end { func() } /* error: narrowing conversion */, i != end; ++ i )

The proliferation of initialization syntaxes is itself a big problem though, and GCC only diagnoses this as a warning by default. (Clang does not follow suit.)

Personally I tend to default to size_t for loop indexes since int seldom provides any benefits, and in practice it’s big enough, and negative numbers should be prevented before the loop in any case. Still, in principle you’re right.

Bjorn Reese

unread,
Feb 13, 2014, 5:19:02 AM2/13/14
to std-dis...@isocpp.org
On 02/13/2014 02:58 AM, Ahmed Charles wrote:

> Boost allows you to do:
>
> for (auto i : boost::counting_range(0, 10)) std::cout << i << "\n";

While this is correct, boost::irange(0, 10) is probably closer to the
Python xrange() because you can also specify the step size.

Matthew Woehlke

unread,
Feb 13, 2014, 12:40:50 PM2/13/14
to std-dis...@isocpp.org
On 2014-02-12 21:33, David Krauss wrote:
> Personally I tend to default to size_t for loop indexes since int
> seldom provides any benefits,

...unless you're passing it to a function that expects 'int' :-). Or
anything else signed.

Mixing different libraries' containers (e.g. Qt, VTK, STL) is where you
run into trouble. The index type of the above is int, size_t and
vtkIdType (e.g. long long), respectively, none of which are
interchangeable when you start passing them to other methods that expect
that library's index type.

(And AAA which will silently take care of these differences, even if you
later switch out container types or the library's index type changes.)

--
Matthew

David Krauss

unread,
Feb 13, 2014, 5:26:59 PM2/13/14
to std-dis...@isocpp.org
Ah, these are close. They would be drop-in solutions except that they deduce the Integer or Incrementable type parameter from both the first and last arguments, requiring a typecast akin to the raw for loop.

So you still need a wrapper to either hide first from deduction, or pass a hard-coded zero with an encapsulated cast.

By the way, this StackOverflow answer clarifies that the result from irange is only bidirectional, but you specify the step, whereas counting_range provides a random-access iterator.

Reply all
Reply to author
Forward
0 new messages