December 21, 2018
On Fri, Dec 21, 2018 at 02:43:50PM +0000, Boris-Barboris via Digitalmars-d wrote:
> On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:
> > This is the feedback thread for the first round of Community Review for DIP 1018, "The Copy Constructor"
> 
> 1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like
> 
> A a;
> A fun()
> {
>     return a;      // lowered to return tmp.copyCtor(a)
> }
> 
> void main()
> {
>     A b = fun();    // the return value of fun() is moved to the location of b
> }
> 
> be allowed to modify the value of a. This is an absolute pain to read\debug, and I would instead like to see a mandatory const\immutable on the source reference.
>
> Copy operation should not modify the source, or it should not be called a copy. If we are talking about a typical smart pointer struct (Refcounted), copy still should not modify the source. It is D's transitive const\immutable that is a problem here and it must be explicitly casted away by the developer.
> 
> Only const also mitigates the combinatorial mess caused by 4 combinations of mutable\immutable copy constructors, since immutable is implicitly converted to const.

Shouldn't const be inout in this case?


> 2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..."
> 
> If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.
[...]

+1.  Doing otherwise adds needless complexity for something I can't think of any use cases for.



-- 
Programming is not just an act of telling a computer what to do: it is also an act of telling other programmers what you wished the computer to do. Both are important, and the latter deserves care. -- Andrew Morton
December 22, 2018
On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:
> Shouldn't const be inout in this case?

Just in case: it's not my code, it's from the DIP example snippets.
I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.
December 22, 2018
On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote:
> On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:
>> Shouldn't const be inout in this case?
>
> Just in case: it's not my code, it's from the DIP example snippets.
> I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.

One of the primary use cases for this is ref-counting stuff, the refcount needs to be mutable.
December 22, 2018
On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote:
> On Friday, 21 December 2018 at 21:40:23 UTC, H. S. Teoh wrote:
>> Shouldn't const be inout in this case?
>
> Just in case: it's not my code, it's from the DIP example snippets.
> I was advocating for forbidden mutable copy source. Inout indeed solves the boilerplate/combinatorial problem, but my main conjecture was about readability, wich is harmed by mutable source default.

The DIP goes over why const wasn't used for the source. Consider the following:

struct A {
    int* ptr;
}

Now to simulate the copy constructor:

A copy(ref const A a) {
    A result;
    result.ptr = a.ptr; // error can't convert `const int*` to `int*`
    return result;
}

Const is infectious and just causes more problems making it pretty much useless in the general case. It's only suitable for a very narrow use case. A lot of people using D avoid const entirely, except for really basic simple things involving primitive types.

December 22, 2018
On Friday, 21 December 2018 at 12:01:49 UTC, Kagamin wrote:
> On Thursday, 20 December 2018 at 16:53:29 UTC, Neia Neutuladh wrote:
>> this.tupleof = other.tupleof;
>> this.field30 = null;
>
> This will initialize field30 twice, won't it?

Indeed. What happens if field30 is const?
December 22, 2018
On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:
> On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:
> 1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like
>
> A a;
> A fun()
> {
>     return a;      // lowered to return tmp.copyCtor(a)
> }
>
> void main()
> {
>     A b = fun();    // the return value of fun() is moved to the location of b
> }
>
> be allowed to modify the value of a.

I totally agree. A copy modifying the source is very counter intuitive.

> 2). "A declaration is a copy constructor declaration if it is a constructor declaration that takes only one non-default parameter by reference that is of the same type as typeof(this), followed by any number of default parameters..."
>
> If you need other parameters, you are not performing a copy. Copy constructor needs no additional parameters. If the semantics of your domain problem involve parametrized post-copy operations, the code should state that explicitly - by using specialized properly-named methods, that notify the reader about this particularity.

I also agree with this. It is not even possible to pass something to the additional (default) parameters if not calling the Copy constructor explicitly, is it?

```
struct A {
    this(ref A src, int b = 0) {}
}

void main() {
    A a;
    auto b = a; // How to pass something as b?
    auto c = A(a, 1); // Works in this case
}
```

> 3). Section "Copy constructor usage", I don't understand the difference:
>
> void main()
> {
>     A a;
>     A b = a; // copy constructor gets called
>     b = a;   // assignment, not initialization
> }
>
> and
>
> void main()
> {
>     A a;
>     a = fun();      // NRVO - no copy constructor call
>     A b; // b is initialized after this semicolon.
>     // why is this one not an assignment, but initialization?
>     // do we have the difference formally and consistently defined for structs?
>     b = gun();      // NRVO cannot be performed, copy constructor is called
> }

