Info
-
Did you know about C++2X proposal to add Multidimensional subscript operator?
Example
template <class T, auto Dimensions> class mdarray2 {
public:
template <class I1, class I2> constexpr T &operator[](I1 i1, I2 i2) {
return vs_[i1][i2];
}
private:
std::array<std::array<T, 2>, Dimensions> vs_{};
};
int main() {
mdarray2<int, 2> a{};
a[1, 1] = 42;
assert(0 == (a[0, 0]));
assert(42 == (a[1, 1]));
}
Puzzle
- Can you implement a generic multidimensional array with multidimensional subscript operator?
template <class T, std::size_t N>
class mdarray; // TODO
int main() {
using namespace boost::ut;
"mdarray"_test = [] {
"support multiple dimenions"_test = [] {
mdarray<int, 2> a{2, 2};
a[1, 1] = 42;
expect(42_i == a[1, 1]);
};
"support multiple dimenions with different sizes"_test = [] {
mdarray<int, 3> a{1, 3, 2};
a[0, 1, 1] = 42;
a[0, 2, 0] = 84;
expect(0_i == a[0,0,0]);
expect(0_i == a[0,2,1]);
expect(42_i == a[0,1,1]);
expect(84_i == a[0,2,0]);
};
};
}
Solutions
template <class T, std::size_t N>
class mdarray {
public:
template<typename ... D>
mdarray(D ... args) requires(sizeof...(D) ==N): dims{args...} {
std::size_t storage = std::accumulate(dims.begin(), dims.end(), 1, std::multiplies<int>());
vals.resize(storage);
}
template<typename ...D>
T& operator[](D... args) requires(sizeof...(D) == N) {
std::array<int, N> idx{args...};
std::size_t offset = idx[0];
for(std::size_t d=1; d<N; offset= offset*dims[d]+idx[d], d++);
return vals[offset];
}
private:
std::array<int, N> dims;
std::vector<T> vals;
};
template <class T, std::size_t N>
class mdarray {
std::array<std::size_t, N> dims_{};
std::vector<T> data_{};
public:
mdarray(const auto&... dims)
: dims_{static_cast<std::size_t>(dims)...}
, data_((dims * ...))
{}
template <class... Is>
requires (sizeof...(Is) == N)
[[nodiscard]] constexpr T& operator[](const Is&... is) {
const std::array indexes{static_cast<std::size_t>(is)...};
const auto offset = ranges::accumulate(
ranges::views::zip(dims_, indexes),
std::size_t{0},
[](const auto offset, const auto zipped) {
const auto [dim, idx] = zipped;
return offset * dim + idx;
});
return data_[offset];
}
};
template <class T, std::size_t N>
class mdarray {
public:
mdarray(const auto... es) requires (sizeof...(es) == N) {
const auto extent = (1 * ... * es);
storage.reset(new T[extent]);
if constexpr (std::is_trivially_constructible_v<T>) {
std::fill_n(storage.get(), extent, T{});
}
const auto a = std::array{es..., 1};
std::inclusive_scan(std::crbegin(a), std::prev(std::crend(a)),
std::rbegin(extents),
std::multiplies{});
}
auto operator[](const auto... is) -> T& requires (sizeof...(is) == N) {
const auto indices = std::array{is...};
return storage[std::transform_reduce(std::cbegin(indices), std::cend(indices),
std::cbegin(extents), 0)];
}
private:
std::array<std::size_t, N> extents{};
std::unique_ptr<T[]> storage{};
};
template <class T, std::size_t N>
class mdarray: public std::vector<T>
{
using Base = std::vector<T>;
public:
mdarray( auto ... ns ) requires (sizeof...( ns) == N )
: dim{ std::size_t( ns) ... }
{
assert( ( ( ns >=0 ) && ...) );
Base::resize( (ns * ... * 1) );
for ( int i = 1; i <N ; ++i )
dim[i] *= dim[i-1];
for ( int i = N-1 ; i >0; --i )
dim[i] = dim[i-1];
dim[0] = 1;
}
T & operator [] ( auto ... ns ) requires (sizeof...( ns) == N)
{
assert( ( ( ns >=0 ) && ...) );
auto indexes = std::forward_as_tuple(ns...);
auto index = [&]<typename TT, auto ... Is>( std::integer_sequence<TT, Is... > const & ) {
return ((std::get<Is>(indexes) * dim[Is]) + ...);
}( std::make_index_sequence<N>());
return Base::operator[](index);
}
private:
std::array<std::size_t, N > dim;
};
template <class T, std::size_t N>
class mdarray {
public:
template<class ...Dim>
mdarray(Dim && ...dim) requires (sizeof...(dim) == N) : data((dim * ...)) {}
T & operator[](auto &&...n) requires (sizeof...(n) == N) {
auto inc = [i = 1] mutable { return i++; };
return data[(((n * inc()) + ...))];
}
private:
std::vector<T> data;
};
template <class T, std::size_t N>
class mdarray {
std::array<std::size_t, N> dimensions;
std::vector<T> values;
public:
constexpr mdarray(std::convertible_to<std::size_t> auto... dimensions)
requires (sizeof...(dimensions) == N) :
dimensions{static_cast<std::size_t>(dimensions)...},
values((1 * ... * dimensions)) {};
constexpr auto &operator[](std::convertible_to<std::size_t> auto... indices)
requires (sizeof...(indices) == N) {
return [=]<auto... Is>(std::index_sequence<Is...>) -> auto & {
return values[(0 + ... + [=]<auto... Js>(std::index_sequence<Js...>, auto index) {
return index * (1 * ... * dimensions[Js]);
}(std::make_index_sequence<Is>{}, indices))];
}(std::make_index_sequence<N>{});
}
};
template <class T, std::size_t N>
class mdarray{
private:
std::array<int, N> dims_;
std::vector<T> data_;
auto getSize(auto ... dims)
{
return (1 * ... * dims);
}
auto getIndex(auto ... locs)
{
std::array<int, N> locations{locs...};
auto index = locations[0];
for(int i =1; i <N; ++i)
{
index += locations[i] * dims_[i-1];
}
return index;
}
public:
mdarray(auto ... dims)
: dims_{dims...},
data_(getSize(dims...))
{
}
T &operator[](auto ... locs) {
return data_[getIndex(locs...)];
}
};