www.digitalmars.com

D Programming Language 1.0


Last update Sat Mar 6 18:04:37 2010

Operator Overloading

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 then the other, or one compiles and the other does not, the one 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, it should follow the following form:

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

Overloading < <=, > and >=

Comparison operations are rewritten as follows:

Overloadable Unary Operators
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 follow the following form:

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

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.

Assignment Operator Overloading

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

The assignment operator cannot be overloaded for rvalues that can be implicitly cast to the lvalue type. Furthermore, the following parameter signatures for opAssign are not allowed:

opAssign(...)
opAssign(T)
opAssign(T, ...)
opAssign(T ...)
opAssign(T, U = defaultValue, etc.)

where T is the same type as the aggregate type A, is implicitly convertible to A, or if A is a struct and T is a pointer to a type that is implicitly convertible to A.

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!("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!("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!("op")(c, i, j)

and

a[] op= c

it is rewritten as:

a.opSliceOpAssign!("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.

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

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

Overloadable Unary Operators
op opfunc
-e opNeg
+e opPos
~e opCom
e++ opPostInc
e-- opPostDec
cast(type)e opCast

Given a unary overloadable operator op and its corresponding class or struct member function name opfunc, the syntax:

op a

where a is a class or struct object reference, is interpreted as if it was written as:

a.opfunc()

Overloading ++e and --e

Since ++e is defined to be semantically equivalent to (e += 1), the expression ++e is rewritten as (e += 1), and then checking for operator overloading is done. The situation is analogous for --e.

Examples

  1. class A { int opNeg(); }
    A a;
    -a;	// equivalent to a.opNeg();
    
  2. class A { int opNeg(int i); }
    A a;
    -a;	// equivalent to a.opNeg(), which is an error
    

Overloading cast(type)e

The member function e.opCast() is called, and the return value of opCast() is implicitly converted to type. Since functions cannot be overloaded based on return value, there can be only one opCast per struct or class. Overloading the cast operator does not affect implicit casts, it only applies to explicit casts.

struct A
{
    int 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

Overloadable Binary Operators
op commutative? opfunc opfunc_r
+ yes opAdd opAdd_r
- no opSub opSub_r
* yes opMul opMul_r
/ no opDiv opDiv_r
% no opMod opMod_r
& yes opAnd opAnd_r
| yes opOr opOr_r
^ yes opXor opXor_r
<< no opShl opShl_r
>> no opShr opShr_r
>>> no opUShr opUShr_r
~ no opCat opCat_r
== yes opEquals -
!= yes opEquals -
< yes opCmp -
<= yes opCmp -
> yes opCmp -
>= yes opCmp -
= no opAssign -
+= no opAddAssign -
-= no opSubAssign -
*= no opMulAssign -
/= no opDivAssign -
%= no opModAssign -
&= no opAndAssign -
|= no opOrAssign -
^= no opXorAssign -
<<= no opShlAssign -
>>= no opShrAssign -
>>>= no opUShrAssign -
~= no opCatAssign -
in no opIn opIn_r

Given a binary overloadable operator op and its corresponding class or struct member function name opfunc and opfunc_r, and the syntax:

a op b
the following sequence of rules is applied, in order, to determine which form is used:
  1. The expression is rewritten as both:
    a.opfunc(b)
    b.opfunc_r(a)
    
    If any a.opfunc or 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.
  2. If the operator is commutative, then the following forms are tried:
    a.opfunc_r(b)
    b.opfunc(a)
    
  3. If a or b is a struct or class object reference, it is an error.

Examples

  1. class A { int opAdd(int i); }
    A a;
    a + 1;	// equivalent to a.opAdd(1)
    1 + a;	// equivalent to a.opAdd(1)
    
  2. class B { int opDiv_r(int i); }
    B b;
    1 / b;	// equivalent to b.opDiv_r(1)
    
  3. class A { int opAdd(int i); }
    class B { int 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)
    
  4. class A { int opAdd(B b);  int 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)
    
  5. class A { int opAdd(B b);  int opAdd_r(B b); }
    class B { int 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 !=

Both operators use the opEquals() function. The expression (a == b) is rewritten as a.opEquals(b), and (a != b) is rewritten as !a.opEquals(b).

The member function opEquals() is defined as part of Object as:

int opEquals(Object o);

so that every class object has a default 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 Object, not the type for the class.

Structs and unions (hereafter just called structs) can provide a member function:

int opEquals(S s)

or:

int opEquals(S* s)

where S is the struct name, to define how equality is determined.

If a struct has no opEquals function declared for it, a bit compare of the contents of the two structs is done to determine equality or inequality.

Note: Comparing a reference to a class object against null should be done as:

if (a is null)

and not as:

if (a == null)

The latter is converted to:

if (a.opEquals(null))

which will fail if opEquals() is a virtual function.

Overloading <, <=, > and >=

These comparison operators all use the opCmp() function. The expression (a op b) is rewritten as (a.opCmp(b) op 0). The commutative operation is rewritten as (0 op b.opCmp(a))

The member function opCmp() is defined as part of Object as:

int opCmp(Object o);

so that every class object has a opCmp().

opCmp for structs works analogously to opEquals for structs:

struct Pair
{
    int a, b;
    int opCmp(Pair rhs)
    {
        if (a!=rhs.a) return a-rhs.a;
        return b-rhs.b;
    }
}

If a struct has no opCmp() function declared for it, attempting to compare two structs is an error.

Rationale

The reason for having both opEquals and opCmp is that:

The parameter to opEquals and opCmp for class definitions must be of type Object, rather than the type of the particular class, in order to override the Object.opEquals and Object.opCmp functions properly.

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.

Array Operator Overloading

Overloading Indexing a[i]

The array index operator, [], can be overloaded by declaring a function named opIndex with one or more parameters. Assignment to an array can be overloaded with a function named opIndexAssign with two or more parameters. The first parameter is the rvalue of the assignment expression.

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

void test()
{   A a;
    int i;

    i = a[5,6,7];	// same as i = a.opIndex(5,6,7);
    a[i,3] = 7;		// same as a.opIndexAssign(7,i,3);
}

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

Note: Array index overloading currently does not work for the lvalue of an op=, ++, or -- operator.

Overloading Slicing a[] and a[i .. j]

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

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

    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 = new A();
    int i;
    int v;

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

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

Assignment Operator Overloading

The assignment operator = can be overloaded if the lvalue is a struct or class aggregate, and opAssign is a member function of that aggregate.

The assignment operator cannot be overloaded for rvalues that can be implicitly cast to the lvalue type. Furthermore, the following parameter signatures for opAssign are not allowed:

opAssign(...)
opAssign(T)
opAssign(T, ...)
opAssign(T ...)
opAssign(T, U = defaultValue, etc.)

where T is the same type as the aggregate type A, is implicitly convertible to A, or if A is a struct and T is a pointer to a type that is implicitly convertible to A.

Future Directions

The operators ! && || ?: and a few others will likely never be overloadable.