samedi 23 février 2019

Why do I get compilation errors instead of substitution failures when overloading operator?

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