Should we add drop to Phobos?

Jonathan M Davis jmdavisProg at gmx.com
Tue Aug 16 17:39:13 PDT 2011


On Tuesday, August 16, 2011 16:19 Dmitry Olshansky wrote:
> On 17.08.2011 2:35, Jonathan M Davis wrote:
> > On Tuesday, August 16, 2011 15:10 Dmitry Olshansky wrote:
> >> I think forward ranges that are value types should just have ref save{
> >> return this; } that in the end should entail 0 copies(?).
> >> Creating a simple function that does return move(x.save) on ForwardRange
> >> and move(x) on others might be good idea to abstract this discrepancy
> >> away.
> 
> While I agree on all of the above, I'm obviously not making myself clear
> about avoiding copies.
> 
> > That would copy. Returning ref allows you to chain functions that take
> > ref, and it allows you to alter the variable which is returned as long
> > as you do it in a single expression without assigning it to anything.
> > 
> > Besides, that doesn't help in this case anyway. The problem is that save
> > _isn't_ being called but a copy is still happening in the case of
> > value-type ranges and arrays, whereas it isn't happening with
> > reference-type ranges.
> 
> Let's deal with my strange "0 copies" point, in fact, it's about
> ensuring exactly 1 copy is made:
> 
> void doStuff(R)(R range)//here struct is copied when passed, class is
> passed by ref and not copied
> {
> auto r = range.save(); //here struct should avoid copy by
> returning ref, while class object finally does create a copy
> /// ... work with r
> }
> 
> replacing range.save to simply range on non-forward ranges.

save needs to save every time that it's called, or it's not doing its job. If 
it doesn't, then you risk not having a copy when you thought that you did. So, 
even in concept, what you're suggesting here really isn't a good idea. But 
regardless,

auto r = range.save();

will copy even if save returned by ref, because you can't declare a variable 
with ref like that. Only function parameters, foreach declarations, and return 
values can be ref. You'd have to be able to declare

ref E r = range.save();

for that to work (where E is the element type), and that's not legal. I think 
that what we should probably do is have something like

static if(isForwardRange!R)
 range = range.save;

at the beginning of any function which takes a forward range. If R is a class, 
then it fixes it so that the function's behavior is the same for classes as it 
is for structs. If it's a struct, as long as it doesn't declare a postblit 
constructor, I would fully expect the call to be optimized out, since 
ultimately, it's assigning itself to itself. The one case where it couldn't be 
optimized out would be if the struct had a postblit constructor, and odds are 
that if it has postblit constructor, it's actually reference type and not a 
value type anyway, so the save could would actually be needed - though I 
suppose that it might also not get optimized when a member variable of the 
struct (or a member variable of a member varible, etc.) has a postblit 
constructor, in which case you'd lose some efficiency. But that case would 
probably be quite abnormal.

The main problem here IMHO is the annoyance factor. It's easy to screw up (but 
then again, it's also easy to forget to call save, so being able to easily 
screw up some basics of forward ranges is nothing new). However, proper unit 
tests will catch it easily, so ultimately, I supposed that the main issue is 
just that it's more boilerplate code. I don't see any way around it though if 
we want all forward ranges to act the same with range-based functions. 
Otherwise, the reference-type ranges will be consumed as if they were only 
input ranges, while the value-type ranges won't be consumed.

- Jonathan M Davis


More information about the Digitalmars-d mailing list