I am writing a Matrix class hierarchy, but get in trouble when overloading multiplication operators for multiplication of a matrix with a matrix, and scaling of a matrix by a scalar. I use std::enable_if_t to disambiguate which operator should get called depending on whether the types to be multiplied are matrices or not. Whether a variable is a matrix is determined by checking whether it inherits from an empty base class matrix_tag. Below is a fairly minimal reproducible sample of the code:
#include <type_traits>
#include <vector>
// Forward declaration
template <typename T, size_t N, size_t M> class Matrix;
// Empty base class for all matrices, to enable checking whether a type is a
// matrix
struct matrix_tag {};
template <typename MatrixType> struct is_matrix {
static constexpr bool value =
std::is_base_of<matrix_tag, MatrixType>::value;
};
template <typename MatrixType>
constexpr bool Is_matrix = is_matrix<MatrixType>::value;
// Helper type function: The result of multiplying two generic types
template <typename T1, typename T2> struct product_type {
using type = decltype(std::declval<T1>() * std::declval<T2>());
};
// Convenience wrapper
template <typename T1, typename T2>
using Product_type = typename product_type<T1, T2>::type;
// Compile time dispatch for the result of matrix multiplications
template <typename Matrix1, typename Matrix2> struct matrix_product_type {
static constexpr size_t N = Matrix1::number_of_rows;
static constexpr size_t M = Matrix2::number_of_cols;
static_assert(Matrix1::number_of_cols == Matrix2::number_of_rows);
using element_type = Product_type<typename Matrix1::element_type,
typename Matrix2::element_type>;
using type = Matrix<element_type, N, M>;
};
// Convenience wrapper
template <typename Matrix1, typename Matrix2>
using Matrix_product_type =
typename matrix_product_type<Matrix1, Matrix2>::type;
// Compile time dispatch for Matrix scaling
template <typename MatrixType, typename T> struct scaled_matrix_type {
static constexpr size_t N = MatrixType::number_of_rows;
static constexpr size_t M = MatrixType::number_of_cols;
using element_type = Product_type<typename MatrixType::element_type, T>;
using type = Matrix<element_type, N, M>;
};
// Convenience wrapper
template <typename MatrixType, typename T>
using Scaled_matrix_type = typename scaled_matrix_type<MatrixType, T>::type;
// Class definition
template <typename T, size_t N, size_t M> class Matrix : public matrix_tag {
public:
// Types
using element_type = T;
// Traits
static constexpr size_t number_of_rows = N;
static constexpr size_t number_of_cols = M;
// Default constructor
Matrix() : elements_(N * M, 0) {}
// Public access functions
virtual const T &operator()(size_t row, size_t col) const {
return elements_[row * number_of_cols + col];
}
virtual T &operator()(size_t row, size_t col) {
return const_cast<T &>(
(*static_cast<const Matrix *>(this))(row, col));
}
private:
// Element storage
std::vector<T> elements_;
};
// Scaling
// Returns a new matrix with element_type reflecting the result of
// elementwise multiplication
template <typename MatrixType, typename T>
std::enable_if_t<(Is_matrix<MatrixType> && !Is_matrix<T>),
Scaled_matrix_type<MatrixType, T>>
operator*(const MatrixType &A, const T &x) {
typename scaled_matrix_type<MatrixType, T>::type B = A;
for (size_t i = 0; i != B.number_of_rows; ++i) {
for (size_t j = 0; j != B.number_of_rows; ++j) {
B(i, j) *= x;
}
}
return B;
}
template <typename T, typename MatrixType>
std::enable_if_t<(!Is_matrix<T> && Is_matrix<MatrixType>),
Scaled_matrix_type<MatrixType, T>>
operator*(const T &x, const MatrixType &A) {
return A * x;
}
// Matrix multiplication
template <typename Matrix1, typename Matrix2>
std::enable_if_t<(Is_matrix<Matrix1> && Is_matrix<Matrix2>),
Matrix_product_type<Matrix1, Matrix2>>
operator*(const Matrix1 &A, const Matrix2 &B) {
typename matrix_product_type<Matrix1, Matrix2>::type C;
for (size_t i = 0; i != A.number_of_rows; ++i) {
for (size_t j = 0; j != B.number_of_cols; ++j) {
for (size_t k = 0; k != A.number_of_cols; ++k) {
C(i, j) += A(i, k) * B(k, j);
}
}
}
return C;
}
int main() {
Matrix<double, 4, 3> A{};
Matrix<float, 3, 2> B{};
auto C = A * B;
}
Clang gives me a bunch of errors similar to this one:
error: type 'int' cannot be used prior to '::' because it has no members static_assert(Matrix1::number_of_cols == Matrix2::number_of_rows);
It seems to me that this should be a substitution failure rather than a compile error. What gives?
Aucun commentaire:
Enregistrer un commentaire