I have been lurking on the C++ side of stack-overflow only long enough to know there are a lot of beginners and intermediate programmers baffled by multi-dimensional arrays. I've seen a lot of monstrosities : three-star programmers, cache-unfriendly implementations, vectors of vectors of vectors ..., etc.
I thought to provide them with a basic multi-dimensional array:
- open for extensions,
- fit for production and experimentation,
- working without allocations,
- and cache-friendly.
Before I do so, I'd like some advice from the community.
- What can I improve in the provided features of this class?
- What new feature could one add, and why would it be useful?
#include <array>
#include <numeric>
namespace ysc
{
namespace _details
{
template<class InputIt, class OutputIt>
OutputIt partial_product(InputIt first, InputIt last, OutputIt output)
{ *output++ = 1; return partial_sum(first, last, output, std::multiplies<>{}); }
// cache-friendly:
// neighbor objects within the right-most coordinate are neighbors in memory
template<class TDim, class TCoord>
auto coordinates_to_index(TDim const& dimensions, TCoord const& coords)
{
std::array<std::size_t, dimensions.size()> dimension_product;
using std::crbegin, std::crend, std::prev;
partial_product(crbegin(dimensions), prev(crend(dimensions)), begin(dimension_product));
return std::inner_product(cbegin(dimension_product), cend(dimension_product), crbegin(coords), 0);
}
}
constexpr struct matrix_zero_t {} matrix_zero;
template<class T, std::size_t... Dimensions>
class matrix
{
template<class, std::size_t...> friend class matrix;
public:
static constexpr std::size_t order = sizeof...(Dimensions);
static constexpr std::array dimensions = { Dimensions... };
private:
std::array<T, (Dimensions * ...)> _data;
public:
friend void swap(matrix& lhs, matrix& rhs)
{
using std::swap;
swap(lhs._data, rhs._data);
}
public:
matrix() = default;
matrix(matrix&& other) = default;
matrix& operator=(matrix&& other) = default;
matrix(matrix_zero_t) : _data({}) {}
template<class U>
matrix(matrix<U, Dimensions...> const& other) { std::copy(cbegin(other._data), cend(other._data), begin(_data)); }
template<class U>
matrix& operator=(matrix<U, Dimensions...> const& other)
{
matrix o{other};
swap(*this, o);
return *this;
}
public:
template<class... Args>
T const& operator()(Args... coordinates) const
{ return _data[_details::coordinates_to_index(dimensions, std::array{coordinates...})]; }
template<class... Args>
T& operator()(Args... coordinates)
{ return _data[_details::coordinates_to_index(dimensions, std::array{coordinates...})]; }
};
} // namespace ysc
Usage demo: http://coliru.stacked-crooked.com/a/1652451c78275436
This is a C++17 implementation; this itself is not set in stone.
matrix<...> m = matrix_zero; m(x,y,z) = some_value; m(a,b,c) = m(x,y,z) + m(y,z,x);
. \$\endgroup\$std::array
s (for example like this rough sketch) with its current feature set. // In my experience, when working with multidimensional arrays you often want some way to refer to some "subslice" of the covered space. That would be a nice feature ;) \$\endgroup\$