Operator Overloading - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# 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 involves many concepts, some of which will be covered later in the book (templates, `auto ref`, etc.). For that reason, you may find this chapter to be harder to follow than the previous ones.

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.

We have seen how to define structs and their member functions in previous chapters. As an example, we have defined the `increment()` member function to be able to add `Duration` objects to `TimeOfDay` objects. Here are the two structs from previous chapters, with only the parts that are relevant to this chapter:

    struct Duration {
        int minute;
    }

    struct TimeOfDay {
        int hour;
        int minute;

        void increment(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.

The definition of this member function does not look like the ones that we have seen so far. That is because `opOpAssign` is actually a function template. Since we will see templates in much later chapters, I will have to ask you to accept the operator overloading syntax as is for now:

    struct TimeOfDay {
    // ...
        ref TimeOfDay opOpAssign(string op)(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. This syntax is a template constraint, which will also be covered in later chapters.

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));

The `!`"+"`` part that is after `opOpAssign` specifies that this call is for the definition of the operator for the `+` character. We will cover this template syntax in later chapters as well.

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.

##### Overloadable operators

There are different kinds of operators that can be overloaded.

###### Unary operators

An operator that takes a single operand is called a unary operator:

        ++weight;

`++` is a unary operator because it works on a single variable.

Unary operators are defined by member functions named `opUnary`. `opUnary` does not take any parameters because it uses only the object that the operator is being executed on.

The overloadable unary operators and the corresponding operator strings are the following:

Operator | Description | Operator String
--- | --- | ---
-object | negative of (numeric complement of) | "-"
+object | the same value as (or, a copy of) | "+"
~object | bitwise negation | "~"
object, access to what it points to, "" | object, access to what it points to, "" | object, access to what it points to, ""
++object | increment | "++"
--object | decrement | "--"

For example, the `++` operator for `Duration` can be defined like this:

    struct Duration {
        int minute;

        ref Duration opUnary(string op)()
                if (op == "++") {
            ++minute;
            return this;
        }
    }

Note that the return type of the operator is marked as `ref` here as well. This will be explained later below.

`Duration` objects can now be incremented by `++`:

        auto duration = Duration(20);
        ++duration;

The post-increment and post-decrement operators cannot be overloaded. The `object++` and `object--` uses are handled by the compiler automatically by saving the previous value of the object. For example, the compiler applies the equivalent of the following code for post-increment:

        /* The previous value is copied by the compiler
         * automatically: */
        Duration __previousValue__ = duration;

        /* The ++ operator is called: */
        ++duration;

        /* Then __previousValue__ is used as the value of the
         * post-increment operation. */

Unlike some other languages, the copy inside post-increment has no cost in D if the value of the post-increment expression is not actually used. This is because the compiler replaces such post-increment expressions with their pre-increment counterparts:

        /* The value of the expression is not used below. The
         * only effect of the expression is incrementing 'i'. */
        i++;

Because the previous value of `i` is not actually used above, the compiler replaces the expression with the following one:

        /* The expression that is actually used by the compiler: */
        ++i;

Additionally, if an `opBinary` overload supports the `duration += 1` usage, then `opUnary` need not be overloaded for `++duration` and `duration++`. Instead, the compiler uses the `duration += 1` expression behind the scenes. Similarly, the `duration -= 1` overload covers the uses of `--duration` and `duration--` as well.

###### Binary operators

An operator that takes two operands is called a binary operator:

        totalWeight = boxWeight + chocolateWeight;

The line above has two separate binary operators: the `+` operator, which adds the values of the two operands that are on its two sides, and the `=` operator that assigns the value of its right-hand operand to its left-hand operand.

The rightmost column below describes the category of each operator. The ones marked as "=" assign to the left-hand side object.

Operator | Description | Function name | Function name for right-hand side | Category
--- | --- | --- | --- | ---
+ | add | opBinary | opBinaryRight | arithmetic
- | subtract | opBinary | opBinaryRight | arithmetic
* | multiply | opBinary | opBinaryRight | arithmetic
/ | divide | opBinary | opBinaryRight | arithmetic
% | remainder of | opBinary | opBinaryRight | arithmetic
^^ | to the power of | opBinary | opBinaryRight | arithmetic
& | bitwise and | opBinary | opBinaryRight | bitwise
| | bitwise or | opBinary | opBinaryRight | bitwise
^ | bitwise xor | opBinary | opBinaryRight | bitwise
<< | left-shift | opBinary | opBinaryRight | bitwise
>> | right-shift | opBinary | opBinaryRight | bitwise
>>> | unsigned right-shift | opBinary | opBinaryRight | bitwise
~ | concatenate | opBinary | opBinaryRight |
in | whether contained in | opBinary | opBinaryRight |
== | whether equal to | opEquals | - | logical
!= | whether not equal to | opEquals | - | logical
< | whether before | opCmp | - | sorting
<= | whether not after | opCmp | - | sorting
> | whether after | opCmp | - | sorting
>= | whether not before | opCmp | - | sorting
= | assign | opAssign | - | =
+= | increment by | opOpAssign | - | =
-= | decrement by | opOpAssign | - | =
*= | multiply and assign | opOpAssign | - | =
/= | divide and assign | opOpAssign | - | =
%= | assign the remainder of | opOpAssign | - | =
^^= | assign the power of | opOpAssign | - | =
&= | assign the result of & | opOpAssign | - | =
|= | assign the result of | | opOpAssign | - | =
^= | assign the result of ^ | opOpAssign | - | =
<<= | assign the result of << | opOpAssign | - | =
>>= | assign the result of >> | opOpAssign | - | =
>>>= | assign the result of >>> | opOpAssign | - | =
~= | append | opOpAssign | - | =

`opBinaryRight` is for when the object can appear on the right-hand side of the operator. Let's assume a binary operator that we shall call op appears in the program:

        x op y

In order to determine what member function to call, the compiler considers the following two options:

        // the definition for x being on the left:
        x.opBinary!"op"(y);

        // the definition for y being on the right:
        y.opBinaryRight!"op"(x);

The compiler picks the option that is a better match than the other.

`opBinaryRight` is useful when defining arithmetic types that would normally work on both sides of an operator like e.g. `int` does:

        auto x = MyInt(42);
        x + 1;    // calls opBinary!"+"
        1 + x;    // calls opBinaryRight!"+"

Another common use of `opBinaryRight` is the `in` operator. It usually makes more sense to define `opBinaryRight` for the object that appears on the right-hand side of `in`. We will see an example of this below.

The parameter name `rhs` that appears in the following definitions is short for right-hand side. It denotes the operand that appears on the right-hand side of the operator:

        x op y

For the expression above, the `rhs` parameter would represent the variable `y`.

##### Element indexing and slicing operators

The following operators enable using a type as a collection of elements:

Description | Function Name | Sample Usage
--- | --- | ---
element access | opIndex | collection[i]
assignment to element | opIndexAssign | collection[i] = 7
unary operation on element | opIndexUnary | ++collection[i]
operation with assignment on element | opIndexOpAssign | collection[i] *= 2
number of elements | opDollar | collection[$ - 1]
slice of all elements | opSlice | collection[]
slice of some elements | opSlice(size_t, size_t) | collection[i..j]

We will cover those operators later below.

The following operator functions are from the earlier versions of D. They are discouraged:

Description | Function Name | Sample Usage
--- | --- | ---
unary operation on all elements | opSliceUnary (discouraged) | ++collection[]
unary operation on some elements | opSliceUnary (discouraged) | ++collection[i..j]
assignment to all elements | opSliceAssign (discouraged) | collection[] = 42
assignment to some elements | opSliceAssign (discouraged) | collection[i..j] = 7
operation with assignment on all elements | opSliceOpAssign (discouraged) | collection[] *= 2
operation with assignment on some elements | opSliceOpAssign (discouraged) | collection[i..j] *= 2

###### Other operators

The following operators can be overloaded as well:

Description | Function Name | Sample Usage
--- | --- | ---
function call | opCall | object(42)
type conversion | opCast | to!int(object)
dispatch for non-existent function | opDispatch | object.nonExistent()

These operators will be explained below under their own sections.

##### Defining more than one operator at the same time

To keep the code samples short, we have used only the `++`, `+`, and `+=` operators 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)(int amount)
                if (op == "+") {
            minute += amount;
            return this;
        }

        ref Duration opOpAssign(string op)(int amount)
                if (op == "-") {
            minute -= amount;
            return this;
        }
    }

    unittest {
        auto duration = Duration(10);

        ++duration;
        assert(duration.minute == 11);

        --duration;
        assert(duration.minute == 10);

        duration += 5;
        assert(duration.minute == 15);

        duration -= 3;
        assert(duration.minute == 12);
    }

    void main() {
    }

