Extend the range-based for statement

170 views
Skip to first unread message

Antonio Perez

unread,
Aug 7, 2016, 4:49:20 PM8/7/16
to ISO C++ Standard - Future Proposals
Currently, the range-based for statement, defined in section 6.5.4 of the C++ international Standard, is defined as follows:

 The range-based for statement
for ( for-range-declaration : for-range-initializer ) statement
is equivalent to
{
   
auto &&__range = for-range-initializer ;
   
auto __begin = begin-expr ;
   
auto __end = end-expr ;
   
for ( ; __begin != __end; ++__begin ) {
       
for-range-declaration = *__begin;
        statement
   
}
}

I propose that the range-based for statement is expanded to allow iteration through multiple for-range-initializes simultaneously, with
for ( for-range-declaration : for-range-initializer; for-range-declaration2 : for-range-initializer2 /*; ...*/) statement
being equivalent to:
{
   
auto &&__range = for-range-initializer ;
   
auto &&__range2 = for-range-initializer2 ;
   
auto __begin = begin-expr ;
   
auto __begin2 = begin-expr2;
   
auto __end = end-expr ;
   
auto __end2 = end-expr2 ;
   
for ( ; __begin != __end && __begin2 != __end2; ++__begin, ++__begin2 ) {
       
for-range-declaration = *__begin;
       
for-range-declaration2 = *__begin2;
        statement
   
}
}

This change would make the range-based for statement more useful and more versatile 

D. B.

unread,
Aug 7, 2016, 5:12:14 PM8/7/16
to std-pr...@isocpp.org
Simultaneously? As in, each range must have identical size, and they are at the same index for each iteration?

Or did you really mean sequentially, i.e. it would basically stitch the ranges together then iterate the result?

Either way, can you show an example of code that currently you feel is overly hard to express without this ability?

D. B.

unread,
Aug 7, 2016, 5:16:56 PM8/7/16
to std-pr...@isocpp.org
...sorry, I didn't read closely enough the first time. So you definitely mean simultaneously.

I'm not sure this fits the intended model of range-for, as it would really require all ranges to have identical numbers of elements, whereas the point of range-for is that you don't care about how many elements you have or which index you're currently at - you just want to do something for them all.

If the situation you want this for is e.g. copying from source to destination pointers, then I think that idiom is so well-established that it wouldn't suffice as a justification for this, and if anything, it might look more obfuscated with this sort of range-for.

But I might be wrong...

Daker Fernandes Pinheiro

unread,
Aug 7, 2016, 5:27:06 PM8/7/16
to std-pr...@isocpp.org
It is a kind of python's:

for a, b in zip(iterA, iterB):
    ....

It stop iteration when the first iterator stop.


--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CACGiwhEBPCJYM4KoHx3hg8bfBmqEzX29%2BpWn6%3D1W3Sq2Sf0bZA%40mail.gmail.com.



--
Daker Fernandes Pinheiro
http://codecereal.blogspot.com

D. B.

unread,
Aug 7, 2016, 5:34:03 PM8/7/16
to std-pr...@isocpp.org
Cool, well if there's a pre-existing model that's widely used, then it might be a lot more likely to succeed than I thought. :-) I haven't gotten around to learning Python... yet.

Ville Voutilainen

unread,
Aug 7, 2016, 5:35:01 PM8/7/16
to ISO C++ Standard - Future Proposals
On 7 August 2016 at 23:49, Antonio Perez <ant...@perezexcelsior.com> wrote:
> Currently, the range-based for statement, defined in section 6.5.4 of the
> C++ international Standard, is defined as follows:
>
> The range-based for statement
> for ( for-range-declaration : for-range-initializer ) statement
> is equivalent to
> {
> auto &&__range = for-range-initializer ;
> auto __begin = begin-expr ;
> auto __end = end-expr ;
> for ( ; __begin != __end; ++__begin ) {
> for-range-declaration = *__begin;
> statement
> }
> }
>
> I propose that the range-based for statement is expanded to allow iteration
> through multiple for-range-initializes simultaneously, with
> for ( for-range-declaration : for-range-initializer; for-range-declaration2
> : for-range-initializer2 /*; ...*/) statement


This is https://cplusplus.github.io/EWG/ewg-active.html#43
and there's also
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0026r0.pdf
which was reviewed in Jacksonville but put on hold for now, pending
further investigations
on whether a library approach is in all aspects superior.

Antonio Perez

unread,
Aug 7, 2016, 5:42:27 PM8/7/16
to ISO C++ Standard - Future Proposals
Yes! That is what I'm proposing. Example code you could write with this:

