Почему я получаю ошибки компиляции вместо ошибок подстановки при перегрузке оператора? - PullRequest
0 голосов
/ 24 февраля 2019

Я пишу иерархию классов Matrix, но у меня возникают проблемы при перегрузке операторов умножения для умножения матрицы на матрицу и масштабирования матрицы скаляром.Я использую std :: enable_if_t, чтобы выяснить, какой оператор должен вызываться в зависимости от того, являются ли умножаемые типы матричными или нет.Является ли переменная матрицей, определяется проверкой, наследуется ли она от пустого базового класса matrix_tag.Ниже приведен довольно минимальный воспроизводимый пример кода:

#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 дает мне несколько ошибок, подобных этой:

error: тип 'int' не может быть использовандо '::', потому что у него нет членов static_assert (Matrix1 :: number_of_cols == Matrix2 :: number_of_rows);

Мне кажется, что это должна быть ошибка замещения, а не ошибка компиляции,Что дает?

1 Ответ

0 голосов
/ 24 февраля 2019

Судя по этому коду:

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) {...}

Вы, вероятно, ожидаете, что второй аргумент шаблона будет полностью отброшен (без какой-либо проверки), если первый аргумент шаблона будет ложным.

Но здесь происходит то, что оба аргумента шаблона сначала проверяются, а , а затем второй аргумент отбрасывается, если условие оказалось ложным.

Поскольку используется Matrix_product_type с нематричнымПараметры шаблона - это серьезная ошибка (а не ошибка, обнаруживаемая SFINAE), вы получаете серьезную ошибку до того, как enable_if_t получит шанс.


Самое простое решение - сделать второепараметр шаблона для enable_if_t действителен независимо от того, является ли условие истинным или нет.

Один из возможных способов сделать это:

template <typename Matrix1, typename Matrix2>
typename std::enable_if_t<(Is_matrix<Matrix1> && Is_matrix<Matrix2>),
                 matrix_product_type<Matrix1, Matrix2>>::type
operator*(const Matrix1 &A, const Matrix2 &B)

Обратите внимание, что я заменил Matrix_product_type (типпсевдоним) с matrix_product_type (a struct).

Таким образом, matrix_product_type<>::type недоступен, если условие не выполняется.

Возможно, вам придется внести аналогичные изменения в другие перегрузкиoperator*.

...