Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add copy constructor to byUTF() #6900

Merged
merged 1 commit into from
Mar 13, 2019
Merged

Conversation

WalterBright
Copy link
Member

@WalterBright WalterBright commented Mar 12, 2019

A ForwardRange's save() function has a confusing relationship with the copy constructor. This helps clarify it. save() acts like a copy constructor, but it uses assignment semantics instead of construction semantics, carrying the seeds of bugs, besides making it impossible to call save() on a const object. This PR has save() use a constructor to construct the copy.

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @WalterBright!

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + phobos#6900"

@WalterBright WalterBright force-pushed the byUTF-dip1000 branch 6 times, most recently from fc775f9 to 1427d49 Compare March 12, 2019 23:26
@WalterBright
Copy link
Member Author

Continuing to call save() in the implementation will be necessary until copy constructors become de riguer.

@dlang-bot dlang-bot merged commit b083f26 into dlang:master Mar 13, 2019
@WalterBright WalterBright deleted the byUTF-dip1000 branch March 13, 2019 08:09
@jmdavis
Copy link
Member

jmdavis commented Mar 13, 2019

@WalterBright

A ForwardRange's save() function has a confusing relationship with the copy constructor. This helps clarify it. save() acts like a copy constructor, but it uses assignment semantics instead of construction semantics, carrying the seeds of bugs, besides making it impossible to call save() on a const object. This PR has save() use a constructor to construct the copy.

Continuing to call save() in the implementation will be necessary until copy constructors become de riguer.

Wait, wait, wait. Are you trying to say that copy constructors are supposed to replace save now? That can only work if classes can't be ranges, because classes don't have copy constructors. They're reference types. As such, range-based code can't rely on copying ranges. You need to call save for that to work with all forward ranges. And in general, copying a range has undefined semantics. If it's a reference type, then you end up with another reference to the same thing. If it's a value type (including if it implements a postblit constructor or copy constructor to behave that way), then it's equivalent to calling save. And if it's pseudo-reference type, then what happens depends on what the range's members are. In the case of something like a dynamic array, that ends up being equivalent to calling save, but for structs, it could easily end up being the case that if you iterate the original after iterating the copy (or vice versa), the range will be screwed up, because the value types in the range were independent, but the reference types weren't. In fact, a wrapper range like byUTF risks that exact problem if it's a wrapping range which is a reference type, because it has a buffer which is a static array - and thus a value type. So, if you copy the wrapper range and then iterate either the original or the copy, then any time that popFront would just change the value types in the range to iterate to the next element, it would only affect the one being directly iterated, whereas any time that calling popFront pops the next element off of the wrapped reference type range, it will affect both.

So, you can't just copy a range and expect the behavior of save - far from it. Are you suggesting that we start requiring that forward ranges implement copy constructors in order to be a forward range rather than that they implement save? If we did that, then we could start relying on copying a forward range resulting in an independent copy, but that wouldn't work with classes - we'd have to start making it so that classes can't be forward ranges - and it would be problematic in the cases where a struct doesn't actually need a copy constructor in order to make it so that copying it results in an independent copy, because then you'd basically be forced to declare an unnecessary copy constructor just to make it so that isForwardRange treated it as a forward range.

@@ -4270,6 +4270,19 @@ if (isSomeChar!C)
{
static struct Result
{
this(return R r)
{
this.r = r;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I probably need to re-read the copy constructor DIP, but this looks like it's defined to only copy the range being wrapped and not the other members. If so, then how is this range being copied properly? Also, was it the intention to make it equivalent to calling save? If so, then it's not, because it doesn't call save on the range being wrapped, and what happens then depends entirely on how that particular range is implemented (e.g. if it's a class, it's not being saved).

@PetarKirov
Copy link
Member

PetarKirov commented Mar 15, 2019

@jmdavis I think you got confused about what is happening here. This PR added two constructors, none of which are copy-constructors in the C++ sense (T(cv-qualifiers_opt T&):

  1. The first one simply creates a new instance with blank state. It is used only at the end of the function: return Result(r);.
    It is not used for copying Result type, nor it is used in the save function.
  2. The second constructor has all the necessary parameters to copy the whole internal state, so it was what Walter referred to as "copy constructor", even though it's not one in the strict C++ sense, or what is proposed by the DIP. This constructor is used inside the save() member function.

All in all, implementing save() for struct ranges is much more elegantly done via copy-construction (not only in the strict C++ sense), so net result of this PR is that the code is more straightforward and the compiler has easier time tracking scope/return.
These changes have nothing to do with the copy-constructor DIP, nor you should judge them as some grand language-breaking initiative. It's just that for this particular piece of code, this refactoring was the most beneficial.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants