This is an old revision of the document!
Operator overloading enables defining how user-defined types behave when used with operators. In this context, the term overload means providing the definition of an operator for a specific type.
To better understand the use of operator overloading let us take an example:
struct Duration { int minute; } struct TimeOfDay { int hour; int minute; void increment(in Duration duration) { minute += duration.minute; hour += minute / 60; minute %= 60; hour %= 24; } } void main() { auto lunchTime = TimeOfDay(12, 0); lunchTime.increment(Duration(10)); }
A benefit of member functions is being able to define operations of a type alongside the member variables of that type. Despite their advantages, member functions can be seen as being limited compared to operations on fundamental types. After all, fundamental types can readily be used with operators:
int weight = 50; weight += 10; // by an operator
According to what we have seen so far, similar operations can only be achieved by member functions for user-defined types:
auto lunchTime = TimeOfDay(12, 0); lunchTime.increment(Duration(10)); // by a member function
Operator overloading enables using structs and classes with operators as well. For example, assuming that the += operator is defined for TimeOfDay , the operation above can be written in exactly the same way as with fundamental types:
lunchTime += Duration(10); // by an operator, even for a struct
Before getting to the details of operator overloading, let's first see how the line
above would be enabled for TimeOfDay . What is needed is to redefine the
increment()
member function under the special name opOpAssign(string
op) and also to specify that this definition is for the '+' character. As it will be
explained below, this definition actually corresponds to the '+=' operator:
struct TimeOfDay { // ... ref TimeOfDay opOpAssign(string op)(in Duration duration)//(1) if (op == "+") //(2) { minute += duration.minute; hour += minute / 60; minute %= 60; hour %= 24; return this; } }
The template definition consists of two parts:
Also note that this time the return type is different from the return type of the
increment()
member function: it is not void
anymore. We will discuss the
return types of operators later below.
Behind the scenes, the compiler replaces the uses of the +=
operator with calls
to the opOpAssign!”+“ member function:
lunchTime += Duration(10); // The following line is the equivalent of the previous one lunchTime.opOpAssign!"+"(Duration(10));
Note that the operator definition that corresponds to +=
is defined by +
, not
by +=
. The Assign in the name of opOpAssign() already implies that this
name is for an assignment operator.
Being able to define the behaviors of operators brings a responsibility: the
programmer must observe expectations. As an extreme example, the previous
operator could have been defined to decrement the time value instead of
incrementing it. However, people who read the code would still expect the value
to be incremented by the +=
operator.
To some extent, the return types of operators can also be chosen freely. Still, general expectations must be observed for the return types as well. Keep in mind that operators that behave unnaturally would cause confusion and bugs
For a list of all the operators that can be overloaded, check out this link.
To keep the code samples short, we have used only the +=
operator
above. It is conceivable that when one operator is overloaded for a type, many
others would also need to be overloaded. For example, the ++
, '+
, –
, -
and -=
operators are
also defined for the following Duration:
struct Duration { int minute; ref Duration opUnary(string op)() if (op == "++") { ++minute; return this; } ref Duration opUnary(string op)() if (op == "--") { --minute; return this; } ref Duration opOpAssign(string op)(in int amount) if (op == "+") { minute += amount; return this; } ref Duration opOpAssign(string op)(in int amount) if (op == "-") { minute -= amount; return this; } }
The operator overloads above have code duplications. The only differences between the similar functions are the operators that are used. Such code duplications can be reduced and sometimes avoided altogether by string mixins. We will see the mixin keyword in a later lab, but I would like to show briefly how this keyword helps with operator overloading. mixin inserts the specified string as source code right where the mixin statement appears in code. The following struct is the equivalent of the one above:
struct Duration { int minute; ref Duration opUnary(string op)() if ((op == "++") || (op == "--")) { mixin(op ~ "minute;"); return this; } ref Duration opOpAssign(string op)(in int amount) if ((op == "+") || (op == "-")) { mixin("minute " ~ op ~ "= amount;"); return this; } }
If the Duration objects also need to be multiplied and divided by an amount, all that is needed is to add two more conditions to the template constraint:
struct Duration { // ... ref Duration opOpAssign(string op)(in int amount) if ((op == "+") || (op == "-") || (op == "*") || (op == "/")) { mixin ("minute " ~ op ~ "= amount;"); return this; } }
In fact, the template constraints are optional:
ref Duration opOpAssign(string op)(in int amount) /* no constraint */ { mixin ("minute " ~ op ~ "= amount;"); return this; }