template<int n> array<double, n> add(array<double, n>& x1, array<double, n>& x2) {
array<double, n> sum;
for (double& s : sum; double v1 : x1; double v2 : x2)
{
s = v1 + v2;
}
return sum;
}
template<int n> double dot(array<double, n>& x1, array<double, n> x2) {
double sum = 0.0;
for (double v1 : x1; double v2 : x2)
{
sum += v1 * v2;
}
return sum;
}
template<int rows, int cols> array<double, rows> mul(const array<array<double, cols>, rows>& matrix, const array<double, cols>& vector) {
array<double, rows> result;
for (const auto& row : matrix; double& sum : result) 
{
sum = 0;
for (double& x1 : row; double& x2 : vector) 
{
sum += x1 * x2;
}
}
return result;
}

Nicol Bolas

unread,
Aug 7, 2016, 5:57:25 PM8/7/16
to ISO C++ Standard - Future Proposals

With structured binding, it'd be somewhat difficult to justify why a `std::zip`-style solution would be incapable of being equally as effective as a language one. Indeed, I'd much rather see this:

for(auto &[x, y] : std::zip(rng1, rng2))

Than this:

for(auto &x : rng1 ; auto &y : rng2)


Izzy Coding

unread,
Aug 8, 2016, 1:14:05 AM8/8/16
to ISO C++ Standard - Future Proposals
It was my understanding tha P0026 was refused due to another proposal allowing the mapping of parts within the initializer.

E.g.
for (auto [a, b]: {list1, list2}) { ... }

Personally I don't like the idea of this. Feels dirty to me that we need to combine the different entities together just so we can iterate them at the same time. It almost seems like mixing concerns.

Also could a for loop as described in P0026 not be use to implement the zip method internals? It would be interesting to see how that is implemented compared with this proposal.

Nicol Bolas

unread,
Aug 8, 2016, 11:29:43 AM8/8/16
to ISO C++ Standard - Future Proposals
On Monday, August 8, 2016 at 1:14:05 AM UTC-4, Izzy Coding wrote:
It was my understanding tha P0026 was refused due to another proposal allowing the mapping of parts within the initializer.

E.g.
for (auto [a, b]: {list1, list2}) { ... }

Personally I don't like the idea of this. Feels dirty to me that we need to combine the different entities together just so we can iterate them at the same time. It almost seems like mixing concerns.


But you are mixing concerns. You're iterating through multiple ranges simultaneously. The loop will end when one of the ranges ends. The behavior of the loop is based on the aggregation of the ranges.

So actually aggregating them seems entirely appropriate.

With a single range-for, you have "Variable : Range". With multi-range-for, you have "Variables : Ranges". Having it be "Variable: Range; Variable: Range" feels really strange. Especially since we have structured binding and thus have a way to declare "Variables".

So even as a language feature, I'd much rather have what you showed above (though the braced-init-list is potentially problematic) than the semicolon-based syntax.
 

Also could a for loop as described in P0026 not be use to implement the zip method internals?


How could a multi-range `for` be used to implement a zip iterator/range?

Ville Voutilainen

unread,
Aug 8, 2016, 11:41:33 AM8/8/16
to ISO C++ Standard - Future Proposals
On 8 August 2016 at 18:29, Nicol Bolas <jmck...@gmail.com> wrote:
> On Monday, August 8, 2016 at 1:14:05 AM UTC-4, Izzy Coding wrote:
>>
>> It was my understanding tha P0026 was refused due to another proposal
>> allowing the mapping of parts within the initializer.
>>
>> E.g.
>> for (auto [a, b]: {list1, list2}) { ... }
>>
>> Personally I don't like the idea of this. Feels dirty to me that we need
>> to combine the different entities together just so we can iterate them at
>> the same time. It almost seems like mixing concerns.
>
>
> But you are mixing concerns. You're iterating through multiple ranges
> simultaneously. The loop will end when one of the ranges ends. The behavior
> of the loop is based on the aggregation of the ranges.
>
> So actually aggregating them seems entirely appropriate.

Da huh? The code above looks like it creates an initializer_list of
list1 and list2, and
then tries to create a and b as decompositions of each element of that
initializer_list.
That certainly doesn't do what the multi-iteration range-for proposal
tried to do.

> With a single range-for, you have "Variable : Range". With multi-range-for,
> you have "Variables : Ranges". Having it be "Variable: Range; Variable:

The loop above is not a "multi-range-for", it's using destructured bindings with
an initializer_list.

Nicol Bolas

unread,
Aug 8, 2016, 12:13:32 PM8/8/16
to ISO C++ Standard - Future Proposals


