December 14, 2006
Kevin Bealer wrote:
>
> == Quote from Andrei Alexandrescu (See Website For Email)
> (SeeWebsiteForEmail@erdani.org)'s article
>>
>> Here's a better alternative:
>> Require opAssign() to always return void and have the compiler return a
>> reference to the left-hand side. That is, transform:
>> a = b;
>> into:
>> (a.opAssign(b), a);
>> with the mention that a only gets evaluated once.
>> This way assignment _always_ returns its left-hand side and user code
>> cannot subvert that behavior. Also the code will be efficient because no
>> more spurious copies are being made.
> 
> I wonder, are there cases where assignment should not return 'this'?

I've been thinking the same thing, and so far haven't been able to come up with an example where "correct" use of an assignment operator should not return 'this'.  I was considering suggestion the same thing as Andrei above, but was hoping someone would post a counterexample in the interim :-)  About the only weird thing with returning void and allowing this:

    a = b = c;

Is that it isn't equivalent to this:

    a.opAssign( b.opAssign( c ) );

Which may be a bit confusing.  But it does seem quite natural that the result of an assignment should always be the LHS of the assignment.


Sean
December 14, 2006
Jarrett Billingsley schrieb:
> "Walter Bright" <newshound@digitalmars.com> wrote in message news:elnv14$r2d$1@digitaldaemon.com...
> 
>> For whatever the merits of opCall vs this(), efficiency is NOT a problem with either, and is not a reason to choose one or the other. The generated code is the same.
>>
>> Under the hood, they are the same. Both take a hidden pointer to where the result is stored. The rest is window dressing.
> 
> Alright then.  All I've got then is the orthogonality argument.  But you won't listen to that either.
> 
> People will come to D from C++ and C# and ask "where are constructors in structs?" and we'll say "you have to use static opCall."  And they'll ask "why?"  And all we'll be able to do is shake our heads, sigh, and say "I don't know."
> 
> For the last time, even though you don't care: we have been using static opCall as a WORKAROUND.  As in *we never intended for that to be the canonical method of constructing a struct*.  I don't think anyone would complain if they were given a consistent, logical method of initializing any aggregate type. 
> 
> 

To me another big problem is the cast to a struct. How could anyone, who is new to D/ didn't study the spec enough expect, that a cast to a struct is done by calling the "opCall". One of those who came from C++ (like me too) would expect the static opCall as the equivalent to those rather rarely used C++-operator()() overloads.
What could one answer if some of them ask why it's like this? Couldn't there just be another name than static opCall?

Then, to keep it from being ambigous, there could be a change of the let's say opCreate syntax. It doesn't has to be like in C++, I imagine something in the direction of static struct initializers:

struct A
{
	int i, j;
}

int v = 9;
A a = A:{a:10, j:v /*not only constants*/};
December 14, 2006
Lionello Lunesu wrote:
> Walter Bright wrote:
>> It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to:
>>
>>     *(a.opAssign(b))
>>
> 
> class C {
>   C* opAssign(...) {...}
> }
> struct S {
>   S* opAssign(...) {...}
> }
> 
> Seriously?? Ugh.

It isn't necessary for classes, as they are already reference types.

> Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized.
> 
> Am I missing something?

Yes. The issue is that opAssign has both a return value *and* copies values into it's 'this' pointer.
December 14, 2006
Walter Bright wrote:
> Stewart Gordon wrote:
<snip>
>> The programmer would have a choice - opAssign returning void to modify in-place the object referenced by the lvalue, or returning a new object that will be assigned to the lvalue.  What is this precluding?
> 
> opAssign has 3 externally visible characteristics:
> 
> 1) the parameter
> 2) the 'this' pointer
> 3) the return value
> 
> Your proposal mixes up 2 and 3. opAssign works like:
> 
>     a = b
> becomes:
>     a.opAssign(b)
> 
> The return value is not assigned to a, it is the value of the expression (a = b). Mixing up the return value and the assignment to a will cause problems, as the two are different things, and should be independent.

C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)?

Stewart.
December 14, 2006
Stewart Gordon wrote:
> C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)?

I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.
December 14, 2006
Walter Bright wrote:
> Stewart Gordon wrote:
>> C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)?
> 
> I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.

For one, it might be useful to return some sort of proxy for 'a'.

--bb
December 15, 2006
Lionello Lunesu wrote:
> Walter Bright wrote:
>> It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to:
>>
>>     *(a.opAssign(b))
>>
> 
> class C {
>   C* opAssign(...) {...}
> }
> struct S {
>   S* opAssign(...) {...}
> }
> 
> Seriously?? Ugh.
> 
> Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized.
> 
> Am I missing something?

There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious.


Andrei
December 15, 2006
Walter Bright wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> Require opAssign() to always return void and have the compiler return a reference to the left-hand side. That is, transform:
>>
>> a = b;
>>
>> into:
>>
>> (a.opAssign(b), a);
>>
>> with the mention that a only gets evaluated once.
>>
>> This way assignment _always_ returns its left-hand side and user code cannot subvert that behavior. Also the code will be efficient because no more spurious copies are being made.
> 
> It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to:
> 
>     *(a.opAssign(b))

Makes sense, but then I keep on racking my brain to ever think of any C++ code that ever return something else than *this. Never saw any, never wrote any - ever.

So I just conclude that customizing =, +=, -= etc. is a good thing, but customizing their return type and value is not. Adding the silly "return this;" litany at the end of every single operator is not something for a language that wants to do things the right way. The speed will be better, too. It's kind of annoying to know that I can't write:

a = b;

and simply have the compiler do what it takes and no more. Every of those spurious copies is just some more discomfort I have to put in.


Andrei
December 15, 2006
Walter Bright wrote:
>> Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized.
>>
>> Am I missing something?
> 
> Yes. The issue is that opAssign has both a return value *and* copies values into it's 'this' pointer.

This is wrong. The assignment should return an lvalue, and the current semantics of opAssign do not allow that. Here is some code:

void Increment(inout int x) {
  ++x;
}

int a;
Increment(a = 5);

This code leaves a containing the value 6. This is because a is first assigned a 5, then a is passed __as an lvalue__ to Increment, which bumps it.

Now consider:

struct S {
  int a;
  S opAssign(int x) {
    a = x;
    return this;
  }
}

void Increment(inout S x) {
  ++x.a;
}

S a;
Increment(a = 5);

This is going to have very different (and useless and unwanted) semantics.

Again, the right thing to do: give the Caesar what belongs to the Caesar. Have the user do the assignment (and return void), and have the compiler pass the lhs lvalue around, when needed.


Andrei
December 15, 2006
Walter Bright wrote:
> Stewart Gordon wrote:
>> C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)?
> 
> I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.

Just did so in my previous post.

Andrei