foreach over pointers OR foreach that mutates the iterator

ZombineDev via Digitalmars-d digitalmars-d at puremagic.com
Thu Jan 26 03:32:09 PST 2017


On Wednesday, 25 January 2017 at 16:15:49 UTC, Las wrote:
> So the reference says that (when range has the properties) 
> `foreach (e; range) { ... }` is equivalent to:
> for (auto __r = range; !__r.empty; __r.popFront())
> {
>     auto e = __r.front;
>     ...
> }
>
> Though this isn't always true, as when I use this struct:
> struct S {
> 	int front = 10;
> 	void popFront() {
> 		--front;
> 	}
> 	@property bool empty() {
> 		return front == 0;
> 	}
> }
>
> then I can do this:
> void main() {
> 	S s;
> 	auto p = &s;
> 	p.popFront;
> 	writeln(p.front);
> }
>
> But not this:
> void main() {
> 	S s;
> 	auto p = &s;
> 	foreach(i; p)
> 		writeln(i);
> }
>
> x.d(18): Error: invalid foreach aggregate p
> Failed: ["dmd", "-v", "-c", 
> "-of/tmp/.rdmd-1000/rdmd-x.d-032B33C4A922C519594F67AF08DBF6C9/objs/x.o", "x.d", "-I."]
>
> Why should this work? Because some times I want foreach to 
> modify the iterator, because some times I like to have an inner 
> foreach that uses the same iterator as the outer one, to  
> effectively still iterate over the same range, but change the 
> contents of the loop.
>
> Bad example:
> foreach(i; &range) {
>   writeln(i);
>   if(i > 2) foreach(i; &range) {
>     writeln(i * 3);
>     if(i < 10)
>       break;
>   }
> }
>
> This loop would change behavior each time one of the 'if's pass.
>
> An alternative would be to implement a new foreach, perhaps 
> &foreach, that does this instead:
> for (/+ NB: We are not copying the range! +/; !range.empty; 
> range.popFront())
> {
>     auto e = range.front;
>     ...
> }
>
> Related:
> UFCS does not work on pointers, which matters because of 
> std.range.primitives.
>
> Thoughts?

Not sure if this is a bug in isInputRange or foreach, but they 
should work consistently. Anyway, another solution is to use 
refRange:

void main() {
     import std.range : refRange;
     S s;
     auto p = refRange(&s);
     foreach(i; p)
         writeln(i);
}

That way you don't need to dereference 'p' everytime you want to 
iterate over it. Plus, it should compose well with other range 
wrappers / algorithms.


More information about the Digitalmars-d mailing list