Introduction
boost::multi_array provides multidimensional arrays in C++, but iterating over them generically (regardless of dimensionality) requires recursive template techniques. A dimension-independent loop uses template recursion that processes one dimension at a time, bottoming out at the innermost dimension where the actual element access happens. This pattern enables writing algorithms that work with 1D, 2D, 3D, or N-dimensional arrays without code duplication.
Basic boost::multi_array Usage
1#include <boost/multi_array.hpp>
2#include <iostream>
3
4int main() {
5 // 3D array: 2x3x4
6 boost::multi_array<double, 3> arr(boost::extents[2][3][4]);
7
8 // Fill with values
9 int val = 0;
10 for (int i = 0; i < 2; ++i)
11 for (int j = 0; j < 3; ++j)
12 for (int k = 0; k < 4; ++k)
13 arr[i][j][k] = val++;
14
15 std::cout << arr[1][2][3] << std::endl; // 23
16}
This hard-codes the number of nested loops. For a dimension-independent approach, we need compile-time recursion.
Dimension-Independent Loop with Template Recursion
1#include <boost/multi_array.hpp>
2#include <iostream>
3
4// Base case: 1D — iterate over elements directly
5template <typename Array, typename Func>
6void for_each_element(Array& arr, Func func,
7 typename std::enable_if<Array::dimensionality == 1>::type* = 0)
8{
9 for (auto it = arr.begin(); it != arr.end(); ++it) {
10 func(*it);
11 }
12}
13
14// Recursive case: N-D — iterate over sub-arrays
15template <typename Array, typename Func>
16void for_each_element(Array& arr, Func func,
17 typename std::enable_if<(Array::dimensionality > 1)>::type* = 0)
18{
19 for (auto it = arr.begin(); it != arr.end(); ++it) {
20 for_each_element(*it, func); // Recurse into sub-array
21 }
22}
23
24int main() {
25 boost::multi_array<int, 3> arr(boost::extents[2][3][4]);
26
27 // Fill
28 int val = 0;
29 for_each_element(arr, [&val](int& elem) { elem = val++; });
30
31 // Print
32 for_each_element(arr, [](int elem) { std::cout << elem << " "; });
33 // 0 1 2 3 4 5 ... 23
34}
The dimensionality compile-time constant drives SFINAE to select the correct overload. The recursive version peels off one dimension, and the base case processes the innermost elements.
With Index Tracking
To know the coordinates of each element during iteration:
1#include <vector>
2
3template <typename Array, typename Func>
4void for_each_with_index(Array& arr, Func func, std::vector<size_t>& indices,
5 typename std::enable_if<Array::dimensionality == 1>::type* = 0)
6{
7 for (size_t i = 0; i < arr.shape()[0]; ++i) {
8 indices.push_back(i);
9 func(arr[i], indices);
10 indices.pop_back();
11 }
12}
13
14template <typename Array, typename Func>
15void for_each_with_index(Array& arr, Func func, std::vector<size_t>& indices,
16 typename std::enable_if<(Array::dimensionality > 1)>::type* = 0)
17{
18 for (size_t i = 0; i < arr.shape()[0]; ++i) {
19 indices.push_back(i);
20 for_each_with_index(arr[i], func, indices);
21 indices.pop_back();
22 }
23}
24
25// Convenience wrapper
26template <typename Array, typename Func>
27void for_each_with_index(Array& arr, Func func) {
28 std::vector<size_t> indices;
29 for_each_with_index(arr, func, indices);
30}
31
32// Usage
33boost::multi_array<double, 2> matrix(boost::extents[3][4]);
34
35for_each_with_index(matrix, [](double& elem, const std::vector<size_t>& idx) {
36 elem = idx[0] * 10 + idx[1];
37 std::cout << "[" << idx[0] << "," << idx[1] << "] = " << elem << "\n";
38});
C++17 if constexpr Version
C++17 simplifies the recursion with if constexpr:
1template <typename Array, typename Func>
2void for_each_element(Array& arr, Func func) {
3 if constexpr (Array::dimensionality == 1) {
4 for (auto& elem : arr) {
5 func(elem);
6 }
7 } else {
8 for (auto it = arr.begin(); it != arr.end(); ++it) {
9 for_each_element(*it, func);
10 }
11 }
12}
No SFINAE needed — if constexpr discards the unused branch at compile time.
Accumulate / Reduce Pattern
1template <typename Array, typename T, typename BinaryOp>
2T accumulate_elements(const Array& arr, T init, BinaryOp op) {
3 if constexpr (Array::dimensionality == 1) {
4 for (const auto& elem : arr) {
5 init = op(init, elem);
6 }
7 } else {
8 for (auto it = arr.begin(); it != arr.end(); ++it) {
9 init = accumulate_elements(*it, init, op);
10 }
11 }
12 return init;
13}
14
15// Usage: sum all elements
16boost::multi_array<int, 3> arr(boost::extents[2][3][4]);
17// ... fill arr ...
18
19int total = accumulate_elements(arr, 0, std::plus<int>{});
20std::cout << "Sum: " << total << std::endl;
Apply a function to every element and write the result to another multi_array:
1template <typename SrcArray, typename DstArray, typename Func>
2void transform_elements(const SrcArray& src, DstArray& dst, Func func) {
3 if constexpr (SrcArray::dimensionality == 1) {
4 for (size_t i = 0; i < src.shape()[0]; ++i) {
5 dst[i] = func(src[i]);
6 }
7 } else {
8 for (size_t i = 0; i < src.shape()[0]; ++i) {
9 transform_elements(src[i], dst[i], func);
10 }
11 }
12}
13
14// Usage: double every element
15boost::multi_array<int, 2> src(boost::extents[3][4]);
16boost::multi_array<int, 2> dst(boost::extents[3][4]);
17// ... fill src ...
18
19transform_elements(src, dst, [](int x) { return x * 2; });
Using data() for Flat Iteration
If you just need to iterate over all elements without caring about indices, multi_array::data() gives a pointer to the contiguous storage:
1boost::multi_array<int, 3> arr(boost::extents[2][3][4]);
2
3int* data = arr.data();
4int total = arr.num_elements();
5
6for (int i = 0; i < total; ++i) {
7 data[i] = i; // Flat access
8}
This bypasses the multi-dimensional structure entirely. Useful for bulk operations and interoperability with C APIs.
Common Pitfalls
Forgetting dimensionality is a compile-time constant: The SFINAE or if constexpr branch is resolved at compile time. You cannot use a runtime variable to select the dimensionality — the array dimensions must be known at compile time.
Iterator invalidation after reshape: Calling resize() on a multi_array invalidates all iterators and sub-array references. Complete any iteration before reshaping.
Performance of recursive sub-array access: Each operator[] on a multi-dimensional multi_array creates a sub-array view object. For performance-critical code, use data() with manual index calculation or multi_array_ref for views without copies.
Mixing const and non-const in recursion: A const multi_array produces const sub-arrays on iteration. If your function modifies elements, the array parameter must be non-const at every recursion level. Use separate overloads for const and non-const.
Assuming row-major storage: boost::multi_array defaults to C storage order (row-major). If you change the storage order to Fortran (column-major), flat iteration via data() traverses elements in a different order than nested loops.
Summary
Use template recursion with dimensionality to write dimension-independent loops over boost::multi_array
The base case handles 1D arrays; the recursive case iterates over sub-arrays and recurses
C++17 if constexpr simplifies the recursion by eliminating SFINAE
Track indices with a vector<size_t> passed through the recursion
For flat iteration without index awareness, use arr.data() and arr.num_elements()
The same pattern extends to accumulate, transform, and other algorithmic patterns