This is an old revision of the document!


Lab 04: Advanced D topics

Operator overloading

The topics covered in this chapter apply mostly for classes as well. The biggest difference is that the behavior of assignment operation opAssign() cannot be overloaded for classes.

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:

  1. opOpAssign(string op) : This part must be written as is and should be accepted as the name of the function. We will see below that there are other member functions in addition to opOpAssign .
  2. if (op == ”+” ) : opOpAssign is used for more than one operator overload. '”+”' specifies that this is the operator overload that corresponds to the '+' character.

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.

Defining more than one operator at the same time

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;
}
dss/laboratoare/04.1561114113.txt.gz · Last modified: 2019/06/21 13:48 by razvan.nitu1305
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0