Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant operator overloading in D

Tags:

For a while I was confused about the direction of D's operator overloading, but now I realize it's a beautiful system... if It would only work with core types (int, float, etc). Consider the follow code:

struct Vector {
    float X, Y;

    void opOpAssign(string op)(Vector vector) {
        X.opOpAssign!op(vector.X); // ERROR: no property "opOpAssign" for float
        Y.opOpAssign!op(vector.Y); // ERROR: ditto
    }
}

This would be beautiful code if it worked, seeing as it overloads all +=, -=, *=, etc.. operators in one method. However, as you can see, it doesn't work out of the box. I have created a solution using templates (god I love D):

template Op(string op, T) {
    void Assign(ref T a, T b) {
        static if (op == "+") a += b;
          else if (op == "-") a -= b;
          else if (op == "*") a *= b;
          else if (op == "/") a /= b;
    }
}

struct Vector {
    float X, Y;

    void opOpAssign(string op)(Vector vector) {
        Op!(op, typeof(X)).Assign(X, vector.X);
        Op!(op, typeof(Y)).Assign(Y, vector.Y);
    }
}

This is fine, only I'd much prefer to keep everything "in house". Is there a way to make this work without the aid of a template? I know I'm being picky here, seeing as there's no performance loss and it's not hard to import a module in situation I need to do this. I'm just wondering if it's built in and I'm overlooking something.

like image 773
F i L Avatar asked Oct 19 '11 19:10

F i L


2 Answers

Almost all overloaded operators in D are templates by definition. Notice that void opOpAssign(string op)(Vector vector) has a template parameter which is a string. So, no you can't overload it as a non-template function. Now, you don't need a second template to do it (so if by asking whether you need a template, you mean a helper template, then the answer is no), but the overloaded operator function is already a template.

The canonical way to do what you you're trying to do here is to use string mixins:

void opOpAssign(string op)(Vector vector)
{
    mixin("X" ~ op ~ "=vector.X;");
    mixin("Y" ~ op ~ "=vector.Y;");
}
like image 166
Jonathan M Davis Avatar answered Nov 08 '22 21:11

Jonathan M Davis


this is meant to be combined with mixins

void opOpAssign(string op)(Vector vector) {
    mixin("X"~op~"=vector.X;");
    mixin("Y"~op~"=vector.Y;");
}

not to mention this can easily be coupled to other arithmetic operations

Vector opBinary(string op)(Vector l)if(op=="+"||op=="-"){//only addition and subtraction makes sense for 2D vectors
    mixin("return Vector(x"~op~"l.x,y"~op~"l.y;");
}

///take in anything as long as a corresponding binaryOp exists
///this essentially rewrites all "vec op= variable;" to "vec = vec op variable;"
void opOpAssign(string op,T)(T l){
    this  = this.binaryOp!op(l);
}

and even to other scaling the Vector

Vector opBinary(string op)(real l)if(op=="*"||op=="/"){
    mixin("return Vector(x"~op~"l,y"~op~"l;");
}

Vector opBinaryRight(string op)(real l)if(op=="*"){// for 2 * vec
    return this*l;
}

note that the defined opBinarys restrict what can be passed to opOpAssign but you can go both ways (define opBinary in terms of opOpAssign)

like image 42
ratchet freak Avatar answered Nov 08 '22 21:11

ratchet freak