Search code examples
c++inheritancepolymorphismcovariance

How to design an override-safe factory method that allows subclasses to return covariant types?


I have a parent class that is designed to be const-safe, so I want to create "factory" methods that will return an altered version of itself in a newly constructed object. I want to be able to return by value and avoid heap allocation, similar in function to std::string::substr. This works fine in the parent class, but how to I allow a subclass to return override this method to return a covariant type, preferably without pointers or references?

I have a Point class that is provided to me that represents a 2D position:

class Point
{
    double x, y;

public:
    Point() : Point(0, 0)
    { }

    Point(double xpos, double ypos) : x{xpos}, y{ypos}
    { }

    double get_x() const { return x; }
    double get_y() const { return y; }

    virtual Point flip_x() const
    {
        return Point{x, -y};
    }

    virtual Point flip_y() const
    {
        return Point{-x, y};
    }
    ...
};

Notice that flip_x and flip_y are factory methods that return a new flipped Point object by value. This allows me to do something like const Point flipped = p.flip_x() without modifying the contents of const Point p.

I am implementing a subclass to Point called Point3D that supports an additional z depth coordinate. I want to be able to override these flip functions to return a Point3D instead, in order to be consistent with the return type of flip_z:

class Point3D : public Point
{
    double z;

public:
    Point3D() : Point3D(0, 0, 0)
    { }

    Point3D(double xpos, double ypos, double zpos)
    : Point(xpos, ypos), z{zpos}
    { }

    double get_z() const { return z; }

    // *** ERROR: return type is not covariant ***
    Point3D flip_x() const override
    {
        return Point3D{get_x(), -get_y(), -z};
    }

    // *** ERROR: return type is not covariant ***
    Point3D flip_y() const override
    {
        return Point3D{-get_x(), get_y(), -z};
    }
    
    Point3D flip_z() const
    {
        return Point3D{-get_x(), -get_y(), z};
    }
    ...
};

So these errors tell me that the return types are not covariant with the parent methods. As far as I can tell, my only options for using covariant returns are to use references or pointers. I cannot return a reference or pointer to a local variable, and using new to return a heap allocated Point adds unnecessary complexity to memory management. How can I achieve the effect of returning by value/static binding when returning a covariant type, so that the following is still valid:

const Point p{2, 2};
const Point flipped = p.flip_x();     // (2, -2)
const Point3D p3{2, 2, 2};
const Point3D flipped3 = p3.flip_x(); // (2, -2, -2)

Is this possible using this parent class and within these limitations, or will I have to switch to pointers & heap allocation?


Solution

  • If you remove virtual, you might do:

    class Point
    {
        double x, y;
    
    public:
        Point() : Point(0, 0) { }
        Point(double xpos, double ypos) : x{xpos}, y{ypos} { }
    
        double get_x() const { return x; }
        double get_y() const { return y; }
    
        Point flip_x() const { return Point{x, -y}; }
        Point flip_y() const { return Point{-x, y}; }
        // ...
    };
    
    class Point3D : public Point
    {
        double z;
    
    public:
        Point3D() : Point3D(0, 0, 0) { }
        Point3D(double xpos, double ypos, double zpos) : Point(xpos, ypos), z{zpos}     {}
    
        double get_z() const { return z; }
    
        // hides parent ones
        Point3D flip_x() const { return Point3D{get_x(), -get_y(), -z}; }
        Point3D flip_y() const { return Point3D{-get_x(), get_y(), -z}; }
        
        Point3D flip_z() const { return Point3D{-get_x(), -get_y(), z}; }
        ///...
    };