On Friday, 13 July 2012 at 10:33:13 UTC, Jonathan M Davis wrote:
On Friday, July 13, 2012 12:24:45 monarch_dodra wrote:
On Friday, 13 July 2012 at 09:47:26 UTC, Jonathan M Davis wrote:
> On Friday, July 13, 2012 11:23:41 monarch_dodra wrote:
>> I think that in the case of your example, if "range" >> fulfills
>> the
>> requirements for (at least) an input range, then "save"
>> *should*
>> be called instead of "opSlice". I'm *think* this is what the
>> compiler does, but I'm not 100% sure.
> > The compiler never calls save. It will call opSlice on
> non-ranges, but it'll
> never call save on anything. It doesn't even know that save
> exists. It's
> unnecessary for ranges which aren't reference types, and you
> already have to
> worry about calling save on ranges that could be reference
> types any time that
> you pass them to a function that you don't want to consume > it,
> so it's not
> really a big deal to have to call save with foreach for such
> ranges. Most
> range-based functions don't even use foreach much anyway.
> > - Jonathan M Davis

What exactly are the semantics of save? The reference in
std.range isn't very clear. It would appear it is only useful it
its *existence* that promotes a range from input to forward.
However, how is it different from a simple assignment?

Also, if you are supposed to call save before a foreach
"consumes" your range, then why does foreach even bother making a
copy of the range before iterating on it? Isn't this behavior
promoting dangerous usage for ranges where save is simply
"{return this;}", but bites you in the ass the day you use a
range with a specific save?

I'm confused...

save exists so that classes can be forward ranges. Arrays and most structs are copied when you assign them to another variable or pass them to a function, but classes aren't. So, save was introduced to make it so that there is a way to explicitly copy a range. It becomes useful even for structs and arrays in that it documents that you're copying it, but it's outright necessary for classes. Unfortunately, since arrays and structs are by far the most common range types, save doesn't get used anywhere near as much as it should be. Basically, you use save if you want to guarantee that the range is copied, and
you don't use save if you don't care.

- Jonathan M Davis

Thanks a lot for the explanation. It makes a lot of sense.

However, foreach is starting to look very dangerous to me: Isn't the fact that it (potentially) calls opSlice, or makes a copy of your input just asking for potential problems? Or is this more of a "struct vs class" issue, that I do not yet fully grasp?

Shouldn't D _enforce_ an _explicit_ opSlice/save? Eg:
auto SomeContainer = ... ;
auto SomeRange = SomeContainer[ ... ];

foreach(v; SomeContainer[]) ... ; //Fine, iterate on a new range, and consume that foreach(v; SomeRange.save) ... ; //Fine, iterate on a copy of the range, and comue that

foreach(v; SomeContainer) ... ; //Fine, I will NOT call opSlice, and _consume_ your container foreach(v; SomeRange) ... ; //Fine, I will NOT copy, and _consume_ your range

On a side note, it would also make "foreach"s behavior clearer...

...

I suppose the "recommendation" is to use the above form, and that is what I will be doing as of now on.

But I still feel that the internal call to opSlice/copy is really a just trap disguised as a safety net...

Reply via email to