I believe the first case is straight forward, so your confusion probably stems from the second case:

In the main function, we do have assignments to a and b. Those are not initializations and so no copy construction is taking place because of this. But when functions return, if NRVO (named return value optimization) cannot be performed, a copy of their return value is created. This happens in the case of gun because a is a module level variable and so NRVO cannot be performed.

I believe this example is indeed a bit confusing, mostly because the DIP does not at all explain what actually happens here and only uses the cryptic acronym NRVO. I believe a more detailed description is needed here, maybe in conjunction with a comparison to how this example would work with postblit.


December 22, 2018
In the section "Copy constructor call vs. blitting", the DIP explains how copying is handled when no copy constructor is defined:

> When a copy constructor is not defined for a struct, initializations are handled by copying the contents from the memory location of the right-hand side expression to the memory location of the left-hand side expression (i.e. "blitting"):
>
> struct A
> {
>     int[] a;
> }
>
> void main()
> {
>     A a = A([7]);
>     A b = a;                 // mempcy(&b, &a)
>     immutable A c = A([12]);
>     immutable A d = c;       // memcpy(&d, &c)
> }

Then later, in the section "Generating copy constructors", the DIP describes under which situations copy constructors are generated implicitly:

> A copy constructor is generated implicitly by the compiler for a struct S if:
> 
>     S does not define any copy constructors;
>     S does not have an overlapping field that defines a copy constructor;
>     S defines at least one member that has a copy constructor.
> 
> If all of the restrictions above are met, the following copy constructor is generated:
> 
> this(ref inout(S) src) inout
> {
>     foreach (i, ref inout field; src.tupleof)
>         this.tupleof[i] = field;
> }

Those two statements contradict each other. The first one clearly states that whenever no copy constructor is defined, copying is done by blitting (without exception).
December 22, 2018
On 12/22/2018 12:24 AM, Johannes Loher wrote:
> the cryptic acronym NRVO.

It means Named Return Value Optimization. Something I invented :-)

https://github.com/dlang/dmd/pull/9117

I shoulda patented it. Then I'd own all the other C++ compilers! HAHHAHHAHAHAHAHAHAAAA!
December 22, 2018
On Saturday, 22 December 2018 at 08:24:15 UTC, Johannes Loher wrote:
> On Friday, 21 December 2018 at 14:43:50 UTC, Boris-Barboris wrote:
>> On Tuesday, 18 December 2018 at 14:51:39 UTC, Mike Parker wrote:
>> 1). I do not like the ability to specify a mutable copy source. Under no circumstance should the code like
>>
>> A a;
>> A fun()
>> {
>>     return a;      // lowered to return tmp.copyCtor(a)
>> }
>>
>> void main()
>> {
>>     A b = fun();    // the return value of fun() is moved to the location of b
>> }
>>
>> be allowed to modify the value of a.
>
> I totally agree. A copy modifying the source is very counter intuitive.

Modifying the source _is_ stupid, however const is transitive so we have no way distinguish modification of the source from modifications through indirections in the source which is useful e.g. incrementing the reference count.
December 22, 2018
On Saturday, 22 December 2018 at 03:37:22 UTC, Rubn wrote:
> On Saturday, 22 December 2018 at 00:08:51 UTC, Boris-Barboris wrote:
>
> The DIP goes over why const wasn't used for the source. Consider the following:
>
> struct A {
>     int* ptr;
> }
>
> Now to simulate the copy constructor:
>
> A copy(ref const A a) {
>     A result;
>     result.ptr = a.ptr; // error can't convert `const int*` to `int*`
>     return result;
> }
>
> Const is infectious and just causes more problems making it pretty much useless in the general case. It's only suitable for a very narrow use case. A lot of people using D avoid const entirely, except for really basic simple things involving primitive types.

Exactly my intent, to cause problems in this case and force a cast.

For example, your code is actually not performing a copy of A. Your copy constructor is making a shallow copy, by duplicating only one vertex of the memory graph. If you were actually copying it, you would allocate a new int on the heap and initializa it from the const source.ptr without a problem.

Current blitting is perfect for shallow copies as it is.

If we are to give default semantical meaning to the copy constructor, that the programmer can generally rely on in the unfamiliar codebase, we need to restrict the developer. If we are to give it a semantic meaning of the function where you do whatever you want with source and dest, why should we call it a COPY constructor?