Thread overview | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
January 25, 2017 foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to Las | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to Las | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to Las | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to Las | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to ZombineDev | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to guest | 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 Re: foreach over pointers OR foreach that mutates the iterator | ||||
---|---|---|---|---|
| ||||
Posted in reply to ZombineDev | 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!
|
Copyright © 1999-2021 by the D Language Foundation