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?
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?