#include <iostream>
#include <array>
template <typename T, size_t Rows, size_t Cols>
class Matrix {
protected:
std::array<std::array<T, Cols>, Rows> matrix;
public:
constexpr Matrix() = default;
constexpr explicit Matrix(std::array<std::array<T, Cols>, Rows> matrix) : matrix(std::move(matrix)) { }
consteval Matrix(std::initializer_list<std::initializer_list<T>> matrix) : matrix() {
if (matrix.size() != Rows) {
throw std::invalid_argument("Invalid matrix Rows count");
}
auto current_row = matrix.begin();
for (size_t i = 0; i < matrix.size(); i++, current_row++) {
if (current_row->size() != Cols) {
throw std::invalid_argument("Invalid matrix column count");
}
std::copy(current_row->begin(), current_row->end(), this->matrix[i].begin());
}
}
constexpr auto& operator[](this auto&& self, size_t index) { return self.matrix[index]; }
};
template <typename T, size_t N>
struct Vector : Matrix<T, 1, N> {
using Matrix<T, 1, N>::Matrix;
template <typename U, typename... Us>
consteval explicit Vector(U u, Us... us) {
this->matrix[0] = { u, us... };
}
constexpr auto& operator[](this auto&& self, size_t index) { return self.matrix[0][index]; }
};
template <typename T, typename... U>
Vector(T, U...) -> Vector<T, 1 + sizeof...(U)>;
int main() {
constexpr Matrix<int, 4, 3> matrix {
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 }
};
static_assert(matrix[2][1] == 2);
constexpr Vector v { 1, 2, 3 };
static_assert(v[1] == 2);
}
Here, it's quite easy to write a deduction guide for a vector class to avoid specifying the type and the number of elements, but when it comes to the Matrix
class, it's quite challenging. This is because std::initializer_list::size
for the passed initializer_list is not a constant expression, and I can't deduce the Cols
argument from it.
constexpr Matrix matrix {
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 }
};
constexpr Matrix<double> matrix {
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 }
};
You could add a deduction guide for arrays:
template <class T, size_t R, size_t C>
Matrix(const T(&arr)[R][C]) -> Matrix<T, R, C>;
... and you'll need an extra set of braces to use it:
constexpr Matrix matrix {{
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 },
{ 1, 2, 3 }
}};
Alternatively, replace the constructor taking nested initializer_list
s with constructors taking arrays. That makes your Matrix
class work for types that are moveable but neither copyable nor default constructible and it does not need deduction guides for arrays:
template <typename T, size_t Rows, size_t Cols>
class Matrix {
protected:
std::array<std::array<T, Cols>, Rows> matrix;
constexpr auto to_array(const T (&arr)[Rows][Cols]) {
return [&]<std::size_t... Rs>(std::index_sequence<Rs...>) {
return std::to_array({std::to_array<T>(arr[Rs])...});
}(std::make_index_sequence<Rows>{});
}
constexpr auto to_array(T (&&arr)[Rows][Cols]) {
return [&]<std::size_t... Rs>(std::index_sequence<Rs...>) {
return std::to_array({std::to_array<T>(std::move(arr[Rs]))...});
}(std::make_index_sequence<Rows>{});
}
public:
constexpr Matrix()
requires((Rows == 0 && Cols == 0) || std::is_default_constructible_v<T>)
= default;
constexpr explicit Matrix(const T (&arr)[Rows][Cols])
: matrix(to_array(arr)) {}
constexpr explicit Matrix(T (&&arr)[Rows][Cols])
: matrix(to_array(std::move(arr))) {}
//...
};