f()
Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used.
op  rewrite 

 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; }
++
and Postdecrement e
OperatorsThese are not directly overloadable, but instead are rewritten in terms of the ++e and e prefix operators:
op  rewrite 

e

(auto t = e,  e, t)

e++

(auto t = e, ++ e, t)

op  rewrite 

 a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("")( b_{1}, b_{2}, ... b_{n})

+ a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("+")( b_{1}, b_{2}, ... b_{n}) 
~ a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("~")( b_{1}, b_{2}, ... b_{n}) 
* a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("*")( b_{1}, b_{2}, ... b_{n}) 
++ a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("++")( b_{1}, b_{2}, ... b_{n}) 
 a[ b_{1}, b_{2}, ... b_{n}]

a.opIndexUnary!("")( b_{1}, b_{2}, ... b_{n}) 
op  rewrite 

 a[ i..j]

a.opIndexUnary!("")( a.opSlice( i, j)) 
+ a[ i..j]

a.opIndexUnary!("+")( a.opSlice( i, j)) 
~ a[ i..j]

a.opIndexUnary!("~")( a.opSlice( i, j)) 
* a[ i..j]

a.opIndexUnary!("*")( a.opSlice( i, j)) 
++ a[ i..j]

a.opIndexUnary!("++")( a.opSlice( i, j)) 
 a[ i..j]

a.opIndexUnary!("")( a.opSlice( i, j)) 
 a[ ]

a.opIndexUnary!("")() 
+ a[ ]

a.opIndexUnary!("+")() 
~ a[ ]

a.opIndexUnary!("~")() 
* a[ ]

a.opIndexUnary!("*")() 
++ a[ ]

a.opIndexUnary!("++")() 
 a[ ]

a.opIndexUnary!("")() 
For backward compatibility, if the above rewrites fail to compile and opSliceUnary
is defined, then the rewrites a.opSliceUnary!(op)(i, j)
and a.opSliceUnary!(op)
are tried instead, respectively.
To define how one type can be cast to another, define the opCast
template method, which is used as follows:
op  rewrite 

cast( type) e

e.opCast!( type)() 
Note that opCast
is only ever used with an explicit cast
expression, except in the case of boolean operations (see next section)
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.
The following binary operators are overloadable:
+ 
 
* 
/ 
% 
^^ 
& 
 
^ 
< <

> >

> > >

~ 
in 
The expression:
a op b
is rewritten as both:
a.opBinary!("op")(b) b.opBinaryRight!("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"); }
Note that opIn
and opIn_r
have been deprecated in favor of opBinary!"in"
and opBinaryRight!"in"
respectively.
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 userdefined 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 memberwise 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 userdefined type is to be used as a key in the builtin 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.
==
and !=
Expressions of the form a != b
are rewritten as !(a == b)
.
Given a == b
:
.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); }
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)
. 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 rvalues (e.g. temporaries) bool opEquals(ref const S s) { ... } // for lvalues (e.g. variables) // both hand side can be const object bool opEquals(const S s) const { ... } // for rvalues (e.g. temporaries) }
Alternatively, you can declare a single templated opEquals
function with an auto ref parameter:
struct S { // for lvalues and rvalues, // with converting both hand side implicitly to const bool opEquals()(auto ref const S s) const { ... } }
<
, <
=
, >
, and >
=
Comparison operations are rewritten as follows:
comparison  rewrite 1  rewrite 2 

a < b

a.opCmp(b) < 0

b.opCmp(a) > 0

a < = b

a.opCmp(b) < = 0

b.opCmp(a) > = 0

a > b

a.opCmp(b) > 0

b.opCmp(a) < 0

a > = b

a.opCmp(b) > = 0

b.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 memberwise comparison. If this memberwise comparison is not consistent with the userdefined 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
.
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.
Note that merely declaring opCall
automatically disables struct literal syntax. To avoid the limitation, you need to also declare a 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); }
static opCall
also works as expected for a 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); }
Mixing struct constructors and static opCall
is not allowed.
struct S { this(int i) {} static S opCall() // disallowed due to constructor { return S.init; } }
Note: static opCall
can be used to simulate struct constructors with no arguments, but this is not recommended practice. Instead, the preferred solution is to use a factory function to create struct instances.
The assignment operator =
can be overloaded if the left hand side is a struct aggregate, and opAssign
is a member function of that aggregate.
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 lefthandside 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);
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[
b_{1}, b_{2}, ... b_{n}] = c
are rewritten as a.opIndexAssign(c,
b_{1}, b_{2}, ... b_{n})
.
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); }
If the left hand side of an assignment is a slice operation on a struct or class instance, it can be overloaded by implementing an opIndexAssign
member function that takes the return value of the opSlice
function as parameter(s). Expressions of the form a[
i..j] = c
are rewritten as a.opIndexAssign(c,
a.opSlice(
i, j))
, and a[] = c
as a.opIndexAssign(c)
.
See Array Indexing and Slicing Operators Overloading for more details.
struct A { int opIndexAssign(int v); // overloads a[] = v int opIndexAssign(int v, size_t[2] x); // overloads a[i .. j] = v int[2] opSlice(size_t x, size_t y); // overloads i .. j } void test() { A a; int v; a[] = v; // same as a.opIndexAssign(v); a[3..4] = v; // same as a.opIndexAssign(v, a.opSlice(3,4)); }
For backward compatibility, if rewriting a[
i..j]
as a.opIndexAssign(a.opSlice(
i, j))
fails to compile, the legacy rewrite opSliceAssign(c,
i, j)
is used instead.
The following op assignment operators are overloadable:
+= 
= 
*= 
/= 
% =

^^= 
& =

