Operator overloading

Operators can be overloaded for user-defined types by declaring a function named operator followed by the operator. Binary operators are overloaded with non-member functions taking two parameters. Operator operands can be borrowed implicitly: T& parameters accept values as usual, and lvalues can also fill T* parameters without an explicit &. Ordinary function calls still require & for a T* parameter. Value parameters copy their operands.

struct Vec2: Copyable {
    int x;
    int y;
}

Vec2 operator+(Vec2 a, Vec2 b) {
    return Vec2(a.x + b.x, a.y + b.y);
}

bool operator==(Vec2 a, Vec2 b) {
    return a.x == b.x && a.y == b.y;
}

void main() {
    var a = Vec2(1, 2);
    var b = Vec2(3, 4);
    var c = a + b;
    println(c.x); // prints 4
    println(c.y); // prints 6
    println(a + b == c); // prints true
}

The overloadable operators are ==, !=, <, <=, >, >=, +, -, *, /, %, and the subscript operator [] below. Operator functions can be generic, like any other function.

Comparison operators are synthesized, so only == and < need overloads: != is the negation of ==, and ==/!= also match with their operands swapped. a > b checks b < a, a >= b checks !(a < b), and a <= b checks !(b < a).

The subscript operator is overloaded with a member function, so it can access the instance through this. A typical use is indexed access to a type's components, like the axes of a vector:

struct Vec3: Copyable {
    int x;
    int y;
    int z;

    int operator[](int index) {
        if index == 0 {
            return x;
        } else if index == 1 {
            return y;
        }
        return z;
    }

    void operator[]=(int index, int value) {
        if index == 0 {
            x = value;
        } else if index == 1 {
            y = value;
        } else {
            z = value;
        }
    }
}

void main() {
    var v = Vec3(1, 2, 3);
    println(v[0] + v[1] + v[2]); // prints 6
    v[1] = 20; // calls operator[]=
    println(v.y); // prints 20
}