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 local clone. Page wiki View or edit the community-maintained wiki page associated with this page.

Operator Overloading

Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named member functions. No additional syntax is used.

Unary Operator Overloading

Overloadable Unary Operators
oprewrite
-e e.opUnary!("-")()
+e e.opUnary!("+")()
~e e.opUnary!("~")()
*e e.opUnary!("*")()
++e e.opUnary!("++")()
--e e.opUnary!("--")()

For example, in order to overload the - (negation) operator for struct S, and no other operator:

struct S {
  int m;

  int opUnary(string s)() if (s == "-") {
    return -m;
  }
}

int foo(S s) {
  return -s;
}

Postincrement e++ and Postdecrement e-- Operators

These are not directly overloadable, but instead are rewritten in terms of the ++e and --e prefix operators:

Postfix Operator Rewrites
oprewrite
e-- (auto t = e, --e, t)
e++ (auto t = e, ++e, t)

Overloading Index Unary Operators

Overloadable Index Unary Operators
oprewrite
-a[b1, b2, ... bn] a.opIndexUnary!("-")(b1, b2, ... bn)
+a[b1, b2, ... bn] a.opIndexUnary!("+")(b1, b2, ... bn)
~a[b1, b2, ... bn] a.opIndexUnary!("~")(b1, b2, ... bn)
*a[b1, b2, ... bn] a.opIndexUnary!("*")(b1, b2, ... bn)
++a[b1, b2, ... bn] a.opIndexUnary!("++")(b1, b2, ... bn)
--a[b1, b2, ... bn] a.opIndexUnary!("--")(b1, b2, ... bn)

Overloading Slice Unary Operators

Overloadable Slice Unary Operators
oprewrite
-a[i..j] a.opSliceUnary!("-")(i, j)
+a[i..j] a.opSliceUnary!("+")(i, j)
~a[i..j] a.opSliceUnary!("~")(i, j)
*a[i..j] a.opSliceUnary!("*")(i, j)
++a[i..j] a.opSliceUnary!("++")(i, j)
--a[i..j] a.opSliceUnary!("--")(i, j)
-a[ ] a.opSliceUnary!("-")()
+a[ ] a.opSliceUnary!("+")()
~a[ ] a.opSliceUnary!("~")()
*a[ ] a.opSliceUnary!("*")()
++a[ ] a.opSliceUnary!("++")()
--a[ ] a.opSliceUnary!("--")()

Cast Operator Overloading

Cast Operators
oprewrite
cast(type e) e.opCast!(type)()

Boolean Operations

Notably absent from the list of overloaded unary operators is the ! logical negation operator. More obscurely absent is a unary operator to convert to a bool result. Instead, these are covered by a rewrite to:

opCast!(bool)(e)

So,

if (e)   =>  if (e.opCast!(bool))
if (!e)  =>  if (!e.opCast!(bool))

etc., whenever a bool result is expected. This only happens, however, for instances of structs. Class references are converted to bool by checking to see if the class reference is null or not.

Binary Operator Overloading

The following binary operators are overloadable:

Overloadable Binary Operators
+-*/%^^&
|^<<>>>>>~in

The expression:

a op b

is rewritten as both:

a.opBinary!("$(METACODE op)")(b)
b.opBinaryRight!("$(METACODE op)")(a)

and the one with the ‘better’ match is selected. It is an error for both to equally match.

Operator overloading for a number of operators can be done at the same time. For example, if only the + or - operators are supported:

T opBinary(string op)(T rhs) {
   static if (op == "+") return data + rhs.data;
   else static if (op == "-") return data - rhs.data;
   else static assert(0, "Operator "~op~" not implemented");
}

To do them all en masse:

T opBinary(string op)(T rhs) {
     return mixin("data "~op~" rhs.data");
}

Overloading the Comparison Operators

D allows overloading of the comparison operators ==, !=, <, <=, >=, > via two functions, opEquals and opCmp.

