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...