The operator overloads above have code duplications. The only differences between the similar functions are highlighted. Such code duplications can be reduced and sometimes avoided altogether by string mixins. We will see the `mixin` keyword in a later chapter as well. 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)(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)(int amount)
            if ((op == "+") || (op == "-") ||
                (op == "*") || (op == "/")) {
            mixin ("minute " ~ op ~ "= amount;");
            return this;
        }
    }

    unittest {
        auto duration = Duration(12);

        duration *= 4;
        assert(duration.minute == 48);

        duration /= 2;
        assert(duration.minute == 24);
    }

In fact, the template constraints are optional:

        ref Duration opOpAssign(string op)(int amount)
                /* no constraint */ {
            mixin ("minute " ~ op ~ "= amount;");
            return this;
        }

##### Return types of operators

When overloading an operator, it is advisable to observe the return type of the same operator on fundamental types. This would help with making sense of code and reducing confusions.

None of the operators on fundamental types return `void`. This fact should be obvious for some operators. For example, the result of adding two `int` values as `a + b` is `int`:

        int a = 1;
        int b = 2;
        int c = a + b;  // c gets initialized by the return value
                        // of the + operator

The return values of some other operators may not be so obvious. For example, even operators like `++i` have values:

        int i = 1;
        writeln(++i);    // prints 2

The `++` operator not only increments `i`, it also produces the new value of `i`. Further, the value that is produced by `++` is not just the new value of `i`, rather the variable `i` itself. We can see this fact by printing the address of the result of that expression:

        int i = 1;
        writeln("The address of i                : ", &i);
        writeln("The address of the result of ++i: ", &(++i));

