Ddoc
$(SPEC_S Operator Overloading,
$(P Overloading is accomplished by interpreting specially named
struct and class member functions as being implementations of unary and
binary operators. No additional syntax is used.
)
Unary Operator Overloading
$(TABLE1
Overloadable Unary Operators
$(TR $(TH$(I op)) $(TH$(I opfunc)) )
$(TR
$(TD -$(I e))
$(TD $(CODE opNeg))
)
$(TR
$(TD +$(I e))
$(TD $(CODE opPos))
)
$(TR
$(TD ~$(I e))
$(TD $(CODE opCom))
)
$(V2
$(TR
$(TD *$(I e))
$(TD $(CODE opStar))
)
)
$(TR
$(TD $(I e)++)
$(TD $(CODE opPostInc))
)
$(TR
$(TD $(I e)--)
$(TD $(CODE opPostDec))
)
$(TR
$(TD cast($(I type))$(I e))
$(TD $(CODE opCast))
)
)
$(P Given a unary
overloadable operator $(I op) and its corresponding
class or struct member
function name $(I opfunc), the syntax:
)
---
$(I op) a
---
$(P where $(I a) is a class or struct object reference,
is interpreted as if it was written as:
)
---
a.$(I opfunc)()
---
Overloading ++$(I e) and --$(I e)
$(P Since ++$(I e) is defined to be semantically equivalent
to ($(I e) += 1), the expression ++$(I e) is rewritten
as ($(I e) += 1), and then checking for operator overloading
is done. The situation is analogous for --$(I e).
)
Examples
$(OL
$(LI
-------
class A { int $(B opNeg)(); }
A a;
-a; // equivalent to a.opNeg();
-------
)
$(LI
-------
class A { int $(B opNeg)(int i); }
A a;
-a; // equivalent to a.opNeg(), which is an error
-------
)
)
Overloading cast($(I type))$(I e)
$(P The member function $(I e).$(B opCast()) is called,
and the return value of $(B opCast()) is implicitly converted
to $(I type). Since functions cannot be overloaded based on
return value, there can be only one $(B opCast) per struct or
class.
Overloading the cast operator does not affect implicit casts, it
only applies to explicit casts.
)
-------
struct A
{
int $(B opCast)() { return 28; }
}
void test()
{
A a;
long i = cast(long)a; // i is set to 28L
void* p = cast(void*)a; // error, cannot implicitly
// convert int to void*
int j = a; // error, cannot implicitly convert
// A to int
}
-------
Binary Operator Overloading
$(TABLE1
Overloadable Binary Operators
$(TR $(TH $(I op))
$(TH commutative?)
$(TH $(I opfunc))
$(TH $(I opfunc_r))
)
$(TR $(TD +) $(TD yes) $(TD $(CODE opAdd)) $(TD $(CODE opAdd_r)))
$(TR $(TD -) $(TD no) $(TD $(CODE opSub)) $(TD $(CODE opSub_r)))
$(TR $(TD *) $(TD yes) $(TD $(CODE opMul)) $(TD $(CODE opMul_r)))
$(TR $(TD /) $(TD no) $(TD $(CODE opDiv)) $(TD $(CODE opDiv_r)))
$(TR $(TD %) $(TD no) $(TD $(CODE opMod)) $(TD $(CODE opMod_r)))
$(TR $(TD &) $(TD yes) $(TD $(CODE opAnd)) $(TD $(CODE opAnd_r)))
$(TR $(TD |) $(TD yes) $(TD $(CODE opOr)) $(TD $(CODE opOr_r)))
$(TR $(TD ^) $(TD yes) $(TD $(CODE opXor)) $(TD $(CODE opXor_r)))
$(TR $(TD <<) $(TD no) $(TD $(CODE opShl)) $(TD $(CODE opShl_r)))
$(TR $(TD >>) $(TD no) $(TD $(CODE opShr)) $(TD $(CODE opShr_r)))
$(TR $(TD >>>) $(TD no) $(TD $(CODE opUShr)) $(TD $(CODE opUShr_r)))
$(TR $(TD ~) $(TD no) $(TD $(CODE opCat)) $(TD $(CODE opCat_r)))
$(TR $(TD ==) $(TD yes) $(TD $(CODE opEquals)) $(TD -))
$(TR $(TD !=) $(TD yes) $(TD $(CODE opEquals)) $(TD -))
$(TR $(TD <) $(TD yes) $(TD $(CODE opCmp)) $(TD -))
$(TR $(TD <=) $(TD yes) $(TD $(CODE opCmp)) $(TD -))
$(TR $(TD >) $(TD yes) $(TD $(CODE opCmp)) $(TD -))
$(TR $(TD >=) $(TD yes) $(TD $(CODE opCmp)) $(TD -))
$(TR $(TD =) $(TD no ) $(TD $(CODE opAssign)) $(TD -) )
$(TR $(TD +=) $(TD no) $(TD $(CODE opAddAssign)) $(TD -))
$(TR $(TD -=) $(TD no) $(TD $(CODE opSubAssign)) $(TD -))
$(TR $(TD *=) $(TD no) $(TD $(CODE opMulAssign)) $(TD -))
$(TR $(TD /=) $(TD no) $(TD $(CODE opDivAssign)) $(TD -))
$(TR $(TD %=) $(TD no) $(TD $(CODE opModAssign)) $(TD -))
$(TR $(TD &=) $(TD no) $(TD $(CODE opAndAssign)) $(TD -))
$(TR $(TD |=) $(TD no) $(TD $(CODE opOrAssign)) $(TD -))
$(TR $(TD ^=) $(TD no) $(TD $(CODE opXorAssign)) $(TD -))
$(TR $(TD <<=) $(TD no) $(TD $(CODE opShlAssign)) $(TD -))
$(TR $(TD >>=) $(TD no) $(TD $(CODE opShrAssign)) $(TD -))
$(TR $(TD >>>=) $(TD no) $(TD $(CODE opUShrAssign)) $(TD -))
$(TR $(TD ~=) $(TD no) $(TD $(CODE opCatAssign)) $(TD -))
$(TR $(TD in ) $(TD no ) $(TD $(CODE opIn) ) $(TD $(CODE opIn_r) ))
)
$(P Given a binary
overloadable operator $(I op) and its corresponding
class or struct member
function name $(I opfunc) and $(I opfunc_r),
and the syntax:
)
---
a $(I op) b
---
the following sequence of rules is applied, in order, to determine
which form is used:
$(OL
$(LI The expression is rewritten as both:
---
a.$(I opfunc)(b)
b.$(I opfunc_r)(a)
---
If any $(I a.opfunc) or $(I b.opfunc_r) functions exist,
then overloading is applied
across all of them and the best match is used. If either exist,
and there is no argument match, then it is an error.
)
$(LI If the operator is commutative, then the following
forms are tried:
---
a.$(I opfunc_r)(b)
b.$(I opfunc)(a)
---
)
$(LI If $(I a) or $(I b) is a struct or class object reference,
it is an error.
)
)
Examples
$(OL
$(LI
-------
class A { int $(B opAdd)(int i); }
A a;
a + 1; // equivalent to a.opAdd(1)
1 + a; // equivalent to a.opAdd(1)
-------
)
$(LI
-------
class B { int $(B opDiv_r)(int i); }
B b;
1 / b; // equivalent to b.opDiv_r(1)
-------
)
$(LI
-------
class A { int $(B opAdd)(int i); }
class B { int $(B opAdd_r)(A a); }
A a;
B b;
a + 1; // equivalent to a.opAdd(1)
a + b; // equivalent to b.opAdd_r(a)
b + a; // equivalent to b.opAdd_r(a)
-------
)
$(LI
-------
class A { int $(B opAdd)(B b); int $(B opAdd_r)(B b); }
class B { }
A a;
B b;
a + b; // equivalent to a.opAdd(b)
b + a; // equivalent to a.opAdd_r(b)
-------
)
$(LI
-------
class A { int $(B opAdd)(B b); int $(B opAdd_r)(B b); }
class B { int $(B opAdd_r)(A a); }
A a;
B b;
a + b; // ambiguous: a.opAdd(b) or b.opAdd_r(a)
b + a; // equivalent to a.opAdd_r(b)
-------
)
)
Overloading == and !=
$(P Both operators use the $(CODE $(B opEquals)()) function.
The expression
$(CODE (a == b)) is rewritten as $(CODE a.$(B opEquals)(b)),
and $(CODE (a != b)) is rewritten as $(CODE !a.$(B opEquals)(b)).
)
$(P The member function $(CODE $(B opEquals)()) is defined as part of
Object as:
)
-------
int $(B opEquals)(Object o);
-------
$(P so that every class object has a default $(CODE $(B opEquals)()).
But every class definition which will be using == or != should
expect to need to override opEquals. The parameter to the overriding
function must be of type $(CODE Object), not the type for the class.
)
$(P Structs and unions (hereafter just called structs) can
provide a member function:
)
-------
int $(B opEquals)(S s)
-------
$(P or:)
-------
int $(B opEquals)(S* s)
-------
$(P where $(CODE S) is the struct name, to define how equality is
determined.)
$(P If a struct has no $(B opEquals) function declared for it,
a bit compare of the contents of the two structs is done to
determine equality or inequality.
)
$(P $(B Note:) Comparing a reference to a class object against $(B null)
should be done as:
)
-------
if (a is null)
-------
$(P and not as:)
-------
if (a == null)
-------
$(P The latter is converted to:)
-------
if (a.$(B opEquals)(null))
-------
$(P which will fail if $(CODE $(B opEquals)()) is a virtual function.)
Overloading <, <=, > and >=
$(P These comparison operators all use the $(CODE $(B opCmp)()) function.
The expression
$(CODE (a $(I op) b)) is rewritten as $(CODE (a.$(B opCmp)(b) $(I op) 0)).
The commutative operation is rewritten as $(CODE (0 $(I op) b.$(B opCmp)(a)))
)
$(P The member function $(CODE $(B opCmp)()) is defined as part of Object
as:
)
-------
int $(B opCmp)(Object o);
-------
$(P so that every class object has a $(CODE $(B opCmp)()).
)
$(P If a struct has no $(B opCmp)() function declared for it, attempting
to compare two structs is an error.
)
Rationale
$(P The reason for having both $(B opEquals)() and $(B opCmp)() is
that:)
$(UL
$(LI Testing for equality can sometimes be a much more efficient
operation than testing for less or greater than.)
$(LI Having an opCmp defined in Object makes it possible to
make associative arrays work generically for classes.)
$(LI For some objects, testing for less or greater makes no sense.
This is why Object.opCmp throws a runtime error.
opCmp must be overridden in each class for which comparison
makes sense.)
)
$(P The parameter to $(B opEquals) and $(B opCmp)
for class definitions must
be of type Object, rather than the type of the particular class,
in order to override the Object.$(B opEquals) and Object.$(B opCmp)
functions properly.
)
Function Call Operator Overloading $(I f)()
$(P The function call operator, (), can be overloaded by
declaring a function named $(B opCall):
)
-------
struct F
{
int $(B opCall)();
int $(B opCall)(int x, int y, int z);
}
void test()
{ F f;
int i;
i = f$(B ()); // same as i = f.opCall();
i = f$(B (3,4,5)); // same as i = f.opCall(3,4,5);
}
-------
$(P In this way a struct or class object can behave as if it
were a function.
)
Array Operator Overloading
Overloading Indexing $(I a)[$(I i)]
$(P The array index operator, [], can be overloaded by
declaring a function named $(B opIndex) with one
or more parameters.
Assignment to an array can be overloaded with a function
named $(B opIndexAssign) with two or more parameters.
The first parameter is the rvalue of the assignment expression.
)
-------
struct A
{
int $(B opIndex)(size_t i1, size_t i2, size_t i3);
int $(B opIndexAssign)(int value, size_t i1, size_t i2);
}
void test()
{ A a;
int i;
i = a$(B [)5,6,7$(B ]); // same as i = a.opIndex(5,6,7);
a$(B [)i,3$(B ]) = 7; // same as a.opIndexAssign(7,i,3);
}
-------
$(P In this way a struct or class object can behave as if it
were an array.
)
$(P $(B Note:) Array index overloading currently does not
work for the lvalue of an $(I op)=, ++, or -- operator.
)
Overloading Slicing $(I a)[] and $(I a)[$(I i) .. $(I j)]
$(P Overloading the slicing operator means overloading expressions
like $(CODE a[]) and $(CODE a[i .. j]).
This can be done by declaring a function named $(B opSlice).
Assignment to a slice can be done by declaring $(B opSliceAssign).
)
-------
class A
{
int $(B opSlice)(); // overloads a[]
int $(B opSlice)(size_t x, size_t y); // overloads a[i .. j]
int $(B opSliceAssign)(int v); // overloads a[] = v
int $(B opSliceAssign)(int v, size_t x, size_t y); // overloads a[i .. j] = v
}
void test()
{ A a = new A();
int i;
int v;
i = a$(B []); // same as i = a.opSlice();
i = a$(B [)3..4$(B ]); // same as i = a.opSlice(3,4);
a$(B []) = v; // same as a.opSliceAssign(v);
a$(B [)3..4$(B ]) = v; // same as a.opSliceAssign(v,3,4);
}
-------
Assignment Operator Overloading
$(P The assignment operator $(CODE =) can be overloaded if the
lvalue is a struct $(V1 or class) aggregate, and $(CODE opAssign)
is a member function of that aggregate.)
$(P The assignment operator cannot be overloaded for rvalues
that can be implicitly cast to the lvalue type.
Furthermore, the following parameter signatures for $(CODE opAssign)
are not allowed:)
---
opAssign(...)
opAssign(T)
opAssign(T, ...)
opAssign(T ...)
opAssign(T, U = defaultValue, etc.)
---
$(P where $(I T) is the same type as the aggregate type $(I A),
is implicitly
convertible to $(I A), or if $(I A) is a struct and $(I T)
is a pointer to a type that is
implicitly convertible to $(I A).
)
Future Directions
$(P The operators $(CODE ! . && || ?:) and a few others will
likely never be overloadable.
)
)
Macros:
TITLE=Operator Overloading
WIKI=OperatorOverloading