// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // // BaseMatrix.hpp // // Common base class for matrices. Extends the sangi Vector + // Vector_DIM pattern to matrices. // // Design: // - No virtual functions (operator(), rows(), cols() are inline, no SIMD inhibition). // - shared_storage_ can own dynamic storage (for dynamic derived class Matrix). // Static derived classes (StaticMatrix) and non-owning views set // shared_storage_ = nullptr, with data_ pointing to external / stack arrays. // - row_stride / col_stride are held independently so that row-major, // column-major, sub-matrix, and transposed views can all be expressed // by the same type. // - Derived classes Matrix / StaticMatrix set data_, rows_, cols_, // row_stride_, col_stride_ in their constructor (the same structure as // sangi Vector_DIM pointing Vector::elements at its own fixed array). #ifndef SANGI_BASE_MATRIX_HPP #define SANGI_BASE_MATRIX_HPP #include #include #include #include namespace sangi { template class BaseMatrix { public: // Compile-time dimension traits. // -1 = dynamic (size known only at runtime). Derived classes with statically // known dimensions (StaticMatrix) shadow these with their template parameters. // Used by LinAlg algorithms to enable static_assert dimension checks when // both operands carry static sizes. static constexpr std::ptrdiff_t static_rows = -1; static constexpr std::ptrdiff_t static_cols = -1; protected: // Ownership mode: // shared_storage_ != nullptr: BaseMatrix owns dynamic storage (via shared) // shared_storage_ == nullptr: data_ points to external / derived-class private storage (non-owning view) std::shared_ptr shared_storage_; // Pointer for element access (always valid, used regardless of whether shared_storage_ exists) T* data_; // Shape std::size_t rows_; std::size_t cols_; // Strides (element address calculation: data_[i * row_stride_ + j * col_stride_]) // row-major contiguous: row_stride_ = cols_, col_stride_ = 1 // column-major contiguous: row_stride_ = 1, col_stride_ = rows_ // transposed view: swap row_stride_ and col_stride_ // block view: inherit parent strides, offset data_ std::size_t row_stride_; std::size_t col_stride_; public: // Empty construction (for setting a view later via assignment; default value = empty view) BaseMatrix() noexcept : shared_storage_(), data_(nullptr), rows_(0), cols_(0) , row_stride_(0), col_stride_(0) {} protected: // For derived classes only: allocate and own dynamic storage // (used when sangi::Matrix inherits from BaseMatrix in the future) BaseMatrix(std::size_t rows, std::size_t cols) : shared_storage_(rows * cols > 0 ? new T[rows * cols]() : nullptr) , data_(shared_storage_.get()) , rows_(rows), cols_(cols) , row_stride_(cols), col_stride_(1) {} // For derived classes only: view setter (used by Matrix to sync its own storage to the base). // When pointing to external storage (such as StaticMatrix's stack array), pass shared_storage = nullptr. void set_view(std::shared_ptr storage, T* data, std::size_t rows, std::size_t cols, std::size_t row_stride, std::size_t col_stride = 1) noexcept { shared_storage_ = std::move(storage); data_ = data; rows_ = rows; cols_ = cols; row_stride_ = row_stride; col_stride_ = col_stride; } // For derived classes only: lightweight sync that updates only the data ptr without holding shared_storage // (used when StaticMatrix passes the address of its own std::array to the base) void set_view_nonowning(T* data, std::size_t rows, std::size_t cols, std::size_t row_stride, std::size_t col_stride = 1) noexcept { shared_storage_.reset(); data_ = data; rows_ = rows; cols_ = cols; row_stride_ = row_stride; col_stride_ = col_stride; } public: using value_type = T; using size_type = std::size_t; // === public: ctor for non-owning view === // Directly create a view that references external data (corresponds to sangi // Vector(N, p, shared, Step)). BaseMatrix(T* data, std::size_t rows, std::size_t cols, std::size_t row_stride, std::size_t col_stride = 1) noexcept : shared_storage_(), data_(data), rows_(rows), cols_(cols) , row_stride_(row_stride), col_stride_(col_stride) {} // Implicit conversion: implicit construction (non-owning view) from a row-major // contiguous matrix type (Matrix, StaticMatrix, etc.). // Existing sangi::Matrix / StaticMatrix have data()/rows()/cols(), // so this ctor can convert them to a BaseMatrix view. // (Note: for a const M& input, const_cast is used to store a non-const ptr in data_. // Intended for read-only use through const BaseMatrix& parameters.) template requires std::is_same_v && (!std::is_same_v, BaseMatrix>) && requires(const M& m) { { m.data() } -> std::convertible_to; { m.rows() } -> std::convertible_to; { m.cols() } -> std::convertible_to; } BaseMatrix(const M& m) noexcept : shared_storage_() , data_(const_cast(m.data())) , rows_(m.rows()), cols_(m.cols()) , row_stride_(m.cols()), col_stride_(1) {} // === access === [[nodiscard]] size_type rows() const noexcept { return rows_; } [[nodiscard]] size_type cols() const noexcept { return cols_; } [[nodiscard]] size_type row_stride() const noexcept { return row_stride_; } [[nodiscard]] size_type col_stride() const noexcept { return col_stride_; } [[nodiscard]] size_type size() const noexcept { return rows_ * cols_; } [[nodiscard]] const T& operator()(size_type i, size_type j) const noexcept { return data_[i * row_stride_ + j * col_stride_]; } [[nodiscard]] T& operator()(size_type i, size_type j) noexcept { return data_[i * row_stride_ + j * col_stride_]; } [[nodiscard]] const T* data() const noexcept { return data_; } [[nodiscard]] T* data() noexcept { return data_; } // Whether this is a square matrix [[nodiscard]] bool is_square() const noexcept { return rows_ == cols_; } // === Contiguity checks (for fast path) === [[nodiscard]] bool is_row_major_contiguous() const noexcept { return col_stride_ == 1 && row_stride_ == cols_; } [[nodiscard]] bool is_col_major_contiguous() const noexcept { return row_stride_ == 1 && col_stride_ == rows_; } // === View operations (non-owning, retains a reference to the parent storage) === // block(r0, r1, c0, c1): submatrix view for rows [r0, r1), columns [c0, c1) [[nodiscard]] BaseMatrix block(size_type r0, size_type r1, size_type c0, size_type c1) const noexcept { BaseMatrix v(data_ + r0 * row_stride_ + c0 * col_stride_, r1 - r0, c1 - c0, row_stride_, col_stride_); v.shared_storage_ = shared_storage_; // Keep the parent storage's lifetime alive return v; } // transposed(): transposed view (data_ unchanged, strides swapped, rows/cols swapped) [[nodiscard]] BaseMatrix transposed() const noexcept { BaseMatrix v(data_, cols_, rows_, col_stride_, row_stride_); v.shared_storage_ = shared_storage_; return v; } // row_view(i): obtain the i-th row as a 1 x cols view [[nodiscard]] BaseMatrix row_view(size_type i) const noexcept { BaseMatrix v(data_ + i * row_stride_, 1, cols_, 0, col_stride_); v.shared_storage_ = shared_storage_; return v; } // col_view(j): obtain the j-th column as a rows x 1 view [[nodiscard]] BaseMatrix col_view(size_type j) const noexcept { BaseMatrix v(data_ + j * col_stride_, rows_, 1, row_stride_, 0); v.shared_storage_ = shared_storage_; return v; } // === Storage ownership check (for debugging) === [[nodiscard]] bool owns_storage() const noexcept { return static_cast(shared_storage_); } }; } // namespace sangi #endif // SANGI_BASE_MATRIX_HPP