【问题标题】:Making a function that can accept different types of templated class inputs制作一个可以接受不同类型的模板化类输入的函数
【发布时间】:2021-09-22 17:59:49
【问题描述】:

我有一个应用程序需要减少矩阵类的内存使用量。我的许多矩阵都是对称的,因此只需要 N(N+1)/2 内存而不是 N2。我不能简单地拥有一个派生自我的Matrix 类的MatrixSym 类,因为子类总是使用比基类更多的内存。我已经定义了一个 MatrixAbstract 类,两个类都从该类继承,但是我无法让矩阵乘法函数对它们都起作用。

我的问题

如何定义一个C = multiply(A,B) 函数,该函数接受MatrixMatrixSym 对象用于任何输入/输出?我的最小工作示例并不是那么简单,因为它包含四个multiply 的定义,它们都使用相同的代码并且应该合并为一个。

要求

  • 仅静态分配
  • 矩阵元素必须使用std::array
  • 最少的重复代码:我有许多矩阵运算,而 MatrixSym 唯一不同的是它存储和访问底层 matVals 数组中元素的方式。
#include <array> 
#include <iostream> 

template<unsigned int ROWS, unsigned int COLS, unsigned int NUMEL>
class MatrixAbstract
{
private:
    static constexpr unsigned int numel = NUMEL; 
    std::array<double, NUMEL> matVals;   // The array of matrix elements
public:
    MatrixSuperclass(){}
    virtual unsigned int index_from_rc(const unsigned int& row, const unsigned int& col) const = 0;
    // get the value at a given row and column
    double get_value(const int& row, const int& col) const
    {
        return this->matVals[this->index_from_rc(row,col)];
    }
    // set the value, given the row and column
    void set_value(const int& row, const int& col, double value)
    {
        this->matVals[this->index_from_rc(row,col)] = value;
    }

};

template<unsigned int ROWS, unsigned int COLS, unsigned int NUMEL = ROWS*COLS>
class Matrix : public MatrixSuperclass<ROWS, COLS, NUMEL>
{
public:
    Matrix(){}
    // get the linear index in matVals corresponding to a row,column input
    unsigned int index_from_rc(const unsigned int& row, const unsigned int& col) const {
        return row*COLS + col; 
    }
};

template<unsigned int ROWS, unsigned int COLS = ROWS, unsigned int NUMEL = ROWS*(ROWS+1)/2> 
class MatrixSym : public MatrixSuperclass<ROWS, COLS, NUMEL>
{
public:
    MatrixSym(){}
    // get the linear index in matVals corresponding to a row,column input (Symmetric matrix)
    unsigned int index_from_rc(const unsigned int& row, const unsigned int& col) const {
        unsigned int z;
        return ( ( z = ( row < col ? col : row ) ) * ( z + 1 ) >> 1 ) + ( col < row ? col : row ) ;
    }
};

// THE FOLLOWING FOUR FUNCTIONS ALL USE THE EXACT SAME CODE, ONLY INPUT/OUTPUT TYPES CHANGE

// Multiply a Matrix and Matrix and output a Matrix
template<unsigned int ROWS, unsigned int COLS, unsigned int INNER>
Matrix<ROWS,COLS> multiply (Matrix<ROWS,INNER>& inMatrix1, Matrix<INNER,COLS>& inMatrix2) {
    Matrix<ROWS,COLS> outMatrix;
    for (unsigned int r = 0; r < ROWS; r++) {
        for (unsigned int c = 0; c < COLS; c++) {
            double val = 0.0;
            for (unsigned int rc = 0; rc < INNER; rc++) {
                val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
            }
            outMatrix.set_value(r,c,val);
        }
    }
    return outMatrix;
}

// Multiply a Matrix and MatrixSym and output a Matrix
template<unsigned int ROWS, unsigned int COLS, unsigned int INNER>
Matrix<ROWS,COLS> multiply (Matrix<ROWS,INNER>& inMatrix1, MatrixSym<INNER,COLS>& inMatrix2) {
    Matrix<ROWS,COLS> outMatrix;
    for (unsigned int r = 0; r < ROWS; r++) {
        for (unsigned int c = 0; c < COLS; c++) {
            double val = 0.0;
            for (unsigned int rc = 0; rc < INNER; rc++) {
                val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
            }
            outMatrix.set_value(r,c,val);
        }
    }
    return outMatrix;
}

