为什么在重载运算符时出现编译错误而不是替换失败?

时间:2019-02-23 23:29:33

标签: c++ c++11 templates template-meta-programming sfinae

我正在编写一个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;
}

C语给我带来了很多与此类似的错误:

  错误:错误:类型'int'不能在'::'之前使用,因为它没有   成员       static_assert(Matrix1 :: number_of_cols == Matrix2 :: number_of_rows);

在我看来,这应该是替换失败,而不是编译错误。有什么作用?

1 个答案:

答案 0 :(得分:1)

从这段代码来看:

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

如果第一个模板参数为false,您似乎希望第二个模板参数被完全丢弃(不进行任何验证)。

但是这里发生的是,如果条件成立,则首先验证两个模板参数,然后丢弃第二个参数

由于将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*的其他重载进行类似的更改。