On Monday, August 8, 2016 at 11:41:33 AM UTC-4, Ville Voutilainen wrote:
On 8 August 2016 at 18:29, Nicol Bolas <jmck...@gmail.com> wrote:
> On Monday, August 8, 2016 at 1:14:05 AM UTC-4, Izzy Coding wrote:
>>
>> It was my understanding tha P0026 was refused due to another proposal
>> allowing the mapping of parts within the initializer.
>>
>> E.g.
>> for (auto [a, b]: {list1, list2}) { ... }
>>
>> Personally I don't like the idea of this. Feels dirty to me that we need
>> to combine the different entities together just so we can iterate them at
>> the same time. It almost seems like mixing concerns.
>
>
> But you are mixing concerns. You're iterating through multiple ranges
> simultaneously. The loop will end when one of the ranges ends. The behavior
> of the loop is based on the aggregation of the ranges.
>
> So actually aggregating them seems entirely appropriate.

Da huh? The code above looks like it creates an initializer_list of
list1 and list2, and
then tries to create a and b as decompositions of each element of that
initializer_list.
That certainly doesn't do what the multi-iteration range-for proposal
tried to do.

I wasn't specifically speaking to that example. It was about the general "Variables : Ranges" style vs. "Variable : Range ; Variable : Range" style. How you achieve one vs. the other isn't really the point.

Matthew Woehlke

unread,
Aug 8, 2016, 4:09:43 PM8/8/16
to std-pr...@isocpp.org
On 2016-08-07 16:49, Antonio Perez wrote:
> I propose that the range-based for statement is expanded to allow iteration
> through multiple for-range-initializes simultaneously [...]

Some general observations along the lines of the other replies...

A std::zip needn't be too expensive. It doesn't need to fully construct
a return object, and in fact probably *shouldn't*. At worst you have a
little bit of local state.

Another reason to go with something like std::zip is that you can
combine it with other iterator adaptors to iterate over the longest list
with 'filler' elements when the shorter runs out. Example:

for(auto&& [x, y] : std::zip(long_list, std::pad(short_list, 0)))

(...where std::pad is a hypothetical function that returns elements of
the input list until it runs out, then returns the filler element forever.)

--
Matthew

Thiago Macieira

unread,
Aug 8, 2016, 4:37:58 PM8/8/16
to std-pr...@isocpp.org
On segunda-feira, 8 de agosto de 2016 16:09:39 PDT Matthew Woehlke wrote:
> for(auto&& [x, y] : std::zip(long_list, std::pad(short_list, 0)))
>
> (...where std::pad is a hypothetical function that returns elements of
> the input list until it runs out, then returns the filler element forever.)

This is an infinite loop:

for (auto x : std::pad(short_list, 0))

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Matthew Woehlke

unread,
Aug 8, 2016, 4:50:41 PM8/8/16
to std-pr...@isocpp.org
On 2016-08-08 16:37, Thiago Macieira wrote:
> On segunda-feira, 8 de agosto de 2016 16:09:39 PDT Matthew Woehlke wrote:
>> for(auto&& [x, y] : std::zip(long_list, std::pad(short_list, 0)))
>>
>> (...where std::pad is a hypothetical function that returns elements of
>> the input list until it runs out, then returns the filler element forever.)
>
> This is an infinite loop:
>
> for (auto x : std::pad(short_list, 0))

...yes? That's what "returns the filler element forever" implies...

--
Matthew

Sean Middleditch

unread,
Aug 8, 2016, 7:29:47 PM8/8/16
to ISO C++ Standard - Future Proposals
On Monday, August 8, 2016 at 1:37:58 PM UTC-7, Thiago Macieira wrote:
On segunda-feira, 8 de agosto de 2016 16:09:39 PDT Matthew Woehlke wrote:
>   for(auto&& [x, y] : std::zip(long_list, std::pad(short_list, 0)))
>
> (...where std::pad is a hypothetical function that returns elements of
> the input list until it runs out, then returns the filler element forever.)

This is an infinite loop:

        for (auto x : std::pad(short_list, 0))

Note that the ranges proposal generalizes and clearly defines infinite ranges which are of course iterable in for-range. This isn't (won't be) a new problem.

Arthur O'Dwyer

unread,
Aug 8, 2016, 9:01:23 PM8/8/16
to ISO C++ Standard - Future Proposals
On Monday, August 8, 2016 at 4:29:47 PM UTC-7, Sean Middleditch wrote:
On Monday, August 8, 2016 at 1:37:58 PM UTC-7, Thiago Macieira wrote:
On segunda-feira, 8 de agosto de 2016 16:09:39 PDT Matthew Woehlke wrote:
>   for(auto&& [x, y] : std::zip(long_list, std::pad(short_list, 0)))
>
> (...where std::pad is a hypothetical function that returns elements of
> the input list until it runs out, then returns the filler element forever.)