The equality and inequality operators are treated separately because while practically all user-defined types can be compared for equality, only a subset of types have a meaningful ordering. For example, while it makes sense to determine if two RGB color vectors are equal, it is not meaningful to say that one color is greater than another, because colors do not have an ordering. Thus, one would define opEquals for a Color type, but not opCmp.

Furthermore, even with orderable types, the order relation may not be linear. For example, one may define an ordering on sets via the subset relation, such that x < y is true if x is a (strict) subset of y. If x and y are disjoint sets, then neither x < y nor y < x holds, but that does not imply that x == y. Thus, it is insufficient to determine equality purely based on opCmp alone. For this reason, opCmp is only used for the inequality operators <, <=, >=, and >. The equality operators == and != always employ opEquals instead.

Therefore, it is the programmer's responsibility to ensure that opCmp and opEquals are consistent with each other. If opEquals is not specified, the compiler provides a default version that does member-wise comparison. If this suffices, one may define only opCmp to customize the behaviour of the inequality operators. But if not, then a custom version of opEquals should be defined as well, in order to preserve consistent semantics between the two kinds of comparison operators.

Finally, if the user-defined type is to be used as a key in the built-in associative arrays, then the programmer must ensure that the semantics of opEquals and toHash are consistent. If not, the associative array may not work in the expected manner.

Overloading == and !=

Expressions of the form a != b are rewritten as !(a == b).

Given a == b :

  1. If a and b are both class objects, then the expression is rewritten as:
    .object.opEquals(a, b)
    

    and that function is implemented as:

    bool opEquals(Object a, Object b) {
      if (a is b) return true;
      if (a is null || b is null) return false;
      if (typeid(a) == typeid(b)) return a.opEquals(b);
      return a.opEquals(b) && b.opEquals(a);
    }
    
  2. Otherwise the expressions a.opEquals(b) and b.opEquals(a) are tried. If both resolve to the same opEquals function, then the expression is rewritten to be a.opEquals(b).
  3. If one is a better match than the other, or one compiles and the other does not, the first is selected.
  4. Otherwise, an error results.

If overridding Object.opEquals() for classes, the class member function signature should look like:

class C {
  override bool opEquals(Object o) { ... }
}

If structs declare an opEquals member function for the identity comparison, it could have several forms, such as:

struct S {
  // lhs should be mutable object
  bool opEquals(const S s) { ... }        // for r-values (e.g. temporaries)
  bool opEquals(ref const S s) { ... }    // for l-values (e.g. variables)

  // both hand side can be const object
  bool opEquals(const S s) const { ... }  // for r-values (e.g. temporaries)
}

Alternatively, you can declare a single templated opEquals function with an auto ref parameter:

struct S {
  // for l-values and r-values,
  // with converting both hand side implicitly to const
  bool opEquals()(auto ref const S s) const { ... }
}

Overloading <, <=, >, and >=

Comparison operations are rewritten as follows:

Overloadable Unary Operators
comparisonrewrite 1rewrite 2
a < ba.opCmp(b) < 0b.opCmp(a) > 0
a <= ba.opCmp(b) <= 0b.opCmp(a) >= 0
a >ba.opCmp(b) > 0b.opCmp(a) < 0
a >= ba.opCmp(b) >= 0b.opCmp(a) <= 0

Both rewrites are tried. If only one compiles, that one is taken. If they both resolve to the same function, the first rewrite is done. If they resolve to different functions, the best matching one is used. If they both match the same, but are different functions, an ambiguity error results.

If overriding Object.opCmp() for classes, the class member function signature should look like:

class C {
  override int opCmp(Object o) { ... }
}

If structs declare an opCmp member function, it should have the following form:

struct S {
  int opCmp(ref const S s) const { ... }
}

