patterncppMinorCanonical
Simple matrix class C++14
Viewed 0 times
matrixsimpleclass
Problem
I created a simple 4x4 matrix class (column-major). I would like it to be efficient and to use C++14's full capabilities. Can I improve it?
You
#include
class mat4
{
public:
constexpr mat4() noexcept : matrix() {}
constexpr mat4(const std::array &m) noexcept : matrix(m) {}
constexpr mat4(std::array &&m) noexcept : matrix(std::move(m)) {}
constexpr mat4(const mat4 &other) noexcept : matrix(other.matrix) {}
constexpr mat4(mat4 &&other) noexcept : matrix(std::move(other.matrix)) {}
constexpr bool operator==(const mat4 &other) const noexcept
{
for (size_t i = 0; i operator==(other));
}
constexpr mat4& operator+=(const mat4 &other) noexcept
{
for (size_t i = 0; i matrix, other.matrix);
return *this;
}
constexpr float& operator[](size_t index) { return const_cast(static_cast&>(matrix)[index]); }
constexpr float operator[](size_t index) const { return matrix[index]; }
void print() const noexcept
{
printf("\n");
printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[0], matrix[4], matrix[8], matrix[12]);
printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[1], matrix[5], matrix[9], matrix[13]);
printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[2], matrix[6], matrix[10], matrix[14]);
printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[3], matrix[7], matrix[11], matrix[15]);
}
private:
std::array matrix;
};
constexpr const mat4 operator+(mat4 lhs, const mat4 &rhs) noexcept
{
lhs += rhs;
return lhs;
}
constexpr const mat4 operator*(const mat4 &lhs, const mat4 &rhs) noexcept
{
mat4 result;
for (size_t i = 0; i < 4; ++i)
{
for (size_t j = 0; j < 4; ++j)
{
for (size_t k = 0; k < 4; ++k) {
result[i + 4 * j] += lhs[i + 4 * k] * rhs[k + 4 * j];
}
}
}
return result;
}
constexpr const mat4 operator*(mat4 lhs, float scalar) noexcept
{
lhs *= scalar;
return lhs;
}You
Solution
I'm not sure how I feel about you hardcoding that this should be a 4x4 matrix of floats, and that it should be column major. It seems straightforward enough to make a templated class like so
It might also be worthwhile to have a boolean template parameter for whether or not it should be row or column major; in my experience many applications assume something is row major, and interfacing with your application might yield some unexpected issues.
If you do so, then I think adding a constructor to switch row/column major would be worthwhile as well.
I personally like using
I also generally assume that
Don't use a member function like
You should implement iterators for your matrix as well. Whether you make them row-major or column-major shouldn't matter, but be consistent (assuming you make it possible to switch between orientations). No matter the orientation, the iteration order should be the same.
You should add some improved matrix multiplication algorithms (or, ideally, use a library that does it for you). With some SFINAE wizardry you can probably make a vectorized version, a tiled version, etc. Probably only necessary if you actually make it possible to use larger matrices, although vectorization will probably still be a bit faster even for a small one.
template
class Matrix {
...
};
using mat4 = Matrix;It might also be worthwhile to have a boolean template parameter for whether or not it should be row or column major; in my experience many applications assume something is row major, and interfacing with your application might yield some unexpected issues.
If you do so, then I think adding a constructor to switch row/column major would be worthwhile as well.
I personally like using
operator() instead of operator[] for getting a value at a given row and column; I feel like in general it shouldn't be up to the user to manually calculate the 1d index. Something along the lines of this:constexpr const T& operator()(size_t row, size_t col) const noexcept
{
return matrix[col * numCols + row];
}
constexpr T& operator()(size_t row, size_t col) noexcept
{
return matrix[col * numCols + row];
}I also generally assume that
at() (or similar) will be provided if indexing (or operator()-like access) is provided.Don't use a member function like
print(); instead implement operator to refer to member functions; just use them. Then your operator!= becomes return !operator==(other).operator+= and operator*= can be done using std::transform.You should implement iterators for your matrix as well. Whether you make them row-major or column-major shouldn't matter, but be consistent (assuming you make it possible to switch between orientations). No matter the orientation, the iteration order should be the same.
You should add some improved matrix multiplication algorithms (or, ideally, use a library that does it for you). With some SFINAE wizardry you can probably make a vectorized version, a tiled version, etc. Probably only necessary if you actually make it possible to use larger matrices, although vectorization will probably still be a bit faster even for a small one.
Code Snippets
template <typename T, size_t numRows, size_t numCols>
class Matrix {
...
};
using mat4 = Matrix<float, 4, 4>;constexpr const T& operator()(size_t row, size_t col) const noexcept
{
return matrix[col * numCols + row];
}
constexpr T& operator()(size_t row, size_t col) noexcept
{
return matrix[col * numCols + row];
}Context
StackExchange Code Review Q#136541, answer score: 2
Revisions (0)
No revisions yet.