在使用奇怪循环模板模式(CRTP)时计算多维数组的线性索引



我正在写一个多维数组类在c++中,并希望有它的静态和动态内存版本。根据我以前的一篇文章的答案,我使用CRTP定义了一个基类,然后可以访问派生类中具有连续内存的各自的数据容器(std::array和std::vector)。

我希望在基类本身中实现多维下标操作符(例如访问4x3矩阵的(1,2)项,该矩阵的12个条目连续存储在内存中),以避免代码重复。注意,我之所以选择下标操作符,是因为它是最简单的类方法,可以说明我希望实现的目标。我试图将@Jarod42给出的多维下标线性化解决方案应用于我的CRTP案例。但是,我不确定如何在派生类中存储数组维度,以便在计算线性化索引时在基类中推断它们。我希望下面的代码能清楚地说明我的问题(我正在用c++ 20编译):

#include <array>
#include <cassert>
#include <vector>
/** Multi-dimensional Array Base Class with CRTP. */
template <class derived, class T>
class MultiDimArray
{
private:
constexpr derived& Derived() noexcept { return static_cast<derived&>(*this); }
constexpr const derived& Derived() const noexcept { return static_cast<const derived&>(*this); }
protected:
constexpr MultiDimArray() {}
public:
// I've butchered the syntax in the following subscript operators, but I hope it captures what I intend to do.
constexpr const T& operator()(HookTypeToPack<Derived().Dimensions, std::size_t> ...Is) const {
return *(this->Derived()[ComputeIndex<Derived().Dimensions...>(Is...)]);
}
constexpr T& operator()(HookTypeToPack<Derived().Dimensions, std::size_t> ...Is) {
return *(this->Derived()[ComputeIndex<Derived().Dimensions...>(Is...)]);
}
};
/** Static Multi-dimensional Array Class */
template <class T, std::size_t ...Dims>
class StaticMultiDimArray : public std::array<T, (Dims * ...)>,
public MultiDimArray<StaticMultiDimArray<T, (Dims * ...)>, T>
{
private:
constexpr static std::size_t nEntries = (Dims * ...);
constexpr static std::tuple<HookTypeToPack<Dims, std::size_t>...> Dimensions = std::make_tuple(Dims...);
friend MultiDimArray<StaticMultiDimArray<T, (Dims * ...)>, T>;
public:
constexpr StaticMultiDimArray() : std::array<T, nEntries>(InitStaticArray<T, nEntries>(0.0)) {}
};
/** Dynamic Multi-dimensional Array Class */
template <class T>
class DynamicMultiDimArray : public std::vector<T>, public MultiDimArray<DynamicMultiDimArray<T>, T>
{
private:
std::size_t nEntries;
std::tuple<...> Dimensions; // Not sure what to use here considering a tuple is immutable
friend MultiDimArray<DynamicMultiDimArray<T>, T>;
public:
DynamicMultiDimArray() : std::vector<T>() {}
template <typename ...Dims>
DynamicMultiDimArray(Dims ...dimensions) : std::vector<T>((dimensions * ...), 0.0) { Resize(dimensions...); }
template <typename ...Dims>
void Resize(Dims ...dimensions)
{
nEntries = (dimensions * ...);
this->resize(nEntries);
// store dimensions in Dimensions...
}
};

上述支持函数InitStaticArrayHookTypeToPackComputeIndex定义如下:

template <class T, std::size_t size>
constexpr auto InitStaticArray(const T& value)
{
std::array<T, size> arr;
std::fill(arr.begin(), arr.end(), value);
return arr;
}
template <std::size_t, typename T>
using HookTypeToPack = T;
template <std::size_t ...Dims>
constexpr std::size_t ComputeIndex(HookTypeToPack<Dims, std::size_t> ...multi_index)
{
constexpr std::size_t dims_arr[] = {Dims...};
std::size_t multi_index_arr[] = {multi_index...};
std::size_t index(0), factor(1);
for(int i = 0; i < sizeof...(Dims); i++)
{
assert(0 <= multi_index_arr[i] && multi_index_arr[i] < dims_arr[i]);
index += factor * multi_index_arr[i];
factor *= dims_arr[i];
}
assert(0 <= index && index < (Dims * ...));
return index;
}

谁能给我一些关于如何在StaticMultiDimArrayDynamicMultiDimArray中存储数组尺寸的建议,以便它们可以在MultiDimArray中的operator()过载中适当地调用?

有几个选项。您已经在暗示:将维度存储在派生类中。但是,不要使用std::tuple;允许每个元素具有不同的类型,而我们只希望每个维度的大小为std::size_t。对静态数组使用std::array,对动态数组使用std::vector:

template <class T, std::size_t ...Dims>
class StaticMultiDimArray :
public std::array<T, ...>,
public MultiDimArray<...>
{
constexpr static std::array dimensions{Dims...};
...
};
template <class T>
class DynamicMultiDimArray :
public std::vector<T>,
public MultiDimArray<...>
{
std::vector<std::size_t> dimensions;
...
};

您也不需要使DynamicMultiDimArray的构造函数成为模板,相反,您可以让它将std:initializer_list作为参数,如下所示:

DynamicMultiDimArray(std::initializer_list<std::size_t> dimensions) :
std::vector<T>(std::accumulate(dimensions.begin(), dimensions.end(), std::size_t{1}, std::multiplies<std::size_t>())),
Dimensions(dimensions) {}

是的,元素向量的初始化现在看起来很难看。我建议不要使用继承,而是使用组合。然后,您可以首先初始化维度,然后是数据的数组/向量,并可以在MultiDimArray中创建一个成员变量来计算条目的数量:

template <class derived, class T>
class MultiDimArray {
...
constexpr std::size_t nEntries() const {
const auto &dimensions = Derived().dimensions;
return std::accumulate(dimensions.begin(), dimensions.end(), std::size_t{1}, std::multiplies<std::size_t>());
}
...
};
template <class T>
class DynamicMultiDimArray :
public MultiDimArray<...>
{
std::vector<std::size_t> dimensions;
std::vector<T> data;
...
public:
DynamicMultiDimArray(std::initializer_list<std::size_t> dimensions) :
Dimensions(dimensions),
data(nEntries()) {}
};

同样,您可以让operator()使用std::initializer_list<std::size_t>作为索引。使用它的代码如下所示:

DynamicMultiDimArray dimarr({3, 5, 7}); // create new array
...
std::cout << dimarr({1, 2, 3}) << 'n'; // print element at index 1, 2, 3

这避免了很多模板的麻烦。您甚至可以考虑创建自定义多维索引类型,而不是std::initializer_list<std::size_t>,从而更容易将索引存储在变量中并传递它们。