Search code examples
dartoperator-overloadingsubclass

In Dart, how do I let an overloaded operator return the subclass type?


I have two simple classes: Point (which has an x and y) and Point3D (which extends Point and also has a z parameter). I want to override the + operator so that I can add two points. Because the + operator needs to return the class, the method signature for + in Point is different from the signature for + in Point3D, which is not allowed in Dart. I have tried it via an interface:

abstract class PointOperations<T extends Point> {
   T operator +(T p);
}

and implement this in Point and Point3D as follows:

class Point implements PointOperations<Point> {
  final double x, y;

  Point(this.x, this.y);
    
  @override
  Point operator +(Point p) => Point(x + p.x, y + p.y);
}

class Point3D extends Point implements PointOperations<Point3D>{
  final double z;

  Point3D(double x, double y, this.z) : super(x, y);

  @override
  Point3D operator +(Point3D p) => Point3D(x + p.x, y + p.y, z + p.z);
}

I get the compile error The class 'Point3D' cannot implement both 'PointOperations<Point>' and 'PointOperations<Point3D>' because the type arguments are different. I understand that error, but don't understand how I can accomplish what I want without resorting to methods with distinct names (e.g. Point addPoint(Point p) and Point3D addPoint3D(Point3D p)).

I can get this to work using named constructors:

Point.add(Point a, Point b) : x = a.x + b.x, y = a.y + b.y;

and

Point3D.add(Point3D a, Point3D b) : z = a.z + b.z, super(a.x + b.x, a.y + b.y);

but this is not as elegant and certainly is not operator overloading. Is there a way to accomplish that?


Solution

  • I think I figured it out. In Point I define:

    Point operator +(covariant Point p) => Point(x + p.x, y + p.y);
    

    Defining the argument as a covariant is what allows the subclass to then use the argument Point3D without conflict. In Point3D I define:

    @override
    Point3D operator +(Point3D p) => Point3D(x + p.x, y + p.y, z + p.z);
    

    The return type works as it's a subclass of Point.

    Note it is possible to use the following alternative:

    @override
    Point3D operator +(Point p) => Point3D(x + p.x, y + p.y, z + (p as Point3D).z);
    

    where requires you cast the Point p to access z, but which may be useful if one would want to add a Point to a Point3D (which would require extra type checks and is not what I am looking for, but one could imagine doing this and setting z to 0 if p is a Point and not Point3D).