Difference between range `save` and copy constructor

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sun Feb 16 12:38:51 UTC 2020


On Sunday, February 16, 2020 3:41:31 AM MST uranuz via Digitalmars-d-learn 
wrote:
> I have reread presentation:
> http://dconf.org/2015/talks/davis.pdf
> We declare that `pure` input range cannot be `unpoped` and we
> can't return to the previous position of it later at the time. So
> logically there is no sence of copying input range at all. So
> every Phobos algorithm that declares that it's is working with
> InputRange must be tested for working with range with disabled
> copy constructor and postblit. And if it is not it means that
> this algroithm actually requires a forward range and there we
> missing `save` calls?
> Because as it was written in this presentation a range copy is
> undefined (without call to save). So it's illegal to create copy
> of range in Phobos algorithms without `save`?
> So we need a test for every algorithm that it is working with
> range with disabled copy constructor and postblit if we declare
> that we only use `save` for range copy?

A range that can't be copied is basically useless. Not only do almost all
range-based algorithms take their argumenst by value (and thus copy them),
but foreach copies any range that it's given, meaning that if a range isn't
copyable, you can't even use it with foreach. And since many range-based
algorithms function by wrapping one range with another, the ability to copy
ranges is fundamental to most range-based code.

That being said, the semantics of copying a range are not actually defined
by the range API. Whether iterating over a copy affects the original depends
on how a range was implemented. e.g. In code such as

void foo(R)(R r)
    if(isInputRange!R)
{
    r.popFront();
}

foo(range);

whether the range in the original range in the calling code is affected by
the element being popped from the copy inside of foo is implementation
dependent. If it's a class or a struct that's a full-on reference type, then
mutating the copy does affect the original, whereas if copying a range is
equivalent to save, then mutating the copy has no effect on the original.
And with pseudo-reference types, it's even worse, because you could end up
_partially_ mutating the original by mutating the copy, meaning that you can
get some pretty serious bugs if you attempt to use a range after it's been
copied.

This means that in practice, in generic code, you can never use a range once
it's been copied unless you overwrite it with a new value. Passing a range
to a function or using it with foreach basically means that you should not
continue to use that range, and if you want to be able to continue to use
it, you need to call save and pass that copy to the function or foreach
instead of passing the range directly to a function or foreach.

In order to fix it so that you can rely on the semantics of using a range
after it's been copied, we'd have to rework the range API and make it so
that the semantics of copying a range were well-defined, and that gets into
a huge discussion on its own.

As things stand, if you want to test range-based code to ensure that it
works correctly (including calling save correctly), you have to test it with
a variety of different range types, including both ranges where copying is
equivalent to calling save and ranges which are reference types so that
copying them simply results in another reference to the same data such that
iterating one copy iterates all copies.

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list