// Multiply a MatrixSym and Matrix and output a Matrix
template<unsigned int ROWS, unsigned int COLS, unsigned int INNER>
Matrix<ROWS,COLS> multiply (MatrixSym<ROWS,INNER>& inMatrix1, Matrix<INNER,COLS>& inMatrix2) {
    //MatrixSym<ROWS,COLS> outMatrixSym;
    Matrix<ROWS,COLS> outMatrix;
    for (unsigned int r = 0; r < ROWS; r++) {
        for (unsigned int c = 0; c < COLS; c++) {
            double val = 0.0;
            for (unsigned int rc = 0; rc < INNER; rc++) {
                val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
            }
            outMatrix.set_value(r,c,val);
        }
    }
    return outMatrix;
}

// Multiply a MatrixSym and MatrixSym and output a MatrixSym
template<unsigned int ROWS, unsigned int COLS, unsigned int INNER>
MatrixSym<ROWS,COLS> multiply (MatrixSym<ROWS,INNER>& inMatrix1, MatrixSym<INNER,COLS>& inMatrix2) {
    //MatrixSym<ROWS,COLS> outMatrixSym;
    MatrixSym<ROWS,COLS> outMatrix;
    for (unsigned int r = 0; r < ROWS; r++) {
        for (unsigned int c = 0; c < COLS; c++) {
            double val = 0.0;
            for (unsigned int rc = 0; rc < INNER; rc++) {
                val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
            }
            outMatrix.set_value(r,c,val);
        }
    }
    return outMatrix;
}

int main()
{
    Matrix<3,3> A;
    MatrixSym<3> S;
    Matrix<3,3> AtimesA = multiply(A,A);
    Matrix<3,3> AtimesS = multiply(A,S);
    Matrix<3,3> StimesA = multiply(S,A);
    MatrixSym<3> StimesS = multiply(S,S);
    
    // Make sure that symmetric matrix S is indeed smaller than A
    std::cout << "sizeof(A)/sizeof(double) = " << sizeof(A)/sizeof(double) << std::endl;
    std::cout << "sizeof(S)/sizeof(double) = " << sizeof(S)/sizeof(double) << std::endl;
    return 0;
}

输出:

sizeof(A)/sizeof(double) = 9
sizeof(S)/sizeof(double) = 15

我尝试过的

  • 如果我尝试让函数使用MatrixAbstract 作为参数,我需要使用模板参数,因为我不能将NUMEL 作为模板参数。此外,我无法为返回值实例化 MatrixAbstract
  • 我不知道如何为MatrixMatrixSym 模板化函数。我的直觉是这是解决它的方法,但我不明白如何为模板化类输入模板化函数,但仍然能够使用 ROWSCOLS 作为模板参数。
template<class InMatrix1Type, class InMatrix2Type, class OutMatrixType, unsigned int ROWS, unsigned int COLS, unsigned int INNER>
OutMatrixType<ROWS,COLS> multiply (InMatrix1Type<ROWS,INNER>& inMatrix1, InMatrix2Type<INNER,COLS>& inMatrix2)

给我一​​个以

开头的编译器错误
error: ‘OutMatrixType’ is not a template
 OutMatrixType<ROWS,COLS> multiply (InMatrix1Type<ROWS,INNER>& inMatrix1, InMatrix2Type<INNER,COLS>& inMatrix2)
 ^~~~~~~~~~~~~