 =

^= 
< < =

> > =

> > > =

~= 
The expression:
a op= b
is rewritten as:
a.opOpAssign!("op")(b)
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!("op")(c, b1, b2, ... bn)
If the left hand side of an op= is a slice expression on a struct or class instance and opIndexOpAssign
is a member:
a[i..j] op= c
it is rewritten as:
a.opIndexOpAssign!("op")(c, a.opSlice(i, j))
and
a[] op= c
it is rewritten as:
a.opIndexOpAssign!("op")(c)
For backward compatibility, if the above rewrites fail and opSliceOpAssign
is defined, then the rewrites a.opSliceOpAssign(c, i, j)
and a.opSliceOpAssign(c)
are tried, respectively.
The array indexing and slicing operators are overloaded by implementing the opIndex
, opSlice
, and opDollar
methods. These may be combined to implement multidimensional arrays.
The code example below shows a simple implementation of a 2dimensional array with overloaded indexing and slicing operators. The explanations of the various constructs employed are given in the sections following.
struct Array2D(E) { E[] impl; int stride; int width, height; this(int width, int height, E[] initialData = []) { impl = initialData; this.stride = this.width = width; this.height = height; impl.length = width * height; } // Index a single element, e.g., arr[0, 1] ref E opIndex(int i, int j) { return impl[i + stride*j]; } // Array slicing, e.g., arr[1..2, 1..2], arr[2, 0..$], arr[0..$, 1]. Array2D opIndex(int[2] r1, int[2] r2) { Array2D result; auto startOffset = r1[0] + r2[0]*stride; auto endOffset = r1[1] + (r2[1]  1)*stride; result.impl = this.impl[startOffset .. endOffset]; result.stride = this.stride; result.width = r1[1]  r1[0]; result.height = r2[1]  r2[0]; return result; } auto opIndex(int[2] r1, int j) { return opIndex(r1, [j, j+1]); } auto opIndex(int i, int[2] r2) { return opIndex([i, i+1], r2); } // Support for `x..y` notation in slicing operator for the given dimension. int[2] opSlice(size_t dim)(int start, int end) if (dim >= 0 && dim < 2) in { assert(start >= 0 && end <= this.opDollar!dim); } body { return [start, end]; } // Support `$` in slicing notation, e.g., arr[1..$, 0..$1]. @property int opDollar(size_t dim : 0)() { return width; } @property int opDollar(size_t dim : 1)() { return height; } } unittest { auto arr = Array2D!int(4, 3, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]); // Basic indexing assert(arr[0, 0] == 0); assert(arr[1, 0] == 1); assert(arr[0, 1] == 4); // Use of opDollar assert(arr[$1, 0] == 3); assert(arr[0, $1] == 8); // Note the value of $ differs by dimension assert(arr[$1, $1] == 11); // Slicing auto slice1 = arr[1..$, 0..$]; assert(slice1[0, 0] == 1 && slice1[1, 0] == 2 && slice1[2, 0] == 3 && slice1[0, 1] == 5 && slice1[1, 1] == 6 && slice1[2, 1] == 7 && slice1[0, 2] == 9 && slice1[1, 2] == 10 && slice1[2, 2] == 11); auto slice2 = slice1[0..2, 1..$]; assert(slice2[0, 0] == 5 && slice2[1, 0] == 6 && slice2[0, 1] == 9 && slice2[1, 1] == 10); // Thin slices auto slice3 = arr[2, 0..$]; assert(slice3[0, 0] == 2 && slice3[0, 1] == 6 && slice3[0, 2] == 10); auto slice4 = arr[0..3, 2]; assert(slice4[0, 0] == 8 && slice4[1, 0] == 9 && slice4[2, 0] == 10); }
Expressions of the form arr[
b_{1}, b_{2}, ... b_{n}]
are translated into arr.opIndex(
b_{1}, b_{2}, ... b_{n})
. For example:
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
.
Overloading the slicing operator means overloading expressions like a[]
or a[
i..j]
, where the expressions inside the square brackets contain slice expressions of the form i..j.
To overload a[]
, simply define opIndex
with no parameters:
struct S { int[] impl; int[] opIndex() { return impl[]; } } void test() { auto s = S([1,2,3]); auto t = s[]; // calls s.opIndex() assert(t == [1,2,3]); }
To overload array indexing of the form a[
i..j,
...]
, two steps are needed. First, the expressions of the form i..j are translated via opSlice
into userdefined objects that encapsulate the endpoints i and j. Then these userdefined objects are passed to opIndex
to perform the actual slicing. This design was chosen in order to support mixed indexing and slicing in multidimensional arrays; for example, in translating expressions like arr[1, 2..3, 4]
.
More precisely, an expression of the form arr[
b_{1}, b_{2}, ... b_{n}]
is translated into arr.opIndex(
c_{1}, c_{2}, ... c_{n})
. Each argument b_{i} can be either a single expression, in which case it is passed directly as the corresponding argument c_{i} to opIndex
; or it can be a slice expression of the form x_{i}..
y_{i}, in which case the corresponding argument c_{i} to opIndex
is arr.opSlice!i(
x_{i},
y_{i})
. Namely:
op  rewrite 

arr[1, 2, 3]

arr.opIndex(1, 2, 3) 
arr[1..2, 3..4, 5..6]

arr.opIndex(arr.opSlice!0(1,2), arr.opSlice!1(3,4), arr.opSlice!2(5,6)) 
arr[1, 2..3, 4]

arr.opIndex(1, arr.opSlice!1(2,3), 4) 
Similar translations are done for assignment operators involving slicing, for example:
op  rewrite 

arr[1, 2..3, 4] = c

arr.opIndexAssign(c, 1, arr.opSlice!1(2, 3), 4) 
arr[2, 3..4] += c

arr.opIndexOpAssign!"+"(c, 2, arr.opSlice!1(2, 3)) 
The intention is that opSlice!i
should return a userdefined object that represents an interval of indices along the i
'th dimension of the array. This object is then passed to opIndex
to perform the actual slicing operation. If only onedimensional slicing is desired, opSlice
may be declared without the compiletime parameter i
.
Note that in all cases, arr
is only evaluated once. Thus, an expression like getArray()[1, 2..3, $1]=c
has the effect of:
auto __tmp = getArray(); __tmp.opIndexAssign(c, 1, __tmp.opSlice!1(2,3), __tmp.opDollar!2  1);
where the initial function call to getArray
is only executed once.
For backward compatibility, a[]
and a[
i..j]
can also be overloaded by implementing opSlice()
with no arguments and opSlice(
i, j)
with two arguments, respectively. This only applies for onedimensional slicing, and dates from when D did not have full support for multidimensional arrays. This usage of opSlice
is discouraged.
Within the arguments to array index and slicing operators, $
gets translated to opDollar!i
, where i
is the position of the expression $
appears in. For example:
op  rewrite 

arr[$1, $2, 3]

arr.opIndex(arr.opDollar!0  1, arr.opDollar!1  2, 3) 
arr[1, 2, 3..$]

arr.opIndex(1, 2, arr.opSlice!2(3, arr.opDollar!2)) 
The intention is that opDollar!i
should return the length of the array along its i
'th dimension, or a userdefined object representing the end of the array along that dimension, that is understood by opSlice
and opIndex
.
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.width1, 0) int j = r[0, $1]; // same as: r.opIndex(0, r.opDollar!1) // which is r.opIndex(0, r.height1) }
As the above example shows, a different compiletime 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.
Note that opDollar!i
is only evaluated once for each i
where $
occurs in the corresponding position in the indexing operation. Thus, an expression like arr[$sqrt($), 0, $1]
has the effect of:
auto __tmp1 = arr.opDollar!0; auto __tmp2 = arr.opDollar!2; arr.opIndex(__tmp1  sqrt(__tmp1), 0, __tmp2  1);
If opIndex
is declared with only one argument, the compiletime argument to opDollar
may be omitted. In this case, it is illegal to use $
inside an array indexing expression with more than one argument.
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); }
The D1 operator overload mechanisms are deprecated.
© 1999–2019 The D Language Foundation
Licensed under the Boost License 1.0.
https://dlang.org/spec/operatoroverloading.html