I have an N-dimensional array class with two template parameters, a class
and a size_t
. To construct a 3-dimensional array of size x
by y
by z
, I would like to be able to use:
ArrayND<float> A(x, y, z);
...but can't figure out how to do so--as it stands, I have to redundantly specify the dimension:
ArrayND<float, 3> A(x, y, z);
I thought that this deduction guide:
template <class T, class ...I>
ArrayND(I...) -> ArrayND<T, sizeof...(I)>;
...would be sufficient to allow the omission of the dimension parameter, but it isn't--when I try to compile a program containing "ArrayND<float> A(x, y, z)
" with G++ 12.1.0 using -std=c++20
, it fails ("error: wrong number of template arguments (1, should be 2)
"). Is it possible to do what I'm trying to? If so, what am I doing wrong?
A simplified version of the class in question:
template <class T, size_t N>
class ArrayND {
// Members: the actual storage, and the conceptual size
std::vector<T> storage;
size_t dims[N];
public:
// Constructor: ArrayND<T, N>(dim1, dim2, ...);
template <class ...I> requires (sizeof...(I) == N && std::is_integral_v<std::common_type_t<I...>>)
constexpr ArrayND(const I ...i): storage((i * ...)), dims{i...} {}
private:
// Indexing helper--convert from (i, j, k...) to the actual index within storage
template <decltype(N) M=N-1, class I, class ...J>
constexpr const auto flattened_index(const I i, const J ...j) const {
auto index = i * std::accumulate(dims+N-M, dims+N, 1, std::multiplies());
if constexpr (sizeof...(J) > 0) index += flattened_index<M-1>(j...);
return index;
}
public:
// Index a cell
template <class ...I> requires (sizeof...(I) == N && std::is_integral_v<std::common_type_t<I...>>)
constexpr auto &operator()(const I ...i) { return storage[flattened_index(i...)]; }
// Access underlying data
constexpr auto data() { return storage.data(); }
};
A minimal working example can be found here; delete the 3
on line 47 to see the failure.