【问题讨论】:

  • 您是否使用部分模板专业化 - en.wikipedia.org/wiki/Partial_template_specialization
  • 继承在这里是错误的工具。将您的 multiply 函数编写为一个模板,该模板接受两个类型参数,一个用于每个矩阵。编写一个“普通”Matrix 类,它提供(如果我正在快速浏览您的代码)get_value(unsigned, unsigned) 返回对这些索引值的引用。编写第二个SymmetricMatrix 类,该类提供get_value(unsigned, unsigned),该类返回对这些索引处的值的引用。 multiply 不关心它得到的值是来自完整矩阵还是来自对称(较小)矩阵。
  • 除非您是出于学习目的,否则请考虑使用现有的矩阵库。
  • @PeteBecker、MatrixSymmetricMatrix 几乎相同,只是它们存储数据的方式不同。对于这种情况,继承不是最好的方法吗?我的代码中的每个矩阵都有许多其他成员函数和属性,而且我还有第三种专用矩阵类型,它使用更少的存储空间(与 SymmetricMatrix 不同。我不想复制和粘贴每种矩阵类型的代码。
  • 我使用旧的 Matrix 库 Newmat11。 SymmetricMatrix 使用的内存确实比 Matrix 少,但它使用动态内存分配。

标签: c++ templates matrix


【解决方案1】:

函数multiply 可以是一个模板,它采用两种(可能不同的)参数类型,只要它们支持适当的接口。像这样:

template <unsigned ROWS, unsigned COLS>
struct Matrix {
    double get_value(int row, int col) const;
    void set_value(int row, int col, double value);
};

template <unsigned ROWS, unsigned COLS>
struct Symmetric_Matrix {
    double get_value(int row, int col) const;
    void set_value(int row, int col, double value);
};

template <unsigned ROWS, unsigned COLS, unsigned INNER,
    template <unsigned, unsigned> class M1,
    template <unsigned, unsigned> class M2>
Matrix<ROWS, COLS> multiply(const M1<ROWS, INNER>& m1,
    const M2<INNER, COLS>& m2) {
    Matrix<ROWS, COLS> result;
    for (unsigned r = 0; r < ROWS; ++r)
        for (unsigned c = 0; c < COLS; ++c) {
            double val = 0.0;
            for (unsigned rc = 0; rc < INNER; ++rc)
                val += m1.get_value(r, rc) * m2.get_value(rc, c);
        result.set_value(r, c, val);
        }
    return result;
}

M1M2是模板模板参数;也就是说,它们是用作multiply 的模板参数的模板;当函数被调用时,编译器会计算出它们的类型,以及ROWCOLINNER的对应值。

int main() {
    Matrix<3, 5> res;

    Matrix<3, 4> x1;
    Matrix<4, 5> x2;
    res = multiply(x1, x2);

    Symmetric_Matrix<3, 4> sx1;
    Symmetric_Matrix<4, 5> sx2;
    res = multiply(sx1, sx2);

    res = multiply(x1, sx2);
    res = multiply(sx1, x2);

    return 0;
}

当然,真正的代码会提供get_valueset_value 的实现,但是乘法代码并不关心它们是如何实现的,所以使用两种不同的类型就可以了。

【讨论】:

    【解决方案2】:

    因此,我通过删除与矩阵相关的特定代码来消除您原始问题中的很多噪音,并尝试将其归结为所要求的内容。

    我在这里所做的是坚持使用基类,而是允许用户为 multiply 函数指定他们想要的返回类型。

    我没有对此进行大量测试,但它似乎可以满足您的需求。

    为了减少multiply 函数中非类型模板参数的数量,我为rowcolnumel 添加了一些getter。你当然可以废弃这些并回到你之前的状态,但是这些成员函数将允许你 assert 传入的参数是正确的。

    话虽如此,正如@Pete Becker 所提到的,您也可以在没有继承的情况下完成此操作。阅读他的评论以获取更多信息。

    这不是一个完整的示例,但可以帮助您找到最终的解决方案。

    class MatrixBase {
    public: 
        virtual double get_value( int row, int column ) const = 0;
        virtual void set_value( int row, int column, double value ) const = 0;
        virtual std::uint32_t get_rows( ) const = 0;
        virtual std::uint32_t get_cols( ) const = 0;
        virtual std::uint32_t get_numel( ) const = 0;
        virtual ~MatrixBase( ) = default;
    };
    
    template<std::uint32_t Rows, std::uint32_t Cols, std::uint32_t Numel>
    class Matrix : public MatrixBase {
    public:
        double get_value( int row, int column ) const override { 
            return 1.0; 
        }
    
        void set_value( int row, int column, double value ) const override { }
    
        std::uint32_t get_rows( ) const override {
            return Rows;
        };
    
        std::uint32_t get_cols( ) const override {
            return Cols;
        }
    
        std::uint32_t get_numel( ) const override {
            return Numel;
        }
    
    private:
        std::array<double, Numel> values_{ };
    };
    
    template<std::uint32_t Rows, std::uint32_t Cols, std::uint32_t Numel>
    class MatrixSum : public MatrixBase {
    public:
        double get_value( int row, int column ) const override {
            return 1.0;
        }
    
        void set_value( int row, int column, double value ) const override { }
    
        std::uint32_t get_rows( ) const override {
            return Rows;
        };
    
        std::uint32_t get_cols( ) const override {
            return Cols;
        }
    
        std::uint32_t get_numel( ) const override {
            return Numel;
        }
    
    private:
        std::array<double, Numel> values_{ };
    };
    
    
    template<typename T, std::uint32_t Inner>
    static T multiply( const MatrixBase& m1, const MatrixBase& m2 ) {
        static_assert( std::is_base_of_v<MatrixBase, T>, 
            "Return type must derive from MatrixBase" );
    
        static_assert( std::is_default_constructible_v<T>, 
            "Type must be default constructable" );
    
        T out{ };
    
        // Get the values.
        const auto m1_values{ m1.get_value( 1, 0 ) };
        const auto m2_values{ m2.get_value( 1, 0 ) };
    
        // Set the values on the new matrix.
        out.set_value( 1, 0, m1_values * m2_values );
    
        return out;
    }
    
    int main( ) {
        MatrixSum<10, 10, 10> matrix_sum{ };
        Matrix<10, 10, 10> matrix{ };
    
        auto m{ multiply<Matrix<10, 10, 10>, 10>( matrix_sum, matrix ) };
        auto ms{ multiply<MatrixSum<10, 10, 10>, 10>( matrix, matrix_sum ) };
    }
    

    或者,如果您使用C++20,您可以只定义一个带有requires 子句的concept,您可以在其中指定传入类型必须具有的接口。因此,使用上面的定义可能看起来像这样。

    template<typename T>
    concept Mat = std::is_default_constructible_v<T> && 
    requires( T m, int row, int col, double val ) {
        { m.get_rows( ) } -> std::same_as<std::uint32_t>;
        { m.get_cols( ) } -> std::same_as<std::uint32_t>;
        { m.get_numel( ) } -> std::same_as<std::uint32_t>;
        { m.get_value( row, col ) } -> std::same_as<double>;
        m.set_value( row, col, val );
    };
    
    template<std::uint32_t Inner, Mat T1, Mat T2, Mat T3>
    static T1 multiply( const T2& m1, const T3& m2 ) {
        T1 out{ };
    
        // Get the values.
        const auto m1_values{ m1.get_value( 1, 0 ) };
        const auto m2_values{ m2.get_value( 1, 0 ) };
    
        // Set the values on the new matrix.
        out.set_value( 1, 0, m1_values * m2_values );
    
        return out;
    }
    

    使用这种方法,您可以根据需要完全删除 MatrixBase class,并将 multiply 函数限制为公开所需功能的类型。

    【讨论】:

    • 谢谢@Wbuck,这里有很多好主意我可以使用。这不是我正在寻找的最终解决方案(我不想每次调用multiply 和其他矩阵函数时都必须指定模板参数,因为这需要我更改很多现有代码)但这绝对让我更近了。
    【解决方案3】:

    @PeteBecker 的回答是我所问问题的最佳解决方案,但我也会在此处发布我认为我将用作另一个解决方案的解决方案,因为它利用了两个对称矩阵的乘法可以使用的事实操作比标准矩阵乘法少,而且我仍然想使用基类,因此我不必在 MatrixMatrixSym 之间复制代码(它们共享的成员函数比 get_valueset_value 更多)

    #include <array> 
    #include <iostream> 
    
    template<std::uint8_t ROWS, std::uint8_t COLS, std::uint16_t STORAGE_SIZE>
    class MatrixBase {
    private:
        virtual std::uint16_t index_from_rc(const std::uint8_t& row, const std::uint8_t& col) const = 0;
        std::array<double, STORAGE_SIZE> values_{ };
    public: 
        // get the value at a given row and column
        double get_value(const std::uint8_t& row, const std::uint8_t& col) const
        {
            return this->values_[this->index_from_rc(row,col)];
        }
        // set the value, given the row and column
        void set_value(const std::uint8_t& row, const std::uint8_t& col, double& value)
        {
            this->values_[this->index_from_rc(row,col)] = value;
        }
    };
    
    template<std::uint8_t ROWS, std::uint8_t COLS>
    class Matrix : public MatrixBase<ROWS, COLS, ROWS*COLS> {
    private:
        std::uint16_t index_from_rc(const std::uint8_t& row, const std::uint8_t& col) const {
            return row*COLS + col; 
        }
    };
    
    // Symmetric matrix "MatrixSym": must be square as well, so only need to specify ROWS, since ROWS = COLS 
    template<std::uint8_t ROWS>
    class MatrixSym : public MatrixBase<ROWS, ROWS, ROWS*(ROWS+1)/2> {
    private:
        std::uint16_t index_from_rc(const std::uint8_t& row, const std::uint8_t& col) const {
            std::uint8_t z;
            return ( ( z = ( row < col ? col : row ) ) * ( z + 1 ) >> 1 ) + ( col < row ? col : row ) ;
        }
    };
    
    // General function to multiply a MatrixBase and MatrixBase and output a Matrix
    template<std::uint8_t ROWS, std::uint8_t COLS, std::uint8_t INNER, std::uint16_t STORAGE_SIZE1, std::uint16_t STORAGE_SIZE2>
    Matrix<ROWS,COLS> operator*( const MatrixBase<ROWS,INNER,STORAGE_SIZE1>& inMatrix1, const MatrixBase<INNER,COLS,STORAGE_SIZE2>& inMatrix2){
        std::cout << "Multiplying two MatrixBase objects and outputting a Matrix" << std::endl;
        Matrix<ROWS,COLS> outMatrix;
        for (std::uint8_t r = 0; r < ROWS; r++) {
            for (std::uint8_t c = 0; c < COLS; c++) {
                double val = 0.0;
                for (std::uint8_t rc = 0; rc < INNER; rc++) {
                    val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
                }
                outMatrix.set_value(r,c,val);
            }
        }
        return outMatrix;
    }
    
    // Special case function: multiply a MatrixSym and MatrixSym and output a MatrixSym
    template<std::uint8_t ROWS>
    MatrixSym<ROWS> operator*( const MatrixSym<ROWS>& inMatrix1, const MatrixSym<ROWS>& inMatrix2){
        std::cout << "Multiplying two MatrixSym objects and outputting a MatrixSym" << std::endl;
        MatrixSym<ROWS> outMatrix;
        for (std::uint8_t r = 0; r < ROWS; r++) {
            for (std::uint8_t c = r; c < ROWS; c++) { // improve efficiency by starting c at r (instead of 0) for symmetric matrices
                double val = 0.0;
                for (std::uint8_t rc = 0; rc < ROWS; rc++) {
                    val += inMatrix1.get_value(r,rc)*inMatrix2.get_value(rc,c);
                }
                outMatrix.set_value(r,c,val);
            }
        }
        return outMatrix;
    }
    
    int main( ) {
        MatrixSym<3> S;
        Matrix<3,3> A;
    
        std::cout << "sizeof(A) = " << sizeof(A) << std::endl;
        std::cout << "sizeof(S) = " << sizeof(S) << std::endl;
    
        std::cout << "Calculating A*A" << std::endl;
        auto AtimesA = A*A; // auto = Matrix
        std::cout << "Calculating S*A" << std::endl;
        auto StimesA = S*A; // auto = Matrix
        std::cout << "Calculating A*S" << std::endl;
        auto AtimesS = A*S; // auto = Matrix
        std::cout << "Calculating S*S" << std::endl;
        auto StimesS = S*S; // auto = MatrixSym
        std::cout << "Calculating S*S*A" << std::endl;
        auto StimesStimesA = S*S*A; // auto = MatrixSym
    }
    

    哪个输出:

    sizeof(A) = 80
    sizeof(S) = 56
    Calculating A*A
    Multiplying two MatrixBase objects and outputting a Matrix
    Calculating S*A
    Multiplying two MatrixBase objects and outputting a Matrix
    Calculating A*S
    Multiplying two MatrixBase objects and outputting a Matrix
    Calculating S*S
    Multiplying two MatrixSym objects and outputting a MatrixSym
    Calculating S*S*A
    Multiplying two MatrixSym objects and outputting a MatrixSym
    Multiplying two MatrixBase objects and outputting a Matrix
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多