Thread overview
foreach over pointers OR foreach that mutates the iterator
Jan 25, 2017
Las
Jan 25, 2017
Jonathan M Davis
Jan 26, 2017
Las
Jan 26, 2017
Jonathan M Davis
Jan 26, 2017
guest
Jan 26, 2017
ZombineDev
Jan 26, 2017
guest
Jan 26, 2017
ZombineDev
Jan 26, 2017
Las
January 25, 2017
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?
January 25, 2017
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

January 26, 2017
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.
January 26, 2017
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)

January 26, 2017
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

January 26, 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.
January 26, 2017
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
    }));
}

January 26, 2017
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.
January 26, 2017
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!