C++23 is going to add multidimensional array subscript operators to the language, yippee!
Alas, I have not yet come across a way to adapt them to types where the number of dimensions is templated, so I came up with this, using variadic templates, fold expression and concepts:
#include <cstddef>
#include <concepts>
// version with static bounds on the dimensions
template <typename T, std::size_t... DIMS>
struct vec {
template <std::convertible_to<std::size_t>... Is>
requires (sizeof...(Is) == sizeof...(DIMS))
T operator[](Is... indices) { return {}; }
};
// version with only the number of dimensions specified (unbounded)
template <typename T, std::size_t D>
struct nvec {
template <std::convertible_to<std::size_t>... Is>
requires (sizeof...(Is) == D)
T operator[](Is... indices) { return {}; }
};
int main() {
vec<int, 3, 2, 4> foo;
vec<int, 1, 1> bar;
vec<int, 91'000> baz;
vec<int> bung;
nvec<int, 4> bogget;
return foo[2, 3, 1] + bar[0, 1] + baz[120] + bung[] + bogget[1, 2, 3, 4];
}
Note that the actual implementation in both cases is stubbed out (I suppose in practice, I will probably use fold expressions to resolve the indices to the correct location in the 1-D array that actually backs the storage for the things, probably with some bounds-checking for the case of vec<>
which has static bounds for all dimensions).
I dunno, this feels slightly unwieldy, but this is the most succinct thing I could come up with that allows template-variadic dimensions. There isn't a more concise way now, is there?
static_assert
s would be clearer thanrequires
if you don't need those for SFINAE and perhaps it'd be preferable to join the two templates intovec
somehow. It makesvec
s with only one dimension a little trickier for sure though. \$\endgroup\$static_assert
vsrequires
—my understanding was thatrequires
is meant to be more idiomatic than the former for expressing template constraints... RE merging them into one template, yes that may be possible (I've done similar for a Matrix type I've written where there's a static-sized and dynamic-sized version, backed by an array and vector respectively). The variadic template params for DIMS do make it awkward though. The best I can think of for disambiguation is a tag type as an additional template arg, not sure if that's better or worse \$\endgroup\$requires
as something to simplify SFINAE in place ofenable_if
etc. but if SFINAE isn't needed, the clarity given by astatic_assert
is i.m.o. preferable. Re merging: Yes, I was think in the terms of tagging too. I guess one would have to try it out to get a feeling for how well it would work out. \$\endgroup\$