Note that opCmp is only used for the inequality operators; expressions like a == b always uses opEquals. If opCmp is defined but opEquals isn't, the compiler will supply a default version of opEquals that performs member-wise comparison. If this member-wise comparison is not consistent with the user-defined opCmp, then it is up to the programmer to supply an appropriate version of opEquals. Otherwise, inequalities like a <= b will behave inconsistently with equalities like a == b.

Function Call Operator Overloading f()

The function call operator, (), can be overloaded by declaring a function named opCall:

struct F {
  int opCall();
  int opCall(int x, int y, int z);
}

void test() {
  F f;
  int i;

  i = f();      // same as i = f.opCall();
  i = f(3,4,5); // same as i = f.opCall(3,4,5);
}

In this way a struct or class object can behave as if it were a function.

static opCall also works as expected for function call operator with type names.

struct Double {
  static int opCall(int x) { return x * 2; }
}
void test() {
  int i = Double(2);
  assert(i == 4);
}

Note that merely declaring opCall automatically disables struct literal syntax. To avoid the limitation, you need to also declare constructor so that it takes priority over opCall in Type(...) syntax.

struct Multiplier {
  int factor;
  this(int num) { factor = num; }
  int opCall(int value) { return value * factor; }
}
void test() {
  Multiplier m = Multiplier(10);  // invoke constructor
  int result = m(5);              // invoke opCall
  assert(result == 50);
}

Assignment Operator Overloading

The assignment operator = can be overloaded if the left hand side is a struct aggregate, and opAssign is a member function of that aggregate.

For struct types, operator overloading for the identity assignment is allowed.
struct S {
  // identiy assignment, allowed.
  void opAssign(S rhs);

  // not identity assignment, also allowed.
  void opAssign(int);
}
S s;
s = S();      // Rewritten to s.opAssign(S());
s = 1;        // Rewritten to s.opAssign(1);
However for class types, identity assignment is not allowed. All class types have reference semantics, so identity assignment by default rebinds the left-hand-side to the argument at the right, and this is not overridable.
class C {
  // If X is the same type as C or the type which is
  // implicitly convertible to C, then opAssign would
  // accept identity assignment, which is disallowed.
  // C opAssign(...);
  // C opAssign(X);
  // C opAssign(X, ...);
  // C opAssign(X ...);
  // C opAssign(X, U = defaultValue, etc.);

  // not an identity assignment - allowed
  void opAssign(int);
}
C c = new C();
c = new C();  // Rebinding referencee
c = 1;        // Rewritten to c.opAssign(1);

Index Assignment Operator Overloading

If the left hand side of an assignment is an index operation on a struct or class instance, it can be overloaded by providing an opIndexAssign member function. Expressions of the form a[b1, b2, ... bn] = c are rewritten as a.opIndexAssign(c,b1, b2, ... bn).

struct A {
  int opIndexAssign(int value, size_t i1, size_t i2);
}

void test() {
  A a;
  a[i,3] = 7;  // same as a.opIndexAssign(7,i,3);
}

Slice Assignment Operator Overloading

If the left hand side of an assignment is a slice operation on a struct or class instance, it can be overloaded by providing an opSliceAssign member function. Expressions of the form a[i..j] = c are rewritten as a.opSliceAssign(c, i, j), and a[] = c as a.opSliceAssign(c).

struct A {
  int opSliceAssign(int v);  // overloads a[] = v
  int opSliceAssign(int v, size_t x, size_t y);  // overloads a[i .. j] = v
}

void test() {
  A a;
  int v;

  a[] = v;  // same as a.opSliceAssign(v);
  a[3..4] = v;  // same as a.opSliceAssign(v,3,4);
}

Op Assignment Operator Overloading

The following op assignment operators are overloadable:

Overloadable Op Assignment Operators
+=-=*=/=%=^^=&=
|=^=<<= >>=>>>=~= 

The expression:

a op= b

is rewritten as:

a.opOpAssign!("$(METACODE op)")(b)

Index Op Assignment Operator Overloading

