Operator Overloading in REALbasic

| | Comments (4)

So you want to know about operators, eh? For (almost) every operator in the language, there's a way to overload it as well. Operator overloading is trivial in REALbasic due to it just being a special form of method.

Most overloaded operators look alike. It's a class method where the self instance is implied (obviously) as one of the operands, and the other operand is passed as a parameter. So let's take a look at an easy one -- we're going to make a Vector class and give it an addition operator.

[rbcode]
Class Vector
Dim x as Integer
Dim y as Integer

Function Operator_Add( rhs as Vector ) as Vector
dim ret as new Vector
ret.x = self.x + rhs.x
ret.y = self.y + rhs.y

return ret
End Function
End Class
[/rbcode]

That's all there is to it! The parameter passed in is the right-hand side of the + operator (hence calling it rhs) and the return value is the addition of the implicit self operand and the rhs.

So that's interesting... but what if you want to multiply the Vector by a scalar? Well, then let's overload the * operator to see!

[rbcode]
Function Operator_Multiply( rhs as Integer ) as Vector
Dim ret as new Vector
ret.x = self.x * rhs
ret.y = self.y * rhs

return ret
End Function
[/rbcode]

It looks very similar to the addition overload, but you'll notice that we've overloaded it for integers. This means that you can say SomeVector = MyVector * 5.

This brings us to the "other" form of operators to overload -- the "right" versions. You'll notice that SomeVector = MyVector * 5 will compile, but SomeVector = 5 * MyVector will not. That's because we've yet to overload the operator where our implicit self is on the right-hand side. So let's overload that next.

[rbcode]
Function Operator_MultiplyRight( lhs as Integer ) as Vector
Dim ret as new Vector
ret.x = self.x * lhs
ret.y = self.y * lhs

return ret
End Function
[/rbcode]

It looks exactly the same as the Operator_Multiply overload, except that now the parameter passed in is the left-hand side operand instead of the right-hand side. Every binary operator that you can overload has both a "normal" and a "right" operator to overload. This lets you properly define the behavior for any situation. The binary operators that you can overload are: +, -, *, /, \, Mod, And, Or, (and in a future version, you might be able to overload ^).

There are two unary operators you can overload. Not and - (negate). So let's take a look at how to negate our Vector class.

[rbcode]
Function Operator_Negate() as Vector
Dim ret as new Vector
ret.x = -self.x
ret.y = -self.y

return ret
End Function
[/rbcode]

Since it's a unary operator, that means there's only one operand -- the implicit self. The same is true for the Operator_Not (which is a boolean operator like And & Or).

So we're not quite as done with the binary operators as I led you to believe. There's one large group of binary operators that are all lumped into one overload -- comparison operators. Whenever you use a < , >, < =, >=, = or <> operator, it actually calls one single comparison function that takes care of everything called Operator_Compare. This operator's job is simple -- it takes the second operand (left- or right-hand side -- there's only *one* version of Operator_Compare to implement since the compiler can infer the parameter based on type information) and it returns an integer to signify the result of the comparison. This integer is as follows: < 0 means that self is less than the passed parameter, = 0 means that self and the passed parameter are equal and > 0 means that self is greater than the passed parameter. When the compiler sees any comparison operation, it calls the operator_compare and uses the results as appropriate for the operator. This is why you don't have to try to overload every single comparison operator -- the compiler is smart enough to do it for you.

So let's define a very silly comparison -- we're going to compare our vector's values against a Double. Note, this makes no mathematical sense. But we're going to put in a fudge factor of .5 to each comparison so that you can see when a Vector and a Double are equal.

[rbcode]
Function Operator_Compare( op2 as Double ) as Integer
dim heh as Double = sqrt( pow( self.x, 2 ) + pow( self.y, 2 ) )

if heh < op2 - .5 then return -1

if heh > op2 + .5 then return 1

return 0
End Function
[/rbcode]

So let's take a look at one very handy set of operators -- conversion. This is one operator that has two different signatures. It lets you convert from one type to another in two different fashions.

First is the way to convert from something. So let's see how to convert a Vector from an Integer:

[rbcode]
Sub Operator_Convert( rhs as Operator )
self.x = rhs
self.y = rhs
End Sub
[/rbcode]