The output contains identical addresses:

    The address of i                : 7FFF39BFEE78
    The address of the result of ++i: 7FFF39BFEE78

I recommend that you observe the following guidelines when overloading operators for your own types:

- Operators that modify the object

  With the exception of `opAssign`, it is recommended that the operators that modify the object return the object itself. This guideline has been observed above with the `TimeOfDay.opOpAssign!`"+"`` and `Duration.opUnary!`"++"``.

  The following two steps achieve returning the object itself:

  1. The return type is the type of the struct, marked by the `ref` keyword to mean reference.
  2. The function is exited by `return this` to mean return this object.

  The operators that modify the object are `opUnary!`"++"``, `opUnary!`"--"``, and all of the `opOpAssign` overloads.

- Logical operators

  `opEquals` that represents both `==` and `!=` must return `bool`. Although the `in` operator normally returns the contained object, it can simply return `bool` as well.

- Sort operators

  `opCmp` that represents `<`, `<=`, `>`, and `>=` must return `int`.

- Operators that make a new object

  Some operators must make and return a new object:

  - Unary operators `-`, `+`, and `~`; and the binary operator `~`.
  - Arithmetic operators `+`, `-`, `*`, `/`, `%`, and `^^`.
  - Bitwise operators `&`, `|`, `^`, `<<`, `>>`, and `>>>`.
  - As has been seen in the previous chapter, `opAssign` returns a copy of this object by `return this`.

    Note: As an optimization, sometimes it makes more sense for `opAssign` to return `const ref` for large structs. I will not apply this optimization in this book.

  As an example of an operator that makes a new object, let's define the `opBinary!`"+"`` overload for `Duration`. This operator should add two `Duration` objects to make and return a new one:

      struct Duration {
          int minute;

          Duration opBinary(string op)(Duration rhs) const
                  if (op == "+") {
              return Duration(minute + rhs.minute);  // new object
          }
      }

  That definition enables adding `Duration` objects by the `+` operator:

          auto travelDuration = Duration(10);
          auto returnDuration = Duration(11);
          Duration totalDuration;
          // ...
          totalDuration = travelDuration + returnDuration;

  The compiler replaces that expression with the following member function call on the `travelDuration` object:

          // the equivalent of the expression above:
          totalDuration =
              travelDuration.opBinary!"+"(returnDuration);

- `opDollar`

  Since it returns the number of elements of the container, the most suitable type for `opDollar` is `size_t`. However, the return type can be other types as well (e.g. `int`).

- Unconstrained operators

  The return types of some of the operators depend entirely on the design of the user-defined type: The unary `*`, `opCall`, `opCast`, `opDispatch`, `opSlice`, and all `opIndex` varieties.

##### `opEquals()` for equality comparisons

This member function defines the behaviors of the `==` and the `!=` operators.

The return type of `opEquals` is `bool`.

For structs, the parameter of `opEquals` can be defined as `in`. However, for speed efficiency `opEquals` can be defined as a template that takes `auto ref const` (also note the empty template parentheses below):

        bool opEquals()(auto ref const TimeOfDay rhs) const {
            // ...
        }

As we have seen in the Lvalues and Rvalues chapter, `auto ref` allows lvalues to be passed by reference and rvalues by copy. However, since rvalues are not copied, rather moved, the signature above is efficient for both lvalues and rvalues.

To reduce confusion, `opEquals` and `opCmp` must work consistently. For two objects that `opEquals` returns `true`, `opCmp` must return zero.

Once `opEquals()` is defined for equality, the compiler uses its opposite for inequality:

        x == y;
        // the equivalent of the previous expression:
        x.opEquals(y);

        x != y;
        // the equivalent of the previous expression:
        !(x.opEquals(y));

Normally, it is not necessary to define `opEquals()` for structs. The compiler generates it for structs automatically. The automatically-generated `opEquals` compares all of the members individually.

Sometimes the equality of two objects must be defined differently from this automatic behavior. For example, some of the members may not be significant in this comparison, or the equality may depend on a more complex logic.

Just as an example, let's define `opEquals()` in a way that disregards the minute information altogether:

    struct TimeOfDay {
        int hour;
        int minute;

        bool opEquals(TimeOfDay rhs) const {
            return hour == rhs.hour;
        }
    }
    // ...
        assert(TimeOfDay(20, 10) == TimeOfDay(20, 59));

Since the equality comparison considers the values of only the `hour` members, 20:10 and 20:59 end up being equal. (This is just an example; it should be clear that such an equality comparison would cause confusions.)

##### `opCmp()` for sorting

Sort operators determine the sort orders of objects. All of the ordering operators `<`, `<=`, `>`, and `>=` are covered by the `opCmp()` member function.

For structs, the parameter of `opCmp` can be defined as `in`. However, as with `opEquals`, it is more efficient to define `opCmp` as a template that takes `auto ref const`:

        int opCmp()(auto ref const TimeOfDay rhs) const {
            // ...
        }

To reduce confusion, `opEquals` and `opCmp` must work consistently. For two objects that `opEquals` returns `true`, `opCmp` must return zero.

