Search code examples
c++polymorphism

Creating a derived class as a subset of superclass


I have created (for purely eductional value, this is not production code, there are libraries out there, more efficient implementations exist) a templated matrix class.

I want to do 2D graphics, so I need a 2D vector. Because of homogenous coordinates this will have three items. Moreover, I want a new constructor, I want someone to have an x and y value and him to be able to construct a 2D point without having to understand what a templated Matrix is and why there are 3 elements in this vector. Therefore, I create a derived class, because that is the only way I know how to do that:

#include "matrix.hpp"

// 3 rows because of homogenous coordinate system.
class Point2D: public Matrix<3, 1>
{
    public:
        Point2D(float x, float y) : Matrix<3, 1>({x, y, 1}) {}
};

Note that the derived class is more of a subset of Matrix<N,M>, it does not add any functionality, instead it restricts the templated type to Matrix<3, 1>.

Now I want to operate on my newly created Point2D:

    Point2D a(-1, 1);
    Matrix<3, 3> translate = {(1, 0, 2,
                               0, 1, -2,
                               0, 0, 1);

    Point2D out = translate*a;

And this is not allowed:

g++ -I2dgfx/inc/ -Imatrix/inc/ -Iinc/ -std=gnu++20 -DDEBUG -Og -ggdb -Wall -Wshadow=local -Werror -Wno-error=unused-variable -Wno-error=unused-but-set-variable -MMD -MP -c 2dgfx/test/point2d.cpp -o obj/test/2dgfx/point2d.cpp.o
2dgfx/test/point2d.cpp: In member function ‘virtual void PointMatrix2D_translation_Test::TestBody()’:
2dgfx/test/point2d.cpp:23:28: error: conversion from ‘Matrix<3, 1>’ to non-scalar type ‘Point2D’ requested
   23 |     Point2D out = translate*a;
      |                   ~~~~~~~~~^~
make: *** [2dgfx/rules.mk:34: obj/test/2dgfx/point2d.cpp.o] Error 1

Which I get, a Matrix<3,1> is not the same as a Point2D. Except I want those two to be exactly equal, I just need that new constructor to hide the fact that this object representing a point in 2D space secretly has 3 elements of which the last is a 1 because of math. So a solution is to modify the Point2D class:

#include "matrix.hpp"

// 3 rows because of homogenous coordinate system.
class Point2D: public Matrix<3, 1>
{
    public:
        Point2D(float x, float y) : Matrix<3, 1>({x, y, 1}) {}
        Point2D(const Matrix<3,1>& mat) : Matrix<3, 1>(mat) {}
};

Which works, but is this the best way? Is there a better way to achieve what I want?


Solution

  • Which works, but is this the best way? Is there a better way to achieve what I want?

    The best way depends on how you intend to use your Point2D.

    If you really want Point2D and Matrix<3,1> to be the same, a possible approach is to make Point2D a type alias and write a make function to create it:

    using Point2D = Matrix<3, 1>;
    Point2D make_point_2d(float x, float y) { return Point2D{x, y, 1}; }
    
    int main() {
        auto const a = make_point_2d(-1, 1);
        Matrix<3, 3> translate = {1, 0, 2, 0, 1, -2, 0, 0, 1};
    
        Point2D out = translate * a;
    }
    

    To keep the design consistent, I'd introduce type aliases and make_* functions for other types too. E.g.:

    using Matrix3x3 = Matrix<3,3>;
    Matrix3x3 make_matrix_3x3(/* ... */) { /*...*/ }
    

    This may work as long as the type aliases you need to introduce are not too many.


    Edit: I'm not really sure having Point2D be (or publicly inherit from) Matrix<3,1> is a good idea. As a user, I'd be quite surprised by the fact this code would compile (and run without causing any assert to fail):

    auto const a = make_point_2d(-1, 1);
    a(2,0);
    
    // 1. Why do I need the second index to access the elements of a Point2D?
    // 2. Why can I access the element at index 2?