【问题标题】:Why do I get compilation errors instead of substitution failures when overloading operator?为什么在重载运算符时会出现编译错误而不是替换失败?
【发布时间】:2019-02-23 23:29:32
【问题描述】:

我正在编写 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 给了我一堆类似于这个的错误:

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

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

【问题讨论】:

  • 你想让实例化更懒惰。即让 enable_if_t 为您提供 scaled_matrix_type,并且只能访问 enable_if 之外的 ::type。这样编译器就不需要实例化 scaled_matrix_type 除非 enable_if 中的条件为真。

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


【解决方案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) {...}

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

但是这里发生的情况是,两个模板参数都首先被验证,然后然后如果条件被证明为假,则第二个参数被丢弃。

由于将 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_typestruct)。

这种方式matrix_product_type&lt;&gt;::type 除非条件为真,否则不会被访问。

您可能还需要对 operator* 的其他重载进行类似的更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-06
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    相关资源
    最近更新 更多