Assignment operator overloading

Community Forums/Monkey2 Talk/Assignment operator overloading

Danilo(Posted 2016) [#1]
'Operator=' is used as comparison for equal.

What is the operator for operator-overloading assignments? (theObj = otherObj)
Also used as copy-operator?

Edit:
Help says '=' assignment operator can't be overloaded... hmm.
Would be nice to get assignment/copy too. '+=' etc. already working,
just plain '=' is missing.


marksibly(Posted 2016) [#2]
Normal assignment is a bit special, because it's sort of tied up with how the compiler actually works. The compiler really needs assignment to work 'as expected' for several operations such as initializing variables, copying temporaries around etc. This differs from "operator+" etc in that the compiler doesn't depend on these in any way. In fact, by the time the code is translated these have been converted to simple method calls. Not so assignment which is a 'real' backend op and can do 'magic' things that a normal method can't.

Overloading assignment doesn't even make much sense for reference types, eg:

Class C
   Field x:T
   Method Assign( c:C )
      Self.x=x
   End
End

Function Main()
   Local c:C
   c=New C    'Null object error!
End


It makes more sense for structs, but again it's something that's incredibly useful for the compiler to have control over, so it can safely create as many 'temporaries' as it wants, reliably initialize vars and so on. I guess the compiler could in theory have an 'internal' assignment op, but depending on what you're doing this may defeat the purpose of taking over assignment (eg: smart pointers).

In my experience, in about 93% of cases assignment overloading (in c++ anyway) is used to perform automatic type conversions, eg:

Struct Vec3
End

Struct Vec4
   Method Assign( v:Vec3 )   'prob. not a good idea...
   End
End


I'm am not totally against custom automatic conversions (although I'm less keen on them than I used to be) but I don't think this is the right way to do it. Instead, I think something like this would be better:

'global automatic conversion function
Function To:Vec4( v:Vec3 )
Return New Vec4( v,1 )
End

But that's a way off yet.


Danilo(Posted 2016) [#3]
Okay, looks like we need to import C++ operator= as a method:
struct sVec4
{
    // ...

	void operator=(const sVec4& other)
	{
		x = other.x;
		y = other.y;
		z = other.z;
		w = other.w;
	}
};

In MX2:
Extern

    Struct sVec4
        Method Assign( other:sVec4 ) = "operator="
    End

I have several external C++ classes here that overload operator== and operator=, that’s the reason I was asking.
template <class T>
class vector2d
{
public:
	vector2d<T>& operator= (const vector2d<T>&    other)    { X = other.X;     Y = other.Y;      return *this; }
	vector2d<T>& operator= (const dimension2d<T>& other)    { X = other.Width; Y = other.Height; return *this; }

	bool         operator==(const vector2d<T>& other) const { return equals(other); }

It’s not uncommon to overload the assignment operator in C++. Also copy-operator.

For string-pointers etc. it’s actually required to differentiate if the pointer is copied,
or if a new memory is allocated and the content is copied (new string, for example).

c=New C    'Null object error!


Of course ‚New‘ would call Operator New, the constructor. ;)

In C++ it’s two different things:
c = new C()  ' create
c = b        ' copy / assignment



Shinkiro1(Posted 2016) [#4]
I am glad it is not allowed.
Otherwise, at every assignment you can't be sure what's going on behind the scenes.
And assigning is one of the most frequent operations you do.
Just it being a possibility increases your program complexity and of course, the chance of bugs.

I think we are better off not paying this constant thinking tax.


marksibly(Posted 2016) [#5]
The assignment operator for sVec4=sVec4 can probably be skipped since it just does a straight copy in this case so should just work.

For assignment of other types, yes you'll currently need to use a method - I'd probably just go for 'Set' myself.

For comparisons, this should work:

Struct sVec4
Operator=:Bool( t:T )="operator=="
Operator<>:Bool( t:T )="operator!="
...etc...
End

To add support for the 'sort' operator, this *may* work:

Struct SVec4
Operator<=>:Int( rhs:sVec4 ) Extension="bbCompare"
End

...as long as sVec4 has defined 'operator<'.

> It’s not uncommon to overload the assignment operator in C++. Also copy-operator.

And IMO, this is an area where c++ is a real PITA. You can end up with a ton of 'conversion' methods in copy ctors, assignments etc., when really you should only need to implement type1->type2 *once*. And most copy ctors and assignments *are* just doing conversions...

Some of this extra crap is worth it in c++ because you get to optimize certain things in a bit more depth (eg: copy ctors can/must ignore the values currently in fields because they're uninitialized; assignment operators can't). But a lot of it just doesn't make sense for mx2.

> Of course ‚New‘ would call Operator New, the constructor. ;)

The problem is not with the New, but with the assignment. The variable 'c' will be null when its assignment operator is invoked, so you'll be invoking a method on a null object.


Danilo(Posted 2016) [#6]
Class/Struct Location
    pt:Point ptr
End

Local a:Location, b:Location

a = New Location()
b = a               ' <<    does it copy pointers only?
                    ' << or does it copy the contents of pt?

Looks like having no copy/assignment operator can be a problem and confusing/unclear (IMHO). More so when interfacing with C++. ;)


marksibly(Posted 2016) [#7]
> Looks like having no copy/assignment operator can be a problem and confusing/unclear (IMHO).

Not sure what you mean here - do you want different operators for 'assign struct instance' and 'assign class instance'?


Danilo(Posted 2016) [#8]
@Mark:
Take the last code. If "b = a“ copies the pointers like 'b.pt = a.pt' only, they point to the same memory location.
Changing 'a.pt.x‘ later, changes also 'b.pt.x‘ - because we just copied the pointer ‚pt‘, not the content.
To prevent this, in C++ you write a copy-operator that allocates new memory for b.pt and copies the content of a.pt into it.
Using this copy/assignment operator, after using "b = a“ -->> both objects have their own memory location for pt.

Changing a.pt.x does not change b.pt.x anymore. .pt does not point to the same memory anymore because the
copy/assignment-operator made a real copy, instead of copying pointer values only.

That’s the power of C++, and in MX2 I actually don’t know what it does. I would guess „b = a“ copies pointers only for .pt,
and a.pt and b.pt point to the same thing afterwards - which is not what I want. A copy/assignment operator can determine such behaviour.


marksibly(Posted 2016) [#9]
Still don't really get it sorry...

I am guessing what you're really after is something like Reference<T> (or 'safe pointers'), so that the struct/class distinction can be removed - but that ain't happening. If 'b' is a struct, 'b=a' will copy the struct contents. If 'b' is a class, only a reference will be copied. It may not be clear what 'b=a' is doing (is it in any language?) but it is at least 'well defined'!

There will always be stuff you can do in c++ that you can't do in mx2. But I am a reasonably experienced c++ coder who knows a fair bit about copy constructors and assignment overloading and conversions operators etc (and the huge amount of complexity and subtle side effects they introduce) and I am entirely happy with the compromises made for mx2, such as the struct/class distinction and the lack of assignment operator.

Either way, I struggle to see how the ability to overload assignment in ANY language can make it *clearer* what an assignment is doing! It *may* be copying a value, it *may* be copying an internal reference, it *may* open a window and print 'APRIL FOOLS'!


Danilo(Posted 2016) [#10]
No problem. Just forget it, and thanks anyway!


ImmutableOctet(SKNG)(Posted 2016) [#11]
@marksibly: I wouldn't throw out the idea, especially in C++. However, Monkey doesn't really need R-values as much as it needs C++'s optimization of R-values on the backend. I'd definitely think assignment operator overloading for structures should be a thing, though.

As for what Danilo was trying to explain, he's talking about deep copies vs. shallow copies. In the case of using pointers or highly controlled references for heap-based storage, having the assignment/copy constructor perform a deep copy of everything is ideal.

However, this doesn't have much to do with move-semantics, and has much more to do with state-coherence. In other words, having the structures keep their behavior regardless of copies. A trait that would need heavy personal control over shallow copies, and when they should happen.

In the case of C++ you have move semantics to go a step further, but I think the simplest way to handle this is a copy-constructor. I haven't been keeping up with Monkey too much, but wouldn't a copy-constructor effectively be the same thing as your 'To' function? It makes sense to me to have this constructor for better control of resources. For classes, definitely not, but for structures, this is ideal.

Trust me, your reservations toward assignment operators and copy constructors are nothing compared to the bugs associated with only having raw copies of structures everywhere. Just imagine if one decided to destroy a resource while a copy lived somewhere waiting to be used incorrectly. C++ already has move semantics, so the reduction of extra copies is already mostly implicit. Sure, you wouldn't be able to take full advantage of it, but we also have other means to that end anyway.

By the way, I'm sure this has been asked already, but any thoughts about BlitzMax's 'Var' coming back?

Structures lose a lot of their modern uses without temporary references. As long as you restrict getting a pointer to a "struct-reference", you'd never have the problem of incorrect storage. Even if you didn't restrict raw memory access to struct-references, it's not like it's much of a pitfall.


GC-Martijn(Posted 2016) [#12]
In PHP
$a =1;
$b=$a; // 1
$c=&$a; // ref , will change along with


Danilo(Posted 2016) [#13]
ImmutableOctet(SKNG) wrote:
As for what Danilo was trying to explain, he's talking about deep copies vs. shallow copies. In the case of using pointers or highly controlled references for heap-based storage, having the assignment/copy constructor perform a deep copy of everything is ideal.

Thanks, exactly what I was trying to explain with my limited knowledge of the english language.

- Object copying - Methods of copying
- Object copying - Implementation
Nearly all object-oriented programming languages provide some way to copy objects.
As the majority of languages do not provide most objects themselves, the programmer has to define how an object should be copied,
just as he or she has to define if two objects are identical or even comparable in the first place.
Many languages provide some default behavior.



marksibly(Posted 2016) [#14]
I would recommend a 'Method Copy:T()' for deep copies.

This is IMO much more flexible than a 'copy constructor' because it can do trickier stuff like can return an existing copy, a subclass of the return type etc. Also, if you've only got a reference to a base class and want to be able to deep copy it, you don't have much choice but to do it this way.

I don't understand the logic behind *always* performing deep copies on class instances - this would make it impossible to reference them indirectly because each assignment would create a new copy, eg: pushing an Actor on a stack would push a copy of the actor. This is what structs are really for.

Struct instances are always shallow copied because they are 'value types', like ints, floats etc. If you get to the point where it's necessary to deep copy a struct, I'd recommend thinking about making it a class instead, along with a Copy() method. Structs are really meant for simple, primitive types like Vec3, Mat4 etc. Structs that don't contain any references types never need anything more than a shallow copy as there's no indirection involved.

Class instances are never deep copied because they are passed by reference - this (and virtual methods) is kind of the point of classes, as without references you don't get indirection (easily).


Danilo(Posted 2016) [#15]
Thanks!


Gerry Quinn(Posted 2016) [#16]
I think the real issue is people slowly realising how much was lost when they abandoned the C++ object model. In C++ there's no real distinction between structs and classes and the programmer has complete control over how an object is created, copied or referenced.

You're not going to get that flexibility back even when you split objects into two different kinds with different semantics.