This is an infinite loop:

        for (auto x : std::pad(short_list, 0))

Note that the ranges proposal generalizes and clearly defines infinite ranges which are of course iterable in for-range. This isn't (won't be) a new problem.

[I'm not contradicting anything you said, just expanding on it:]

This already isn't a new problem; it's easy to create objects X in C++11 such that

    for (auto&& x : X) { }

is an infinite loop, just as it was possible in C++03 to create objects X such that

    for (X_t::iterator it = X.begin(); it != X.end(); ++it) { }

was an infinite loop. (Yeesh, it pained me to write that code just now! Hooray for decltype and auto.)  The Ranges proposal does seem to inject the concept very neatly into its philosophical model, but infinite and/or sentineled ranges have been around since forever — just not necessarily in a nice neat guise.

It looks like in Eric Niebler's Ranges-v3, Matthew's hypothetical  std::pad(r, v)  is spelled  ranges::view::concat(r, ranges::view::repeat(v)) .

–Arthur

Klaim - Joël Lamotte

unread,
Aug 9, 2016, 6:28:56 PM8/9/16
to std-pr...@isocpp.org
As pointed  in the proposal, there was a discussion in this group for this proposal (so you might look in it for details about what was already
considered).
Also noted in it is the special implementation of `for_each( in_parrallel( range_a, range_b ... ) )` that I wrote
to show one possible implemenation that is similar to (but not exactly like, because lazy) zipping ranges.

I believe that the same idea could be done in very more simple ways with RangeV3 but I do not have practical experience with it yet.
The official issue report notes that there is great interest but not much work done.
I believe that this proposal was not worked more because of a mix of personal reasons of the author, lack of time and of champion.
I never heard that there was a rebuttal because of another feature, but I might be wrong.

Joël Lamotte


Izzy Coding

unread,
Aug 10, 2016, 12:59:27 AM8/10/16
to ISO C++ Standard - Future Proposals
As the author of the original proposal, other than the discussion referenced, I have had no interest from anyone to either champion or help do any other work to improve the proposal.
I read that my paper was read and considered, but was not adopted due to other work being done which would offer similar possibilities (I believe it was about the ranges v3).

My initial argument was that with a relatively simple change we could support looping multiple "ranges" directly in the language rather than making it a library feature. All the library options could still be usable and so this proposal would not affect anything. To me combining multiple ranges in a wrapper range still feels to me like mixing concerns.

E.G.

for (auto i = 0; i < min(range1.length, range2.length; ++i)
{
auto&& item1 = range1[i];
auto&& item2 = range2[i];

doSomethingWith(item1);

doSomethingRelatedButNotDirectlyConnectedWith(item2);
}

In this case other than doing 2 things in a single loop there is no direct connection between the two items. This would allow for multiple processes that maybe require a transaction. For example the usual financial example, an international account transaction maybe where range1 relates to amounts spent from a given account in one currency, range2 relates to the amount in another currency to pay into account 2. Account 1 and Account2 have nothing to do with each other than the transaction itself. I agree this is not the best way to design a system, but lots of us working on real code bases can't always change the legacy code that puts us in this position.

Personally I have always been available for comments and ideas on my proposal. Yes I had some personal things going on but I still have not had any interest at all thus far. Not even from the many people I have emailed it to. Not even a reply email to say "thanks but no thanks" or whatever.

Arthur O'Dwyer

unread,
Aug 10, 2016, 1:52:33 AM8/10/16
to ISO C++ Standard - Future Proposals
On Tuesday, August 9, 2016 at 9:59:27 PM UTC-7, Izzy Coding wrote:
As the author of the original proposal, other than the discussion referenced, I have had no interest from anyone to either champion or help do any other work to improve the proposal.
I read that my paper was read and considered, but was not adopted due to other work being done which would offer similar possibilities (I believe it was about the ranges v3).

My initial argument was that with a relatively simple change we could support looping multiple "ranges" directly in the language rather than making it a library feature. All the library options could still be usable and so this proposal would not affect anything. To me combining multiple ranges in a wrapper range still feels to me like mixing concerns.


FWIW, I think there's been no interest because the proposal actually isn't a good idea.
Like, it's not laziness or cliquishness or personal animosity, it's just that your proposed feature doesn't sound like a good idea.

I think Nicol had a very good point when he pointed out that the very idea of "iterate over two ranges" conflates two different concerns: there's the idea of "how many input ranges do I have", and then separately from that, there's "how do I aggregate them into something iterable."

For example, you might want

for (auto i = 0; i < min(range1.length, range2.length); ++i)


{
    auto&& item1 = range1[i];
    auto&& item2 = range2[i];

    doSomethingWith(item1);

    doSomethingRelatedButNotDirectlyConnectedWith(item2);
}


but then again you might want

    for (auto i = 0; i < max(range1.length, range2.length); ++i) ...  // presumably pad the shorter range, whichever it is

or

    for (auto i = 0; i < assert_equality(range1.length, range2.length); ++i) ...

or

    for (auto i = 0; i < sum(range1.length, range2.length); ++i) ...   // presumably concatenate the ranges


The particular way in which you aggregate the two ranges together is actually a very important parametrizable quantity. It's not clear why "min" ought to be privileged with a special language syntax but "max" not be.


But if we let the aggregation function be a parameter, and then we notice that assert_equality corresponds basically to zip() and sum corresponds basically to concat() and we start filling in missing primitives... well, before long, we've invented Ranges V3. ;)


So the natural inclination of Committee-folks is probably something like "Ranges exists and/or is coming; core language changes are hard; this core language change is both incomplete/asymmetric *and* redundant with Ranges; so, no."  If you think the proposal is good (which, again, I don't, so I think you should drop it), then you're going to have to figure out a way to change folks' minds.


 

I agree this is not the best way to design a system, but lots of us working on real code bases can't always change the legacy code that puts us in this position.


If you're proposing a core language change, DO NOT use the words "legacy code". Legacy code by definition can't be rewritten to use C++2w features; if it could, then you could equally well rewrite it to use Ranges, right now.  "Legacy code" is the magic phrase you should use when you are defending AGAINST a core language change (among other situations). See also: auto, static_assert.


Personally I have always been available for comments and ideas on my proposal. Yes I had some personal things going on but I still have not had any interest at all thus far. Not even from the many people I have emailed it to. Not even a reply email to say "thanks but no thanks" or whatever.


I'd venture that most software engineers of the caliber that hang out on this list (probably including yourself) get multiple emails a week — from recruiters, or autograph-seekers, or software salespeople, or whatever — to which the most socially appropriate response is "skim, delete, never reply."  After a few years of that routine, some people might come to the opinion that "skim, delete, never reply" is also the most socially appropriate response to any kind of unwelcome proposal.  Experienced recruiters, salespeople, etc., are conditioned over many years to expect "no reply" to 90% of their cold calls. Core-language-feature-proposers might save some of their own sanity by expecting likewise. ;)  Life is what it is.

HTH,
–Arthur

Matthew Greenwood

unread,
Aug 10, 2016, 5:28:22 AM8/10/16
to std-pr...@isocpp.org
I agree it is not a good idea anymore given the work on the ranges proposal.

The "min" in my example was for illustration only and I agree other forms of iteration are and should be possible.

I was not taking personal offence in any way. I was merely trying to say that given the proposal was on the list for discussion and that my contact details are in the proposal so that it was possible to have input.

I do understand the "skim, delete, never reply" process to emails. I was just stating what I had done to try and get extra feedback.

Given the current continuing work in other areas my original proposal is pretty much useless. I don't see the point in having multiple ways to do the same thing. That is just asking for abuse. Also the current library implementation direction would be much easier, extensible and quicker to get ratified and reduces the need for extra language rules. Also as you point out it could also be usable by earlier versions of the standard which would negate the requirement of rewriting legacy code.

On my previous project which inspired the initial proposal all our components were windows based and communicated through COM so the difference in compiler was rather agnostic. However, this would have been a limitation to other platforms. The library option is definitely the way to go in my opinion.
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/TqfLLDb6DW0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Matthew Woehlke

unread,
Aug 10, 2016, 9:33:39 AM8/10/16
to std-pr...@isocpp.org
On 2016-08-08 21:01, Arthur O'Dwyer wrote:
> It looks like in Eric Niebler's Ranges-v3, Matthew's hypothetical std::pad(r,
> v) is spelled ranges::view::concat(r, ranges::view::repeat(v)) .

In fact, I'd even considered writing it in this form. The combination of
concat + repeat is obviously more flexible (and repeat is what Python
uses), and we definitely want these. This combination *may* be common
enough to warrant a shorthand, though; the implementation of such could
just be concat + repeat:

namespace std { // or ranges::view, or whatever
auto pad(auto range, auto pad_element)
{
return concat(range, repeat(pad_element));
}
}

(Please pardon sloppy parameter declarations, above is meant to show the
body, not the declaration.)

--
Matthew

Reply all
Reply to author
Forward
0 new messages