called copy constructor in foreach with ref on Range

Stanislav Blinov stanislav.blinov at gmail.com
Tue Jun 23 06:26:21 UTC 2020


On Tuesday, 23 June 2020 at 03:52:23 UTC, Jonathan M Davis wrote:
> On Monday, June 22, 2020 9:25:55 PM MDT Stanislav Blinov via 
> Digitalmars-d- learn wrote:
>> On Tuesday, 23 June 2020 at 02:41:55 UTC, Jonathan M Davis 
>> wrote:
>> > As things stand, uncopyable ranges aren't really a thing, 
>> > and common range idiomns rely on ranges being copyable.
>>
>> Which idioms are those? I mean, genuine idioms, not design 
>> flaws like e.g. references.
>
> It is extremely common to wrap ranges in other ranges (and in 
> fact, you basically have to in order to have lazy ranges). That 
> really doesn't work very well - if at all - if you can't copy 
> the range. It might be possible with a bunch of explicit calls 
> to move...

Where would "a bunch of explicit calls" come from???

See example implementations of map and filter here:
https://gist.github.com/radcapricorn/d76d29c6df6fa822d7889e799937f39d

If we disallow references as ranges, then any function that takes 
by value *owns that value*, and shouldn't needlessly copy it, 
only move. Yes, even when (in fact, especially when) that value 
is a mere wrapper over a pointer. Only when it would have to 
legitimately create a copy (i.e. fork the range) would it not 
move. At which point, if the range is non-copyable, it would be a 
compile error. Which would be a *good thing*.
That is what all generic code that works with values *should* be 
doing: moving, moving, moving. Copy ctors should only be called 
when *necessary*, not "meh, whenever".

Conceptually:

// assume OtherRange is a struct, which is what ranges should be
Range wrapRange(OtherRange)(OtherRange other)
{
     // ...possibly pop some elements from `other`
     return Range(other);
}

That's what Phobos is doing now. What's going on there? It makes 
a copy of `other` and then destructs `other`. There's no other 
use of `other`. So, it can simply (no, it *should*) just move 
`other`. Moving it would leave `other` in .init state, which is 
destructible, and construct Range with a valid value, without 
calling any copy ctors.
Walter's "moving, copying and forwarding" proposal, if it ever 
comes to fruition, would even let us drop the use of library 
moves in such cases.

> ...but that would result in range-based code in general being 
> @system without a clean way to use @trusted, since

??? `move` infers.

> whether it's really safe to mark such moves with @trusted would 
> depend on the specific range implementation, which then is a 
> big problem with generic code.

A one-argument `move` basically never should be inferred @system! 
(that is, if implementation isn't buggy, which it would seem it 
currently is in druntime...) Two-argument `move` might be 
@system, if the destructor is @system. Which means the range is 
@system anyway.

> Regardless of whether it's actually possible to make it work 
> though, it's a complete mess in comparison to simply copying.

> And the fact that chaining range-based calls is extremely 
> common makes the problem that much worse.

You mean this?

auto a = range.filter!foo.map!bar.array;

It's not a problem in any way. If `range` is non-copyable:

auto a = range.move.filter!foo.map!bar.array;

...which, if we do uphold the ".init is empty range" will *never* 
leave you with an invalid `range`, unlike copies that do 
who-knows-what.

> The way that ranges are routinely passed around and wrapped 
> works as well as it does, because ranges are copyable.

Now I see what you mean by "idioms". Copying things that 
shouldn't be copied :) Which is the bane of all Phobos.

> The semantics of copying a variable or object vary wildly 
> depending on the type regardless of whether we're talking about 
> ranges. Copying a pointer or reference is still a copy even if 
> it isn't a deep copy. Copying a range _always_ results in a 
> copy, just like it does with any other type. It just doesn't 
> necessarily result in a copy which can be independently 
> iterated.

:) Which means:
- input ranges shouldn't be copyable -> no wild semantics 
anymore, statically enforced
- forward ranges should migrate save() into copy ctors -> 
guarantee a copy does what it should
- of course, .init must be an empty range, and all of this is 
moot so long as references are allowed to be ranges (all of this 
is one big "what if")

I feel like a broken record. Maybe I just should take that gist 
and make a more practical implementation out of it, as a demo 
(no, not with Andrei's `fetchNext`).



More information about the Digitalmars-d-learn mailing list