www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - foreach over pointers OR foreach that mutates the iterator

reply Las <lasssafin gmail.com> writes:
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?
Jan 25 2017
next sibling parent reply Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Wednesday, January 25, 2017 16:15:49 Las via Digitalmars-d 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."]
Given that static assert(isInputRange!(typeof(&s))); compiles, the fact that the second foreach doesn't work is arguably a bug.
 Related:
 UFCS does not work on pointers, which matters because of
 std.range.primitives.
It only matters if you're trying to define the range primitives for your range as free functions for some reason. Just put them on the type itself and be done with it. The only reason that wouldn't work would be if you weren't in control of the code for the type in question, and I'm inclined to think that turning a type that isn't a range into a range using free functions isn't the best of ideas. You can always wrap the type in another type though if you really want to turn it into a range and can't just because you're dealing with a pointer. - Jonathan M Davis
Jan 25 2017
parent reply Las <lasssafin gmail.com> writes:
On Wednesday, 25 January 2017 at 20:22:54 UTC, Jonathan M Davis 
wrote:
 It only matters if you're trying to define the range primitives 
 for your range as free functions for some reason. Just put them 
 on the type itself and be done with it. The only reason that 
 wouldn't work would be if you weren't in control of the code 
 for the type in question, and I'm inclined to think that 
 turning a type that isn't a range into a range using free 
 functions isn't the best of ideas. You can always wrap the type 
 in another type though if you really want to turn it into a 
 range and can't just because you're dealing with a pointer.

 - Jonathan M Davis
Strings use std.range.primitives.
Jan 26 2017
parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Thursday, January 26, 2017 09:05:17 Las via Digitalmars-d wrote:
 On Wednesday, 25 January 2017 at 20:22:54 UTC, Jonathan M Davis

 wrote:
 It only matters if you're trying to define the range primitives
 for your range as free functions for some reason. Just put them
 on the type itself and be done with it. The only reason that
 wouldn't work would be if you weren't in control of the code
 for the type in question, and I'm inclined to think that
 turning a type that isn't a range into a range using free
 functions isn't the best of ideas. You can always wrap the type
 in another type though if you really want to turn it into a
 range and can't just because you're dealing with a pointer.

 - Jonathan M Davis
Strings use std.range.primitives.
Yes, because arrays do not have member functions. And as it is, there's been discussion of moving those functions to object.d so that they're always present rather than having to import std.range.primitives to get them (the main blocker is that because of auto-decoding, all of that UTF mess would have to then be available to object.d, which is in druntime, and std.utf is in Phobos, but it may still happen). The fact that arrays get the range primitives via UFCS and an import is required to use them is actually problematic, much as it works (e.g. if you forget the import, things don't work so well). With user-defined types, you have member functions and you don't need UFCS. - Jonathan M Davis
Jan 26 2017
prev sibling next sibling parent guest <guest gmail.com> writes:
On Wednesday, 25 January 2017 at 16:15:49 UTC, Las wrote:
 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);
 }
p.popFront == p->popFront This works as expected: foreach(i; *p)
Jan 26 2017
prev sibling parent reply ZombineDev <petar.p.kirov gmail.com> writes:
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.
Jan 26 2017
next sibling parent reply guest <guest gmail.com> writes:
On Thursday, 26 January 2017 at 11:32:09 UTC, ZombineDev wrote:
 Not sure if this is a bug in isInputRange or foreach, but they 
 should work consistently.
Copy/paste from primitives.d: template isInputRange(R) { enum bool isInputRange = is(typeof( (inout int = 0) { R r = R.init; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke popFront() auto h = r.front; // can get the front of the range })); }
Jan 26 2017
parent ZombineDev <petar.p.kirov gmail.com> writes:
On Thursday, 26 January 2017 at 12:03:39 UTC, guest wrote:
 On Thursday, 26 January 2017 at 11:32:09 UTC, ZombineDev wrote:
 Not sure if this is a bug in isInputRange or foreach, but they 
 should work consistently.
Copy/paste from primitives.d: template isInputRange(R) { enum bool isInputRange = is(typeof( (inout int = 0) { R r = R.init; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke popFront() auto h = r.front; // can get the front of the range })); }
Yes, I know how isInputRange is implemented. The question is: should it disallow pointers if foreach does not try to dereference them automatically, or should foreach do that.
Jan 26 2017
prev sibling parent Las <lasssafin gmail.com> writes:
On Thursday, 26 January 2017 at 11:32:09 UTC, ZombineDev wrote:
 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.
This is really helpful; thanks!
Jan 26 2017