It's as simple as that! This code gets called when you have something like this:
[rbcode]
Dim v as Vector
v = 10
[/rbcode]

You'll notice that we don't use the new keyword anywhere. That's because the assignment operator is returning a new instance of a Vector. This means that saying Dim v as new Vector creates one instance, which is then immediately overwritten by the v = 10 line. So we simply leave the call to new out.

The second type of conversion is a "to" conversion. This happens when you want to convert your class to another type. So let's see how to convert a Vector to a string so that it can be printed:

[rbcode]
Function Operator_Convert() as String
return "x: " + Str( self.x ) + ", y: " + Str( self.y )
End Function
[/rbcode]

You can call this code like this:
[rbcode]
dim v as Vector = 10
MsgBox v
[/rbcode]

The first line declares the Vector object (and it's allocated without new because of the conversion from the integer, as explained above), the second line uses the convert from syntax to convert a vector from an integer and the third line uses the convert to syntax to convert a vector to a string. You'll notice that in both forms of conversion the method name is the same, it's just the signature that changes.

Finally, there is one last operator to discuss, which is the "lookup" operator. This is a very powerful and very dangerous operator to overload. Whenever the compiler "looks up" the right-hand side of a . operator and cannot locate something on the class with that name, the Operator_Lookup method is called. This "looks up" the right hand side and returns a value for it. So let's do something really messy and define a lookup for the faked property named "Sucker":

[rbcode]
Function Operator_Lookup( partName as String ) as Double
if partName = "Sucker" then return sqrt( pow( self.x, 2 ) + pow( self.y, 2 ) )
End Function
[/rbcode]

What this code does is dangerous. It "defines" a .Sucker on the Vector class. So you can say this:
[rbcode]
Dim d as Double
d = myVector.Sucker
[/rbcode]

But it's worse than that! Since the compiler doesn't actually *execute* your code during compile time, it means that you've just defined a .XXX as Double where XXX is *any legal string*. That's right, you can write code like this:
[rbcode]
Dim d as Double
d = myVector.ThisDoesNotExist
[/rbcode]
And the compiler will not complain!! Furthermore, when the line of code executes, d will be 0.0 (the default return value)! So as you can see, messing with the Lookup operator is very dangerous to do. However, it's also very powerful (IIRC, this operator is what allows us easy access to Office Automation). I've never actually used this operator myself, and I would suggest you stay away from it as well.

Phew! That's every operator that I can think of. Hopefully this answers your questions. If you have more, just post them here and I'll answer them.

4 Comments

Dude, I didn't read this at all. I'm just letting you know that we're meeting at JPs at 8:00 on Thursday. You're coming out, bitch:)

I'll call Louie tomorrow. See you Thursday:)

Hi Aaron,

Thank you for your explanations of the operator overloading.

There's one thing I want to emphasize regarding Operator_Compare, as I got bitten the other day : when using a comparator to a class that has a Operator_Compare defined, you must be careful to place the class's instance on the _left_ side of the comparator sign, or the class-defined comparison will not be used.

e.g. :
Class1 has a Operator_Compare(v As Variant) As Integer defined.
Dim a As Class1
Dim b As Variant
a a !

This is because a a uses RB's basic comparison (based on the object's IDs ?)

Thinking of it, wouldn't it be nice if the compiler could evaluate both expressions in the same manner ?

I tried to create a reverse Operator_Compare(Extends v As Variant, a As Class1) As Integer, written as a global method of a module, but no luck, as you can't redefine operator for native types :-(

Sorry,

My previous posting came out wrong, as "inferior" and "superior" sign where not properly encoded. The example should read :

(a "inferior" b) is not the same as (b "superior" a) !

This is because (a "inferior" b) uses Class1.Operator_Compare, whereas (b "superior" a) uses RB’s basic comparison (based on the object’s IDs ?)

Is there way to make up a new operator all together? What I'm trying to do is to make a xor operator. Right now I use an extend on booleans. So I get something like foo = foo.xor(bar). Is there a way to get rid of all that and have simply foo = foo xor bar, short of you guys implementing it straight into RB? (hint hint!) :-)

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.