Let's assume that one of these four operators is used as in the following code:

        if (x op y) {  // ← op is one of <, <=, >, or >=

The compiler converts that expression to the following logical expression and uses the result of the new logical expression:

        if (x.opCmp(y) op 0) {

Let's consider the `<=` operator:

        if (x <= y) {

The compiler generates the following code behind the scenes:

        if (x.opCmp(y) <= 0) {

For the user-defined `opCmp()` to work correctly, this member function must return a result according to the following rules:

- A negative value if the left-hand object is considered to be before the right-hand object
- A positive value if the left-hand object is considered to be after the right-hand object
- Zero if the objects are considered to have the same sort order

To be able to support those values, the return type of `opCmp()` must be `int`, not `bool`.

The following is a way of ordering `TimeOfDay` objects by first comparing the values of the `hour` members, and then comparing the values of the `minute` members (only if the `hour` members are equal):

        int opCmp(TimeOfDay rhs) const {
            /* Note: Subtraction is a bug here if the result can
             * overflow. (See the following warning in text.) */

            return (hour == rhs.hour
                    ? minute - rhs.minute
                    : hour - rhs.hour);
        }

That definition returns the difference between the `minute` values when the `hour` members are the same, and the difference between the `hour` members otherwise. The return value would be a negative value when the left-hand object comes before in chronological order, a positive value if the right-hand object is before, and zero when they represent exactly the same time of day.

Warning: Using subtraction for the implementation of `opCmp` is a bug if valid values of a member can cause overflow. For example, the two objects below would be sorted incorrectly as the object with value `-2` is calculated to be greater than the one with value `int.max`:

    struct S {
        int i;

        int opCmp(S rhs) const {
            return i - rhs.i;          // ← BUG
        }
    }

    void main() {
        assert(S(-2) > S(int.max));    // ← wrong sort order
    }

On the other hand, subtraction is acceptable for `TimeOfDay` because none of the valid values of the members of that `struct` can cause overflow in subtraction.

You can use `std.algorithm.cmp` for comparing slices (including all string types and ranges). `cmp()` compares slices lexicographically and produces a negative value, zero, or positive value depending on their order. That result can directly be used as the return value of `opCmp`:

    import std.algorithm;

    struct S {
        string name;

        int opCmp(S rhs) const {
            return cmp(name, rhs.name);
        }
    }

Once `opCmp()` is defined, this type can be used with sorting algorithms like `std.algorithm.sort` as well. As `sort()` works on the elements, it is the `opCmp()` operator that gets called behind the scenes to determine their order. The following program constructs 10 objects with random values and sorts them with `sort()`:

    import std.random;
    import std.stdio;
    import std.string;
    import std.algorithm;

    struct TimeOfDay {
        int hour;
        int minute;

        int opCmp(TimeOfDay rhs) const {
            return (hour == rhs.hour
                    ? minute - rhs.minute
                    : hour - rhs.hour);
        }

        string toString() const {
            return format("%02s:%02s", hour, minute);
        }
    }

    void main() {
        TimeOfDay[] times;

        foreach (i; 0 .. 10) {
            times ~= TimeOfDay(uniform(0, 24), uniform(0, 60));
        }

        sort(times);

        writeln(times);
    }

As expected, the elements are sorted from the earliest time to the latest time:

    [03:40, 04:10, 09:06, 10:03, 10:09, 11:04, 13:42, 16:40, 18:03, 21:08]

##### `opCall()` to call objects as functions

The parentheses around the parameter list when calling functions are operators as well. We have already seen how `static opCall()` makes it possible to use the name of the type as a function. `static opCall()` allows creating objects with default values at run time.

Non-static `opCall()` on the other hand allows using the objects of user-defined types as functions:

        Foo foo;
        foo();

The object `foo` above is being called like a function.

As an example, let's consider a `struct` that represents a linear equation. This `struct` will be used for calculating the y values of the following linear equation for specific x values:

        y = ax + b

The following `opCall()` simply calculates and returns the value of y according to that equation:

    struct LinearEquation {
        double a;
        double b;

        double opCall(double x) const {
            return a * x + b;
        }
    }

With that definition, each object of `LinearEquation` represents a linear equation for specific a and b values. Such an object can be used as a function that calculates the y values:

        LinearEquation equation = { 1.2, 3.4 };
        // the object is being used like a function:
        double y = equation(5.6);

Note: Defining `opCall()` for a `struct` disables the compiler-generated automatic constructor. That is why the `{ }` syntax is used above instead of the recommended `LinearEquation(1.2, 3.4)`. When the latter syntax is desired, a `static opCall()` that takes two `double` parameters must also be defined.

`equation` above represents the y = 1.2x + 3.4 linear equation. Using that object as a function executes the `opCall()` member function.

This feature can be useful to define and store the a and b values in an object once and to use that object multiple times later on. The following code uses such an object in a loop:

        LinearEquation equation = { 0.01, 0.4 };

        for (double x = 0.0; x <= 1.0; x += 0.125) {
            writefln("%f: %f", x, equation(x));
        }

That object represents the y = 0.01x + 0.4 equation. It is being used for calculating the results for x values in the range from 0.0 to 1.0.

##### Indexing operators

`opIndex`, `opIndexAssign`, `opIndexUnary`, `opIndexOpAssign`, and `opDollar` make it possible to use indexing operators on user-defined types similar to arrays as in `object[index]`.

Unlike arrays, these operators support multi-dimensional indexing as well. Multiple index values are specified as a comma-separated list inside the square brackets (e.g. `object[index0, index1]`). In the following examples we will use these operators only with a single dimension and cover their multi-dimensional uses in the More Templates chapter.

The `deque` variable in the following examples is an object of `struct DoubleEndedQueue`, which we will define below; and `e` is a variable of type `int`.

`opIndex` is for element access. The index that is specified inside the brackets becomes the parameter of the operator function:

        e = deque[3];                    // the element at index 3
        e = deque.opIndex(3);            // the equivalent of the above

`opIndexAssign` is for assigning to an element. The first parameter is the value that is being assigned and the second parameter is the index of the element:

        deque[5] = 55;                   // assign 55 to the element at index 5
        deque.opIndexAssign(55, 5);      // the equivalent of the above

`opIndexUnary` is similar to `opUnary`. The difference is that the operation is applied to the element at the specified index:

        ++deque[4];                      // increment the element at index 4
        deque.opIndexUnary!"++"(4);      // the equivalent of the above

`opIndexOpAssign` is similar to `opOpAssign`. The difference is that the operation is applied to an element:

        deque[6] += 66;                  // add 66 to the element at index 6
        deque.opIndexOpAssign!"+"(66, 6);// the equivalent of the above

`opDollar` defines the `$` character that is used during indexing and slicing. It is for returning the number of elements in the container:

        e = deque[$ - 1];                // the last element
        e = deque[deque.opDollar() - 1]; // the equivalent of the above

###### Indexing operators example

Double-ended queue is a data structure that is similar to arrays but it provides efficient insertion at the head of the collection as well. (In contrast, inserting at the head of an array is a relatively slow operation as it requires moving the existing elements to a newly created array.)

One way of implementing a double-ended queue is to use two arrays in the background but to use the first one in reverse. The element that is conceptually inserted at the head of the queue is actually appended to the head array. As a result, this operation is as efficient as appending to the end.

The following `struct` implements a double-ended queue that overloads the operators that we have seen in this section:

    import std.stdio;
    import std.string;
    import std.conv;

    struct DoubleEndedQueue // Also known as Deque
    {
    private:

        /* The elements are represented as the chaining of the two
         * member slices. However, 'head' is indexed in reverse so
         * that the first element of the entire collection is
         * head[$-1], the second one is head[$-2], etc.:
         *
         * head[$-1], head[$-2], ... head[0], tail[0], ... tail[$-1]
         */
        int[] head;    // the first group of elements
        int[] tail;    // the second group of elements

        /* Determines the actual slice that the specified element
         * resides in and returns it as a reference. */
        ref inout(int) elementAt(size_t index) inout {
            return (index < head.length
                    ? head[$ - 1 - index]
                    : tail[index - head.length]);
        }

    public:

        string toString() const {
            string result;

            foreach_reverse (element; head) {
                result ~= format("%s ", to!string(element));
            }

            foreach (element; tail) {
                result ~= format("%s ", to!string(element));
            }

            return result;
        }

        /* Note: As we will see in the next chapter, the following
         * is a simpler and more efficient implementation of
         * toString(): */
        version (none) {
            void toString(void delegate(const(char)[]) sink) const {
                import std.format;
                import std.range;

                formattedWrite(
                    sink, "%(%s %)", chain(head.retro, tail));
            }
        }

        /* Adds a new element to the head of the collection. */
        void insertAtHead(int value) {
            head ~= value;
        }

        /* Adds a new element to the tail of the collection.
         *
         * Sample: deque ~= value
         */
        ref DoubleEndedQueue opOpAssign(string op)(int value)
                if (op == "~") {
            tail ~= value;
            return this;
        }

        /* Returns the specified element.
         *
         * Sample: deque[index]
         */
        inout(int) opIndex(size_t index) inout {
            return elementAt(index);
        }

        /* Applies a unary operation to the specified element.
         *
         * Sample: ++deque[index]
         */
        int opIndexUnary(string op)(size_t index) {
            mixin ("return " ~ op ~ "elementAt(index);");
        }

        /* Assigns a value to the specified element.
         *
         * Sample: deque[index] = value
         */
        int opIndexAssign(int value, size_t index) {
            return elementAt(index) = value;
        }

        /* Uses the specified element and a value in a binary
         * operation and assigns the result back to the same
         * element.
         *
         * Sample: deque[index] += value
         */
        int opIndexOpAssign(string op)(int value, size_t index) {
            mixin ("return elementAt(index) " ~ op ~ "= value;");
        }

        /* Defines the $ character, which is the length of the
         * collection.
         *
         * Sample: deque[$ - 1]
         */
        size_t opDollar() const {
            return head.length + tail.length;
        }
    }

    void main() {
        auto deque = DoubleEndedQueue();

        foreach (i; 0 .. 10) {
            if (i % 2) {
                deque.insertAtHead(i);

            } else {
                deque ~= i;
            }
        }

        writefln("Element at index 3: %s",
                 deque[3]);    // accessing an element
        ++deque[4];            // incrementing an element
        deque[5] = 55;         // assigning to an element
        deque[6] += 66;        // adding to an element

        (deque ~= 100) ~= 200;

        writeln(deque);
    }

According to the guidelines above, the return type of `opOpAssign` is `ref` so that the `~=` operator can be chained on the same collection:

        (deque ~= 100) ~= 200;

As a result, both 100 and 200 get appended to the same collection:

    Element at index 3: 3
    9 7 5 3 2 55 68 4 6 8 100 200

##### Slicing operators

`opSlice` allows slicing the objects of user-defined types with the `[]` operator.

In addition to this operator, there are also `opSliceUnary`, `opSliceAssign`, and `opSliceOpAssign` but they are discouraged.

D supports multi-dimensional slicing. We will see a multi-dimensional example later in the More Templates chapter. Although the methods described in that chapter can be used for a single dimension as well, they do not match the indexing operators that are defined above and they involve templates which we have not covered yet. For that reason, we will see the non-templated use of `opSlice` in this chapter, which works only with a single dimension. (This use of `opSlice` is discouraged as well.)

`opSlice` has two distinct forms:

- The square brackets can be empty as in `deque[]` to mean all elements.
- The square brackets can contain a number range as in `deque[begin..end]` to mean the elements in the specified range.

The slicing operators are relatively more complex than other operators because they involve two distinct concepts: container and range. We will see these concepts in more detail in later chapters.

In single-dimensional slicing which does not use templates, `opSlice` returns an object that represents a specific range of elements of the container. The object that `opSlice` returns is responsible for defining the operations that are applied on that range elements. For example, behind the scenes the following expression is executed by first calling `opSlice` to obtain a range object and then applying `opOpAssign!`"*"`` on that object:

        deque[] *= 10;    // multiply all elements by 10

        // The equivalent of the above:
        {
            auto range = deque.opSlice();
            range.opOpAssign!"*"(10);
        }

Accordingly, the `opSlice` operators of `DoubleEndedQueue` return a special `Range` object so that the operations are applied to it:

    import std.exception;

    struct DoubleEndedQueue {
    // ...

        /* Returns a range that represents all of the elements.
         * ('Range' struct is defined below.)
         *
         * Sample: deque[]
         */
        inout(Range) opSlice() inout {
            return inout(Range)(head[], tail[]);
        }

        /* Returns a range that represents some of the elements.
         *
         * Sample: deque[begin .. end]
         */
        inout(Range) opSlice(size_t begin, size_t end) inout {
            enforce(end <= opDollar());
            enforce(begin <= end);

            /* Determine what parts of 'head' and 'tail'
             * correspond to the specified range: */

            if (begin < head.length) {
                if (end < head.length) {
                    /* The range is completely inside 'head'. */
                    return inout(Range)(
                        head[$ - end .. $ - begin],
                        []);

                } else {
                    /* Some part of the range is inside 'head' and
                     * the rest is inside 'tail'. */
                    return inout(Range)(
                        head[0 .. $ - begin],
                        tail[0 .. end - head.length]);
                }

            } else {
                /* The range is completely inside 'tail'. */
                return inout(Range)(
                    [],
                    tail[begin - head.length .. end - head.length]);
            }
        }

        /* Represents a range of elements of the collection. This
         * struct is responsible for defining the opUnary,
         * opAssign, and opOpAssign operators. */
        struct Range {
            int[] headRange;    // elements that are in 'head'
            int[] tailRange;    // elements that are in 'tail'

            /* Applies the unary operation to the elements of the
             * range. */
            Range opUnary(string op)() {
                mixin (op ~ "headRange[];");
                mixin (op ~ "tailRange[];");
                return this;
            }

            /* Assigns the specified value to each element of the
             * range. */
            Range opAssign(int value) {
                headRange[] = value;
                tailRange[] = value;
                return this;
            }

            /* Uses each element and a value in a binary operation
             * and assigns the result back to that element. */
            Range opOpAssign(string op)(int value) {
                mixin ("headRange[] " ~ op ~ "= value;");
                mixin ("tailRange[] " ~ op ~ "= value;");
                return this;
            }
        }
    }

    void main() {
        auto deque = DoubleEndedQueue();

        foreach (i; 0 .. 10) {
            if (i % 2) {
                deque.insertAtHead(i);

            } else {
                deque ~= i;
            }
        }

        writeln(deque);
        deque[] *= 10;
        deque[3 .. 7] = -1;
        writeln(deque);
    }

The output:

    9 7 5 3 1 0 2 4 6 8
    90 70 50 -1 -1 -1 -1 40 60 80

##### `opCast` for type conversions

`opCast` defines explicit type conversions. It can be overloaded separately for each target type. As you would remember from the earlier chapters, explicit type conversions are performed by the `to` function and the `cast` operator.

`opCast` is a template as well, but it has a different format: The target type is specified by the `(T : target_type)` syntax:

        target_type opCast(T : target_type)() {
            // ...
        }

This syntax will become clear later after the templates chapter as well.

Let's change the definition of `Duration` so that it now has two members: hours and minutes. The operator that converts objects of this type to `double` can be defined as in the following code:

    import std.stdio;
    import std.conv;

    struct Duration {
        int hour;
        int minute;

        double opCast(T : double)() const {
            return hour + (to!double(minute) / 60);
        }
    }

    void main() {
        auto duration = Duration(2, 30);
        double d = to!double(duration);
        // (could be 'cast(double)duration' as well)

        writeln(d);
    }

The compiler replaces the type conversion call above with the following one:

        double d = duration.opCast!double();

The `double` conversion function above produces 2.5 for two hours and thirty minutes:

    2.5

Although `opCast` is for explicit type conversions, its `bool` specialization is called automatically when the variable is used in a logical expression:

    struct Duration {
    // ...

        bool opCast(T : bool)() const {
            return (hour != 0) || (minute != 0);
        }
    }

    // ...

        if (duration) {               // compiles
            // ...
        }

        while (duration) {            // compiles
            // ...
        }

        auto r = duration ? 1 : 2;    // compiles

Still, the `bool` specialization of `opCast` is not for all implicit `bool` conversions:

    void foo(bool b) {
        // ...
    }

    // ...

        foo(duration);                // ← derleme HATASI
        bool b = duration;            // ← derleme HATASI

    Error: cannot implicitly convert expression (duration) of type Duration to
    bool
    Error: function deneme.foo (bool b) is not callable using argument types
    (Duration)

##### Catch-all operator `opDispatch`

`opDispatch` gets called whenever a missing member of an object is accessed. All attempts to access non-existent members are dispatched to this function.

The name of the missing member becomes the template parameter value of `opDispatch`.

The following code demonstrates a simple definition:

    import std.stdio;

    struct Foo {
        void opDispatch(string name, T)(T parameter) {
            writefln("Foo.opDispatch - name: %s, value: %s",
                     name, parameter);
        }
    }

    void main() {
        Foo foo;
        foo.aNonExistentFunction(42);
        foo.anotherNonExistentFunction(100);
    }

There are no compiler errors for the calls to non-existent members. Instead, all of those calls are dispatched to `opDispatch`. The first template parameter is the name of the member. The parameter values that are used when calling the function appear as the parameters of `opDispatch`:

    Foo.opDispatch - name: aNonExistentFunction, value: 42
    Foo.opDispatch - name: anotherNonExistentFunction, value: 100

The `name` template parameter can be used inside the function to make decisions on how the call to that specific non-existent function should be handled:

       switch (name) {
           // ...
       }

##### Inclusion query by `opBinaryRight!"in"`

This operator allows defining the behavior of the `in` operator for user-defined types. `in` is commonly used with associative arrays to determine whether a value for a specific key exists in the array.

Different from other operators, this operator is normally overloaded for the case where the object appears on the right-hand side:

            if (time in lunchBreak) {

The compiler would use `opBinaryRight` behind the scenes:

            // the equivalent of the above:
            if (lunchBreak.opBinaryRight!"in"(time)) {

There is also `!in` to determine whether a value for a specific key does not exist in the array:

            if (a !in b) {

`!in` cannot be overloaded because the compiler uses the negative of the result of the `in` operator instead:

            if (!(a in b)) {    // the equivalent of the above

###### Example of the `in` operator

The following program defines a `TimeSpan` type in addition to `Duration` and `TimeOfDay`. The `in` operator that is defined for `TimeSpan` determines whether a moment in time is within that time span.

To keep the code short, the following program defines only the necessary member functions.

Note how the `TimeOfDay` object is used seamlessly in the `for` loop. That loop is a demonstration of how useful operator overloading can be.

    import std.stdio;
    import std.string;

    struct Duration {
        int minute;
    }

    struct TimeOfDay {
        int hour;
        int minute;

        ref TimeOfDay opOpAssign(string op)(Duration duration)
                if (op == "+") {
            minute += duration.minute;

            hour += minute / 60;
            minute %= 60;
            hour %= 24;

            return this;
        }

        int opCmp(TimeOfDay rhs) const {
            return (hour == rhs.hour
                    ? minute - rhs.minute
                    : hour - rhs.hour);
        }

        string toString() const {
            return format("%02s:%02s", hour, minute);
        }
    }

    struct TimeSpan {
        TimeOfDay begin;
        TimeOfDay end;    // end is outside of the span

        bool opBinaryRight(string op)(TimeOfDay time) const
                if (op == "in") {
            return (time >= begin) && (time < end);
        }
    }

    void main() {
        auto lunchBreak = TimeSpan(TimeOfDay(12, 00),
                                   TimeOfDay(13, 00));

        for (auto time = TimeOfDay(11, 30);
             time < TimeOfDay(13, 30);
             time += Duration(15)) {

            if (time in lunchBreak) {
                writeln(time, " is during the lunch break");

            } else {
                writeln(time, " is outside of the lunch break");
            }
        }
    }

The output:

    11:30 is outside of the lunch break
    11:45 is outside of the lunch break
    12:00 is during the lunch break
    12:15 is during the lunch break
    12:30 is during the lunch break
    12:45 is during the lunch break
    13:00 is outside of the lunch break
    13:15 is outside of the lunch break

##### Problem

Define a fraction type that stores its numerator and denominator as members of type `long`. Such a type may be useful because it does not lose value like `float`, `double`, and `real` do due to their precisions. For example, although the result of multiplying a `double` value of 1.0/3 by 3 is not 1.0, multiplying a `Fraction` object that represents the fraction 1/3 by 3 would be exactly 1:

    struct Fraction {
        long num;  // numerator
        long den;  // denominator

        /* As a convenience, the constructor uses the default
         * value of 1 for the denominator. */
        this(long num, long den = 1) {
            enforce(den != 0, "The denominator cannot be zero");

            this.num = num;
            this.den = den;

            /* Ensuring that the denominator is always positive
             * will simplify the definitions of some of the
             * operator functions. */
            if (this.den < 0) {
                this.num = -this.num;
                this.den = -this.den;
            }
        }

        /* ... you define the operator overloads ... */
    }

Define operators as needed for this type to make it a convenient type as close to fundamental types as possible. Ensure that the definition of the type passes all of the following unit tests. The unit tests ensure the following behaviors:

- An exception must be thrown when constructing an object with zero denominator. (This is already taken care of by the `enforce` expression above.)
- Producing the negative of the value: For example, the negative of 1/3 should be -1/3 and negative of -2/5 should be 2/5.
- Incrementing and decrementing the value by `++` and `--`.
- Support for four arithmetic operations: Both modifying the value of an object by `+=`, `-=`, `*=`, and `/=`; and producing the result of using two objects with the `+`, `-`, `*`, and `/` operators. (Similar to the constructor, dividing by zero should be prevented.)

  As a reminder, here are the formulas of arithmetic operations that involve two fractions a/b and c/d:

  - Addition: a/b + c/d = (a*d + cb/(b)d)
  - Subtraction: a/b - c/d = (a*d - cb/(b)d)
  - Multiplication: a/b * c/d = (ac/(b)d)
  - Division: (a/b) / (c/d) = (ad/(b)c)
- The actual (and necessarily lossful) value of the object can be converted to `double`.
- Sort order and equality comparisons are performed by the actual values of the fractions, not by the values of the numerators and denominators. For example, the fractions 1/3 and 20/60 must be considered to be equal.

    unittest {
        /* Must throw when denominator is zero. */
        assertThrown(Fraction(42, 0));

        /* Let's start with 1/3. */
        auto a = Fraction(1, 3);

        /* -1/3 */
        assert(-a == Fraction(-1, 3));

        /* 1/3 + 1 == 4/3 */
        ++a;
        assert(a == Fraction(4, 3));

        /* 4/3 - 1 == 1/3 */
        --a;
        assert(a == Fraction(1, 3));

        /* 1/3 + 2/3 == 3/3 */
        a += Fraction(2, 3);
        assert(a == Fraction(1));

        /* 3/3 - 2/3 == 1/3 */
        a -= Fraction(2, 3);
        assert(a == Fraction(1, 3));

        /* 1/3 * 8 == 8/3 */
        a *= Fraction(8);
        assert(a == Fraction(8, 3));

        /* 8/3 / 16/9 == 3/2 */
        a /= Fraction(16, 9);
        assert(a == Fraction(3, 2));

        /* Must produce the equivalent value in type 'double'.
         *
         * Note that although double cannot represent every value
         * precisely, 1.5 is an exception. That is why this test
         * is being applied at this point. */
        assert(to!double(a) == 1.5);

        /* 1.5 + 2.5 == 4 */
        assert(a + Fraction(5, 2) == Fraction(4, 1));

        /* 1.5 - 0.75 == 0.75 */
        assert(a - Fraction(3, 4) == Fraction(3, 4));

        /* 1.5 * 10 == 15 */
        assert(a * Fraction(10) == Fraction(15, 1));

        /* 1.5 / 4 == 3/8 */
        assert(a / Fraction(4) == Fraction(3, 8));

        /* Must throw when dividing by zero. */
        assertThrown(Fraction(42, 1) / Fraction(0));

        /* The one with lower numerator is before. */
        assert(Fraction(3, 5) < Fraction(4, 5));

        /* The one with larger denominator is before. */
        assert(Fraction(3, 9) < Fraction(3, 8));
        assert(Fraction(1, 1_000) > Fraction(1, 10_000));

        /* The one with lower value is before. */
        assert(Fraction(10, 100) < Fraction(1, 2));

        /* The one with negative value is before. */
        assert(Fraction(-1, 2) < Fraction(0));
        assert(Fraction(1, -2) < Fraction(0));

        /* The ones with equal values must be both <= and >=. */
        assert(Fraction(-1, -2) <= Fraction(1, 2));
        assert(Fraction(1, 2) <= Fraction(-1, -2));
        assert(Fraction(3, 7) <= Fraction(9, 21));
        assert(Fraction(3, 7) >= Fraction(9, 21));

        /* The ones with equal values must be equal. */
        assert(Fraction(1, 3) == Fraction(20, 60));

        /* The ones with equal values with sign must be equal. */
        assert(Fraction(-1, 2) == Fraction(1, -2));
        assert(Fraction(1, 2) == Fraction(-1, -2));
    }

... çözüm

Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 5 11:24:19 2026