Search code examples
templatesstructoperator-overloadingd

operator overloading for structs in D language


I'm trying to implement aritmetic operations like +,-,*,/ between 3D vectors in D language (just to get idea if it is worth to migrate from C++ to D, I usually do 3D graphics and numerical math). But I'm perhabs missing something, following code does not work (I tried to do it according to included references).

#!/usr/bin/env rdmd
import std.stdio;

// https://dlang.org/spec/operatoroverloading.html
// https://github.com/PhilippeSigaud/D-templates-tutorial/blob/master/D-templates-tutorial.md#u-op-v--------opbinarystring-s-vv-v-if-s--opv-op-u--------opbinaryrightstring-s-vv-v-if-s--op

struct vec3f{ float x,y,z; }
vec3f opBinary(string op)(vec3f a, vec3f b){
         static if (op == "+"){ return vec3f(a.x+b.x,a.y+b.y,a.z+b.z); }
    else static if (op == "*"){ return vec3f(a.x*b.x,a.y*b.y,a.z*b.z); }
    else{ static assert(0, "Operator "~op~" not implemented"); }
}

struct vec3(T){ T x,y,z; }
auto opBinary(T,string op)(vec3!T a, vec3!T b){
         static if (op == "+"){ return vec3!T(a.x+b.x,a.y+b.y,a.z+b.z); }
    else static if (op == "*"){ return vec3!T(a.x*b.x,a.y*b.y,a.z*b.z); }
    else{ static assert(0, "Operator "~op~" not implemented"); }
}

void main(){
    //auto a = vec3!float(1.1,2.2,3.2);
    //auto b = vec3!float(3.1,2.2,1.2);
    auto a = vec3f(1.1,2.2,3.2);
    auto b = vec3f(3.1,2.2,1.2);
    writeln(a); writeln(b);
    writeln( a+b );
}

The opBinary implementation seems to compile fine, but I always get error when I try to use it:

./operator_overload.d(27): Error: incompatible types for ((a) + (b)): 'vec3f' and 'vec3f'
Failed: ["dmd", "-v", "-o-", "./operator_overload.d", "-I."]

EDIT : I also tried mixin acoording to this answer. With the same error.

struct vec3f{ float x,y,z; }
vec3f opBinary(string op)(vec3f a,vec3f b)if(op=="+"||op=="-"||op=="*"||op=="/"){
     mixin("return vec3f(a.x"~op~"b.x,a.y"~op~"b.y,a.z"~op~"b.z);");
}

EDIT 2 : Yes it have to be part of struct body (I don't know if there is any way how to make it free-standing function ). This works perfect:

#!/usr/bin/env rdmd
import std.stdio;

struct vec3(T){ 
    float x,y,z; 
    vec3!T opBinary(string op)(vec3!T b) if(op=="+"||op=="-"||op=="*"||op=="/"){ 
        mixin("return vec3!T(x"~op~"b.x,y"~op~"b.y,z"~op~"b.z);"); 
    }
}

void main(){
    auto a = vec3!float(1.1,2.2,3.2);
    auto b = vec3!float(3.1,2.2,1.2);
    writeln(a); writeln(b);
    writeln( a+b );
    writeln( a*b );
}

Solution

  • One of the main differences between C++ and D is that user-specified operators in D are always a member of one of the operands. For binary operators, if the struct is the left-hand side the operator is called opBinary and for the right-hand side, it's called opBinaryRight. It is no other way for good reasons: It makes implementing operator overloading very complicated. You may know that C++ ignores namespaces when searching for an operator overload, e.g. << between ostream& and int is in the namespace std. You don't need to write

    std::operator<<(std::operator<<(std::cout, "Hello World."), std::endl);
    

    for

    std::cout << "Hello World." << std::endl;
    

    because of that. D has a module system. Say you have module a which gives you type A and module b which gives you type B, both not directly correlated to each other. Then there is this module c with type C and an operator * that takes types A and B and returns a C because C relates A and B. Now I import a and b and use * on two objects. How should the compiler know about c?