Do you really need a nested array? You could also use a flat array and an adapter for the index, e,g, template<typename T, typename U> concept is_convertible_to = std::is_convertible_v<T, U>; template<typename T, typename Index> struct array_adapter { std::array<T, Index::size()> m_a; constexpr array_adapter(is_convertible_to<T> auto ... t) requires (sizeof...(t) == Index::size()): m_a{t ...} {} constexpr T operator()(is_convertible_to<std::size_t> auto ... i) const requires (sizeof...(i) == Index::dim()) { return m_a[Index{}(i...)]; } constexpr T& operator()(is_convertible_to<std::size_t> auto ... i) requires (sizeof...(i) == Index::dim()) { return m_a[Index{}(i...)]; } }; For Index, you might e.g. use template<std::size_t ... N> struct row_major_index { static constexpr std::size_t size() { return (N*...); } template<std::size_t i> constexpr static std::size_t first(is_convertible_to<std::size_t> auto... n) { if constexpr(i == 0) { return std::get<0>(std::make_tuple(n...)); } else { return std::get<i>(std::make_tuple(n...))+std::get<i>(std::make_tuple(N...))*f irst<i-1>(n...); } } constexpr std::size_t operator()(is_convertible_to<std::size_t> auto... n) { return first<sizeof...(n)-1>(n...); } static constexpr std::size_t dim() { return sizeof...(N); } }; This can be used as follows: nt main() { static const int N1 = 2; static const int N2 = 3; static const int N3 = 4; array_adapter<double, row_major_index<N1,N2,N3>> a{111.,112.,113.,114., 121.,122.,123.,124., 131.,132.,133.,134., 211.,212.,213.,214., 221.,222.,223.,224., 231.,232.,233.,234.}; for(int n1=0; n1 != N1; ++n1) { for(int n2=0; n2!=N2; ++n2) { for(int n3=0; n3!=N3; ++n3) { std::cout << "a(" << n1 << "," << n2 << "," << n3 << ")=" << a(n1,n2,n3) << std::endl; } } } return 0; } Of course, this could be generaized to different types of arrays (e.g. std:.vecrtor) usng some policy class and template<typename T, typename Index, typename Policy> struct array_index_adapter { ... }; Helmut