不同的模板 class 实现但相同的成员函数

Different template class realizations but same member functions

我有一个模板矩阵class,其中包含一些典型的成员函数,例如inversedeterminantoperator*等。我想重用代码对于 fixed- 和 dynamic 大小矩阵的模板实现中的这些成员函数。 这可能吗?如果是,怎么做?

在下面的代码中,与 Eigen 一样,我使用“-1”作为动态维度。是的,我知道我可以为此使用一个库,但对于我的应用程序,这是不可行的。 由于应用程序 (CUDA) 的性质,无法实现标准功能

是否可以根据模板参数制作具有不同成员变量大小的模板class?例如当Dynamic_Rows = -1和[=20时=],那么数据就是 T **data,否则就是 T data[Rows][Cols].

截至目前,我有一个模板 class 用于动态大小矩阵(下面的示例“minimal”,请注意代码受 errors/mistakes 因为我对“高级”class 模板比较陌生。

但是我想在固定大小的矩阵实例化的情况下有一个固定大小的数据成员变量。

template<class T, int Dynamic_Rows, int Dynamic_Cols>
class CMatrix<T, -1, -1>
{
private:
   size_t n_rows, n_cols;
   T** data;

   void allocate_data();
   void deallocate_data();

public:
   CMatrix(const size_t n_rows, const size_t n_cols);
   CMatrix(const CMatrix& other);
   ~CMatrix();
   CMatrix& operator=(const CMatrix& rhs);
   CMatrix exp() const;
};

例如下面 exp() 函数的代码为

template <class T, int Dynamic_Rows, int Dynamic_Cols>
CMatrix<T, -1, -1> CMatrix<T, -1, -1>::exp() const
{
   CMatrix<T, -1, -1> result(n_rows, n_cols);
   for (size_t i = 0; i < n_rows; i++)
   {
      for (size_t j = 0; j < n_cols; j++)
      {
         result.data[i][j] = exp(result.data[i][j]);
      }
   }
   return result;
}

我现在唯一能想到的同时允许动态和固定大小矩阵的方法是基本上实现 class 的另一个模板 as

template<class T, size_t Rows, size_t Cols>
class CMatrix<T, Rows, Cols>
{
private:
   size_t n_rows = Rows, n_cols = Cols;
   T data[Rows][Cols];

public:
   CMatrix() {}
   CMatrix(const CMatrix& other);
   CMatrix& operator=(const CMatrix& rhs);
   CMatrix exp() const;
};

使用可变参数模板

template<class T, int...> class CMatrix;

但是这样只会重复我的大部分成员函数的代码!

Is it possible....[...] ...when Dynamic_Rows = -1 and Dynamic_Cols = -1, then the data is just T **data, but otherwise its T data[Rows][Cols]?

您可以提供特征类型,其特化为您提供适合 CMatrix

的类型
template<typename T, int Rows, int Cols> struct type_helper final {
    static_assert(Rows >= 0 && Cols >= 0, "Rows and COls must be valid!");
    using type = T[Rows][Cols];
};

template<typename T> struct type_helper<T, -1, -1>  final {
    using type = T**;
};

现在在 class

template<class T, int Dynamic_Rows = -1, int Dynamic_Cols = -1>
class CMatrix /* final */
{
    // get the member Type like this!
    using Type = typename type_helper<T, Dynamic_Rows, Dynamic_Cols>::type;

    Type data;
public:

};

(See a demo online)

您可以根据某些 compile-time 常数使用 std::conditional_t 到 select 一种或另一种类型,而无需复制 class.

然而 - 我曾经做过非常相似的事情,发现使用固定大小的数据结构(std::array 而不是 std::vector)在实际用例中几乎没有任何改进。 固定大小数组(std::arraytype[fixed_size])的一个非常大的优点是编译器能够优化很多操作,但如果数组很大,好处就可以忽略不计了。

此外,您应该考虑 n。 'pronouns' m. 的评论 - 特别是如果您使用的是 GPU(它们往往需要在内存中连续的数据结构)。不使用 T** data,而是使用一个 std::vector<T> 和一个额外的 std::array<size_t, number_of_dimensions> 作为每个维度的大小。然后你可以用这样的东西翻译索引:

using Indices = std::array<size_t, number_of_dimensions>;

template <unsigned int n = number_of_dimensions>
static inline size_t indices_to_offset(Indices const & indices,
                                       Indices const & dimensions){
    if constexpr (n == 0) return 0;
    else {
        size_t const next_index_number = indices[n-1];
        assert(next_index_number < dimensions[n-1]);
        return next_index_number +
            dimensions[n-1]*indices_to_offset<n-1>(indices, dimensions);
    }
}

template <unsigned int n = number_of_dimensions>
static inline Indices offset_to_indices(size_t const offset,
                                        Indices const & dimensions,
                                        Indices indices = Indices()){
    if constexpr (n == 0) return indices;
    else {
        size_t const fast_dimension_size = dimensions[n-1];
        indices[n-1] = offset % fast_dimension_size;
        return offset_to_indices<n-1>(offset/fast_dimension_size , dimensions, indices);
    }
}

这是使用 CRTP 进行此类专业化的一种非常通用的方法。它不要求有一个简单的单一条件可用于 select 数据成员的实现,并保持其余不变。

template <class T, class Impl> class MatrixBase {

   // data is NOT defined here
   Impl exp() const
   {
        Impl& self = static_cast<Impl&>(*this);             // <-- this is magic
        Impl result = self;                                 // <-- this is magic
        for (size_t i = 0; i < n_rows; i++)
        {
            for (size_t j = 0; j < n_cols; j++)
            {
                result.data[i][j] = exp(result.data[i][j]); // <-- data is defined in Impl
            }
        }
        return result;
    }
    // other functions that do not depend on actual type of data
};

template <class T>
class DynamicMatrix : public MatrixBase<T, DynamicMatrix<T>> {
    T** data;
    // define constructors etc here
};

template <class T, int Rows, int Cols>
class StaticMatrix : public MatrixBase<T, StaticMatrix<T, Rows, Cols>> {
    T[Rows][Cols] data;
    // define constructors etc here
};

既然您同时拥有 StaticMatrixDynamicMatrix,您可以根据需要将它们统一到一个别名模板中。

template <class T, int Rows, int Cols>
using CMatrix = std::conditional_t <(Rows >= 0 && Cols >= 0),
                                     StaticMatrix<T, Rows, Cols>,
                                     DynamicMatrix<T>
                                   >;

将我的评论变成答案(使用 Vector 而不是 Matrix):

拆分您的 class,这样您就可以专注于特定的 class 和共同的部分,例如:

// helper to allow variadic constructor without pitfall.
constexpr struct in_place_t{} in_place{};

template <typename T, std::size_t DIM>
class VectorData
{
protected: // You might even make it private and give the common accessor in public part
           // To avoid to use interface not shared with std::vector
    std::array<T, DIM> data;
public:
    VectorData() = default;

    template <typename ... Ts>
    VectorData(in_place_t, Ts&&... args) : data{std::forward<Ts>(args)...} {}
};

template <typename T>
class VectorData<T, std::size_t(-1)>
{
protected: // You might even make it private and give the common accessor in public part
           // To avoid to use interface not shared with std::array
    std::vector<T> data;
public:
    explicit VectorData(std::size_t size) : data(size) {}

    template <typename ... Ts>
    VectorData(in_place_t, Ts&&... args) : data{std::forward<Ts>(args)...} {}
};

然后是公共部分:

template <typename T, std::size_t DIM = std::size_t(-1)>
class Vector : public VectorData<T, DIM>
{
public:
    using VectorData<T, DIM>::VectorData;

    Vector(std::initializer_list<T> ini) : VectorData<T, DIM>(in_place, ini) {}

#if 1 // Those might go in `VectorData` to avoid to use `data` directly
    std::size_t size() const { return this->data.size(); }
    T& operator[](std::size_t i) { return this->data[i]; }
    const T& operator[](std::size_t i) const { return this->data[i]; }
#endif

    // Example of methods which don't care underlying container
    template <std::size_t DIM2>
    Vector& operator += (const Vector<T, DIM2>& rhs) {
        assert(this->size() == rhs.size());
        
        for (std::size_t i = 0; i != this->size(); ++i) {
            (*this)[i] += rhs[i];
        }
        return *this;
    }

    template <std::size_t DIM2>
    friend Vector operator + (Vector lhs, const Vector<T, DIM2>& rhs) {
        return lhs += rhs;
    }
};

Demo