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.
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;");
}
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 opBinary
s restrict what can be passed to opOpAssign
but you can go both ways (define opBinary
in terms of opOpAssign
)