If the left hand side of an op= is an index expression on a struct or class instance and opIndexOpAssign is a member:

a[b1, b2, ... bn] op= c

it is rewritten as:

a.opIndexOpAssign!("$(METACODE op)")(c, b1, b2, ... bn)

Slice Op Assignment Operator Overloading

If the left hand side of an op= is a slice expression on a struct or class instance and opSliceOpAssign is a member:

a[i..j] op= c

it is rewritten as:

a.opSliceOpAssign!("$(METACODE op)")(c, i, j)

and

a[] op= c

it is rewritten as:

a.opSliceOpAssign!("$(METACODE op)")(c)

Index Operator Overloading

The array index operator, a[b1, b2, ... bn], can be overloaded by declaring a function named opIndex with one or more parameters.

struct A {
  int opIndex(size_t i1, size_t i2, size_t i3);
}

void test() {
  A a;
  int i;
  i = a[5,6,7];  // same as i = a.opIndex(5,6,7);
}

In this way a struct or class object can behave as if it were an array.

If an index expression can be rewritten using opIndexAssign or opIndexOpAssign, those are preferred over opIndex.

Slice Operator Overloading

Overloading the slicing operator means overloading expressions like a[] and a[i..j]. This can be done by declaring a member function named opSlice.

class A {
  int opSlice();  // overloads a[]
  int opSlice(size_t x, size_t y); // overloads a[i .. j]
}

void test() {
  A a = new A();
  int i;
  int v;

  i = a[];         // same as i = a.opSlice();
  i = a[3..4];  // same as i = a.opSlice(3,4);
}

If a slice expression can be rewritten using opSliceAssign or opSliceOpAssign, those are preferred over opSlice.

Dollar Operator Overloading

Within the arguments to array index and slicing operators, $ gets translated to opDollar, which can be overloaded by a struct or class object.

struct Rectangle {
  int width, height;
  int[][] impl;
  this(int w, int h) {
    width = w;
    height = h;
    impl = new int[w][h];
  }
  int opIndex(size_t i1, size_t i2) {
    return impl[i1][i2];
  }
  int opDollar(size_t pos)() {
    static if (pos==0)
      return width;
    else
      return height;
  }
}

void test() {
  auto r = Rectangle(10,20);
  int i = r[$-1, 0];    // same as: r.opIndex(r.opDollar!0, 0),
                        // which is r.opIndex(r.width-1, 0)
  int j = r[0, $-1];    // same as: r.opIndex(0, r.opDollar!1)
                        // which is r.opIndex(0, r.height-1)
}

As the above example shows, a different compile-time argument is passed to opDollar depending on which argument it appears in. A $ appearing in the first argument gets translated to opDollar!0, a $ appearing in the second argument gets translated to opDollar!1, and so on. Thus, the appropriate value for $ can be returned to implement multidimensional arrays.

If opIndex is declared with only one argument, the compile-time argument to opDollar may be omitted. In this case, it is illegal to use $ inside an array indexing expression with more than one argument.

Note that opDollar does not have to return an integer index; it can return any type as long as that type is understood by opIndex. For example, a special token can be returned by opDollar to implement slicing of infinite ranges.

Forwarding

Member names not found in a class or struct can be forwarded to a template function named opDispatch for resolution.

import std.stdio;

struct S {
  void opDispatch(string s, T)(T i)
  {
    writefln("S.opDispatch('%s', %s)", s, i);
  }
}

class C {
  void opDispatch(string s)(int i) {
    writefln("C.opDispatch('%s', %s)", s, i);
  }
}

struct D {
  template opDispatch(string s) {
    enum int opDispatch = 8;
  }
}

void main() {
  S s;
  s.opDispatch!("hello")(7);
  s.foo(7);

  auto c = new C();
  c.foo(8);

  D d;
  writefln("d.foo = %s", d.foo);
  assert(d.foo == 8);
}
Forums | Comments | Search | Downloads | Home