[Boost.Multi] Initial feedback
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback. I would like to thank Alfredo for writing and sharing the library. I would also thank you for the single header version and the Compiler explorer integration. This is so welcoming not only for the users but also for the reviewers. I am not well familiar with the domain.i only had a look at the docs and played with toy examples in Compiler Explorer. I would like to request a Design Rationale section that would address some of the questions up front and bring some insight into the domain: 1. Is it a common need in the mentioned application fields to add a new dimension to the already stored N-dimensional data? If so, could we see an example of how to do it? 2. Why is c++17 chosen as the minimum? 3. The number of dimensions is static, but the sizes in each dimension are dynamic. Can you provide the rationale for this choice? The design: 1. I am still trying to digest the way by-reference and by-value is handled in the library in the presence of the "ref"/"view" types. It looks like the simple advice for the new users would be: * Always use `auto& ref = x` if you need the reference semantics, where `x` is whatever expression. * Always use `auto val = +x` if you need value semantics, where `x` is any expression. It looks like it is optimum even when `x` is of type `array`. 2. The initialization from values is too similar to the initialization from extents. I cannot easily figure out which initialization is used in `multi::array a({3, 4, 5});`. This is error-prone. I would prefer a longer: `multi::array a(multi::extents{3, 4, 5});`. On the other hand, does the initialization from small manual static sets of data happen often? I would think that for these use cases you would read the data from some external source. 3. If the docs recommend that all functions should be generic to account for subarrays and refs, then this library could offer a concept like `Indexable<Dimensionality, ElementType>` available for C++20 compilers. 4. Are sizes equal to strands? Does .elements() offer contiguous access? 5. For the initialization, did you consider factory functions or tag parameters? ( https://akrzemi1.wordpress.com/2016/06/29/competing-constructors/). The differentiation of meaning by braces/parens seems easy to misinterpret. 6. All the subarrays and dynamic_arrays have function swap, but it is not documented. Does it have a precondition that sizes should match? Documentation: 1.It is very clear already! 2. In the introduction it is worth explaining the term "stride-based layout" or link to the definition. This concept seems crucial to the library. 3. The docs say "or tensors in the 3D case". Tensors are arbitrary dimension. 4. I think that in real, practical use cases the array data is populated from files or datastres rather than from literals (compile-time values in braces). Could the tutorial demonstrate the endorsed way of doing such data population with this library? 5. I really appreciate that you prefix names with `multi::`. The examples could also mention the headers that you need to include. The initial docs examples may be the place where I will learn the headers. 6. The interface discoverability is not great: I wanted to quickly check the contract of `array::elements()`. I cannot find it. The reference section only has repeated text "same as for dynamic_array" (no link). Generally, the "redirect" philosophy in the reference documentation is mean.In order to look up the semantics of array::operator(), I am redirected: first from `array` to `dynamic_array`, then to `array_ref`, and only then to `subarray`. 7. Generally, we need to see a more detailed Reference section. For instance, what is the return type of operator[]? 8. Document if `array` and siblings can have dimensionality 0 (which would mean access to a single element). 9. It is strange to see a section titled "Static arrays" that describes type `dynamic_array`. In C++ "static" and "dynamic" tend to have opposite meanings. Regards, &rzej;
One other thing. The advice to use a generic function template rather than a concrete function comes with a peril. Let me define one: ``` template <class ArrayDouble2D> auto substitute(ArrayDouble2D && l, ArrayDouble2D && r) { // ... l = r; } ``` There is no concept, so I can only guess what operations are allowed by a pseudo-concept ArrayDouble2D. I can assume that using the assignment is fine. now let's define two arrays and test it: ``` int main() { multi::array<int, 2> A = { {1, 2, 3}, {4, 5, 6} }; multi::array<int, 2> B = { {1, 2}, {3, 4}, {5, 6} }; substitute(A, B); } ``` The test passes. But if I change the test to using views: ``` substitute(A(), B()); ``` it starts to crash (we have UB). We will also get UB (without a crash) for: ``` substitute(A(), A()); ``` The peril stems from the fact that both arrays and views offer the assignment operator, but in each case it has different semantics. I mean having or not having a preconditionis the difference in semantics. Having an assignment operator with a precondition is unusual and unexpected. One way to slightly mitigate this would be for this library to introduce a concept (type trait) and encourage using it. The concept would allow indexing but not assignment. Regards, &rzej; sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
I would like to thank Alfredo for writing and sharing the library. I would also thank you for the single header version and the Compiler explorer integration. This is so welcoming not only for the users but also for the reviewers.
I am not well familiar with the domain.i only had a look at the docs and played with toy examples in Compiler Explorer.
I would like to request a Design Rationale section that would address some of the questions up front and bring some insight into the domain:
1. Is it a common need in the mentioned application fields to add a new dimension to the already stored N-dimensional data? If so, could we see an example of how to do it? 2. Why is c++17 chosen as the minimum? 3. The number of dimensions is static, but the sizes in each dimension are dynamic. Can you provide the rationale for this choice?
The design:
1. I am still trying to digest the way by-reference and by-value is handled in the library in the presence of the "ref"/"view" types. It looks like the simple advice for the new users would be: * Always use `auto& ref = x` if you need the reference semantics, where `x` is whatever expression. * Always use `auto val = +x` if you need value semantics, where `x` is any expression. It looks like it is optimum even when `x` is of type `array`.
2. The initialization from values is too similar to the initialization from extents. I cannot easily figure out which initialization is used in `multi::array a({3, 4, 5});`. This is error-prone. I would prefer a longer: `multi::array a(multi::extents{3, 4, 5});`. On the other hand, does the initialization from small manual static sets of data happen often? I would think that for these use cases you would read the data from some external source.
3. If the docs recommend that all functions should be generic to account for subarrays and refs, then this library could offer a concept like `Indexable<Dimensionality, ElementType>` available for C++20 compilers.
4. Are sizes equal to strands? Does .elements() offer contiguous access?
5. For the initialization, did you consider factory functions or tag parameters? ( https://akrzemi1.wordpress.com/2016/06/29/competing-constructors/). The differentiation of meaning by braces/parens seems easy to misinterpret.
6. All the subarrays and dynamic_arrays have function swap, but it is not documented. Does it have a precondition that sizes should match?
Documentation:
1.It is very clear already!
2. In the introduction it is worth explaining the term "stride-based layout" or link to the definition. This concept seems crucial to the library.
3. The docs say "or tensors in the 3D case". Tensors are arbitrary dimension.
4. I think that in real, practical use cases the array data is populated from files or datastres rather than from literals (compile-time values in braces). Could the tutorial demonstrate the endorsed way of doing such data population with this library?
5. I really appreciate that you prefix names with `multi::`. The examples could also mention the headers that you need to include. The initial docs examples may be the place where I will learn the headers.
6. The interface discoverability is not great: I wanted to quickly check the contract of `array::elements()`. I cannot find it. The reference section only has repeated text "same as for dynamic_array" (no link). Generally, the "redirect" philosophy in the reference documentation is mean.In order to look up the semantics of array::operator(), I am redirected: first from `array` to `dynamic_array`, then to `array_ref`, and only then to `subarray`.
7. Generally, we need to see a more detailed Reference section. For instance, what is the return type of operator[]?
8. Document if `array` and siblings can have dimensionality 0 (which would mean access to a single element).
9. It is strange to see a section titled "Static arrays" that describes type `dynamic_array`. In C++ "static" and "dynamic" tend to have opposite meanings.
Regards, &rzej;
On Fri, Jan 16, 2026 at 3:03 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
One other thing. The advice to use a generic function template rather than a concrete function comes with a peril. Let me define one:
``` template <class ArrayDouble2D> auto substitute(ArrayDouble2D && l, ArrayDouble2D && r) { // ... l = r; } ```
There is no concept, so I can only guess what operations are allowed by a pseudo-concept ArrayDouble2D. I can assume that using the assignment is fine.
You are right, the implicit concept is quite loose here. Of course this wouldn't be a problem for `ArrayDouble2D const&` which is the original example in the documentation.
These are the concept that are implicit here (this is a placeholder syntax): template <class ArrayValueReextendableDouble2D, std::enable_if ... ArrayValueDouble2D::dimensionality == 2 && ArrayValueDouble2D::element == double && is_Reextendable(ArrayDouble2D) > auto fun(ArrayDouble2D && l) // this can be ArrayDouble2D& I guess template <class SubarrayReferenceUnreextendableDouble2D, std::enable_if ... SubarrayReferenceUnreextendableDouble2D::dimensionality == 2 && SubarrayReferenceUnreextendableDouble2D::element == double && !is_Reextendable(SubarrayReferenceUnreextendableDouble2D) (by exclusion) > auto fun(SubarrayReferenceDouble2D && l); Of course, this is very redundant, Subarrays are always (library) references and unresizable and Array are always values and resizable. Unfortunately I can't restrict this directly with the semantics of operator=, because it works on both cases. So I have to refer indirectly through the presence of .reextend , even if it is not used explicitly in the implementation of the function. And yes, operator= with preconditons is strange. That is the cost of working with this type of reference semantics, but without it no STL algorithm will work. What I can say is that value-semantics types do not have preconditions on operator=, while reference-sematic types in the library do have preconditions. (I think ARGUABLY that this is akin of saying with regular references that operator= is well defined in references if references are not invalidated, here is the same but with the .size(), so it is not completely out of the ordinary for operator= to crash on a reference --and imposible to detect--, the only difference is that we are so used to see this other kind of precondition on language references). Another type that has a precondition on assignment is `dynamic_array`, although it is owning. I consider assigning references of difference types a logic error in the code. I don't see other solution at the moment. I also can invoke some provenance arguments, for example (too strong) references taken from different arrays (or different views of the same array) shall not be assigned to each other. In same way that people can say that given pointers p and q coming from different allocators, p - q is undefined, if subarrays r and s come from different arrays then r = s is undefined. Another interesting question is whether any operator= can be noexcept. If the elements are trivial I can claim that operator= can never fail. On the other hand it has precondition (on sizes), so if I apply the Lakos' rule it can never be noexcept (in one case because of the precondition) in the other case (for array) because there is a possible allocation. I wish I could distinguish the too by the noexcepness, but I digress. Thanks, Alfredo
Thank you, Andrzej, for the feedback and the appreciation of the library and the effort. On Fri, Jan 9, 2026 at 3:52 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
I would like to request a Design Rationale section that would address some of the questions up front and bring some insight into the domain:
I wrote a design rationale section based on your questions. https://correaa.gitlab.io/boost-multi/multi/technical.html
1. Is it a common need in the mentioned application fields to add a new dimension to the already stored N-dimensional data? If so, could we see an example of how to do it?
No, it is not common to add dimensions on the fly. A new object with different dimensionality can be idiomatically created in this way: `multi::array<T, D> arr =...; `multi::array<T, D+1> extrude(n, arr);` https://godbolt.org/z/GxKscxsh6 2. Why is c++17 chosen as the minimum?
The main reason is the use of `if constexpr`, it appears 68 times Earlier standards (C++14 and below) would require Heavier template metaprogramming and much more boilerplate. I can work out a C++14-compatible version if there is demand.
3. The number of dimensions is static, but the sizes in each dimension are dynamic. Can you provide the rationale for this choice?
The dimensionality (number of dimensions) is static because it is useful to know at compile time. The sizes in each dimension are dynamic because it is useful to choose these numbers at runtime. Empty array values are useful too, and eventually they are resized (reextended) to the correct size. Other choices include runtime dimensions, compile-time sizes, or runtime-fixed sizes. This goes against the library's philosophy; it is useful to dispatch arrays to functions that know their dimensionality at compile time. Algorithms that work the same way for different dimensions are basically nonexistent. Libraries that make dimensionality runtime-dependent need to check it at runtime. Multi achieves a different kind of flexibility (the right kind), but a) treating ND arrays themselves as, for example, 1D arrays of N-1 D subarrays or N - 1D arrays of 1D subarrays, or anything in between. b) providing .elements() which gives a flattened view of an array of any dimensionality.
The design:
1. I am still trying to digest the way by-reference and by-value is handled in the library in the presence of the "ref"/"view" types. It looks like the simple advice for the new users would be: * Always use `auto& ref = x` if you need the reference semantics, where `x` is whatever expression.
The language is fighting me hard here. It is actually `auto&& ref = x` (I wish it were `auto& ref = x` allowed and I wish the language prevented this `auto ref = x`, but it doesn't). * Always use `auto val = +x` if you need value semantics, where `x` is any
expression. It looks like it is optimum even when `x` is of type `array`.
To be generically consistent, I have to make the return type of unary '+' semantically consistent. This means that +x returns a copy of x unconditionally. So it has a cost when x is an array (allocation and copy of elements), and then that is moved (materialized?) into the auto variable; the copy was needed anyway. So, yes, it is also optimal when `x` is a plain array. (I could make unary '+' return `multi::array const&` but I think that broke something fundamental. We can investigate more this. Maybe I missed something.) At least for r-values, the recommendations are still correct and not inefficient. ``` class multi::array { ... auto operator+() const& { return multi::array{*this}; } constexpr auto operator+() && { return multi::array{std::move(*this)}; } ``` I included this explicit advice at the end of this section of the tutorial: https://correaa.gitlab.io/boost-multi/multi/tutorial.html#tutorial_copy_assi...
2. The initialization from values is too similar to the initialization from extents. I cannot easily figure out which initialization is used in `multi::array a({3, 4, 5});`. This is error-prone. I would prefer a longer: `multi::array a(multi::extents{3, 4, 5});`. On the other hand, does the initialization from small manual static sets of data happen often? I would think that for these use cases you would read the data from some external source.
I would claim that the problem with `multi::array a({3, 4, 5});` is the use of CTAD in the first place. If you don't use CTAD, you are forced to make it clear: `multi::array<int, 1> a({3, 4, 5});` `multi::array<int, 3> a({3, 4, 5});` Although for the former I recommend multi::array<int, 1> a = {3, 4, 5};` Yes, using the type `extensions_t` makes everything more explicit, and it is completely opt-in. CTAD doesn't work in your proposed `multi::array a(multi::extensions_t<3>{3, 4, 5});` because there is no information about the type of the element, fixes are (choose one) ``` multi::array<int, 3> a(multi::extensions_t<3>{3, 4, 5}); // recommended multi::array<int, 3> a(multi::extensions_t{3, 4, 5}); // uses CTAD inside multi::array a(multi::extensions_t{3, 4, 5}, int{}); // double CTAD, but weird ``` This is a summary of the possibly confusing cases: https://godbolt.org/z/K3KoYbr88
3. If the docs recommend that all functions should be generic to account for subarrays and refs, then this library could offer a concept like `Indexable<Dimensionality, ElementType>` available for C++20 compilers.
I agree, the most difficult part is to make this concept, also how to include (or not) the notion that is Mutable, or Reextendable (resizable).
4. Are sizes equal to strands? Does .elements() offer contiguous access?
I don't know what are strands, do you mean strides. Not, in general. For contiguous arrays in memory, strides can be trivially computed from the array sizes. For example in D = 2, given size1 and size2 (columuns and row) stride1 = size2, stride2 = 1
5. For the initialization, did you consider factory functions or tag parameters? ( https://akrzemi1.wordpress.com/2016/06/29/competing-constructors/). The differentiation of meaning by braces/parens seems easy to misinterpret.
Yes, I considered it. But I have to find a case in which it can really be a problem. The existence of multi::extensions_t<D> as first class type in the library makes the disambiguation much simpler than in the case of std::vector. So, in some sense, multi::extensions_t<D> is my "tag" and resolves all the cases as far as I know.
6. All the subarrays and dynamic_arrays have function swap, but it is not documented. Does it have a precondition that sizes should match?
Good catch, swaperator is a friend. It is now documented. Yes, for subarrays references (including dynamic_arrays) sizes must match, and the operation is O(n) For array values, sizes don't need to match, and swap is O(1).
Documentation:
1.It is very clear already!
Thank you!
2. In the introduction it is worth explaining the term "stride-based layout" or link to the definition. This concept seems crucial to the library.
Linked to Wikipedia article.
3. The docs say "or tensors in the 3D case". Tensors are arbitrary dimension.
You are technically correct. I fixed it to "(althoug they are still good building blocks for implementing mathematical algorithms, such as representing algebraic dense matrices in the 2D case, or tensors in the general case.)"
4. I think that in real, practical use cases the array data is populated from files or datastres rather than from literals (compile-time values in braces). Could the tutorial demonstrate the endorsed way of doing such data population with this library?
yes, - (de)serialization for internal (to the library) transactions https://correaa.gitlab.io/boost-multi/multi/interop.html#interop_serializati... - lazy arrays for programmatically: https://correaa.gitlab.io/boost-multi/multi/tutorial.html#tutorial_restricti... Since there is no single way to represent arrays in the wild, I don't want to impose one. I will talk to the Boost. JSON people to come up with a way to do JSON that is not intrusive and doesn't add dependencies. 5. I really appreciate that you prefix names with `multi::`. The examples
could also mention the headers that you need to include. The initial docs examples may be the place where I will learn the headers.
Good idea, each section in the tutorial introduces a specific header for the functionality: - array_ref.hpp (subarrays and references to non-owning memory) - array.hpp (dynamic_array, array, owning memory) - restriction.hpp (lazy arrays) - broadcast.hpp (laze expressions)
6. The interface discoverability is not great: I wanted to quickly check the contract of `array::elements()`. I cannot find it. The reference section only has repeated text "same as for dynamic_array" (no link). Generally, the "redirect" philosophy in the reference documentation is mean.In order to look up the semantics of array::operator(), I am redirected: first from `array` to `dynamic_array`, then to `array_ref`, and only then to `subarray`.
What do you mean by contract? The search box leads me to: "a flatted view of all the elements rearranged canonically. A.elements()[0] → A[0][0], A.elements()[1] → A[0][1], etc. The type of the result is not a subarray but a special kind of range. Takes no argument." what other info do you want to see? exceptions handling, allocations? Yes, I should repeat all the members for arrays, given how important it is, and once each member is spelled out, I can redirect directly to the base class that implements it. This will take time.
7. Generally, we need to see a more detailed Reference section. For instance, what is the return type of operator[]?
Searching for "operator[]" leads me to "access specified element by index (single argument), returns a `reference` (see above), for D > 1 it can be used recursively" `reference` is a member type, looking above it says "multi::subarray<T, D-1> or, for D == 1, pointer_traits<P>::reference (usually T&)" I will add "... return a `reference` member type (see above), ..."
8. Document if `array` and siblings can have dimensionality 0 (which would mean access to a single element).
yes, it can but it can be confusing in the documentation. They potentially have very weird properties. They can have 1 or be in a partially formed state (I didn't commit to what is the non-empty state) https://godbolt.org/z/fGbEbvM74
9. It is strange to see a section titled "Static arrays" that describes type `dynamic_array`. In C++ "static" and "dynamic" tend to have opposite meanings.
That is a relic; `dynamic_array` was called `static_array`. `dynamic_array` is a better name because it has a dynamic size. Fixed. Thank you, Alfredo
6. The interface discoverability is not great: I wanted to quickly check the contract of `array::elements()`. I cannot find it. The reference section only has repeated text "same as for dynamic_array" (no link). Generally, the "redirect" philosophy in the reference documentation is mean.In order to look up the semantics of array::operator(), I am redirected: first from `array` to `dynamic_array`, then to `array_ref`, and only then to `subarray`.
What do you mean by contract?
All the characteristics that are usually described when documenting the Standard Library functions: https://cppreference.com/w/cpp/utility/optional/transform.html Like: return type, constraints, const/constexpr properties, postconditions. In this case, I was interested in what properties the returned range has: random-access, contiguous? I naturally expect that it is a contiguous range for multi::array and multi::array_ref, so I wanted to confirm that. I suppose it is different for subarrays. I can hardly imagine they would have the same range category.
The search box leads me to: "a flatted view of all the elements rearranged canonically. A.elements()[0] → A[0][0], A.elements()[1] → A[0][1], etc. The type of the result is not a subarray but a special kind of range. Takes no argument."
It takes me to subarray::elements, but that doesn't imply that array::elements would have the same contract. "special kind of range" is not the level of detail that I expected. I think you owe the user the range category. Regards, &rzej;
On Wed, Jan 21, 2026 at 1:56 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
What do you mean by contract?
All the characteristics that are usually described when documenting the Standard Library functions: https://cppreference.com/w/cpp/utility/optional/transform.html
Like: return type, constraints, const/constexpr properties, postconditions. In this case, I was interested in what properties the returned range has: random-access, contiguous? I naturally expect that it is a contiguous range for multi::array and multi::array_ref, so I wanted to confirm that. I suppose it is different for subarrays. I can hardly imagine they would have the same range category.
return type: A custom `elements_type`, which is a random access range. for `multi::array` the type is `multi::subarray<T, 1>` because it can be contiguous. no contraints, .elements() always returns something valid, even if the array is empty. it is constexpr. postcontions, nothing changes, .elements() is just another view. I say this now: "As an alternative, the elements can be iterated in a flat manner, using the `.elements()` member. This "elements" range also provides the `begin` and `end` iterators (`.elements().begin()`) and indexing, in the canonical order of the original arrays (rightmost index changes fastest). The elements range is always random-access, but it can be contiguous depending on the original array (`multi::array` generally give contiguous element ranges.)" In the reference section, ``` | `elements` | a flatted random-access view of all the elements rearranged canonically. `A.elements()[0] -> A[0][0]`, `A.elements()[1] -> A[0][1]`, etc. The type of the result is not a subarray but a special kind of random-access range. Takes no arguments. ``` And, later for array_ref, ``` | `elements` | a flatten random-access and contiguous view of all the elements in the array in canonical order. (This is overwritten from the base class, to provide contiguous access) ``` I also added that all member functions are constexpr unless specified differently. (almost all member functions are constexpr, a couple of exceptions is some destructors and some constructors, due to C++17 limitations).
The search box leads me to: "a flatted view of all the elements rearranged canonically. A.elements()[0] → A[0][0], A.elements()[1] → A[0][1], etc. The type of the result is not a subarray but a special kind of range. Takes no argument."
It takes me to subarray::elements, but that doesn't imply that array::elements would have the same contract. "special kind of range" is not the level of detail that I expected. I think you owe the user the range category.
range category: always random access, contiguous for multi::array. Thanks, Alfredo On Wed, Jan 21, 2026 at 1:56 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
6. The interface discoverability is not great: I wanted to quickly check the contract of `array::elements()`. I cannot find it. The reference section only has repeated text "same as for dynamic_array" (no link). Generally, the "redirect" philosophy in the reference documentation is mean.In order to look up the semantics of array::operator(), I am redirected: first from `array` to `dynamic_array`, then to `array_ref`, and only then to `subarray`.
What do you mean by contract?
All the characteristics that are usually described when documenting the Standard Library functions: https://cppreference.com/w/cpp/utility/optional/transform.html
Like: return type, constraints, const/constexpr properties, postconditions. In this case, I was interested in what properties the returned range has: random-access, contiguous? I naturally expect that it is a contiguous range for multi::array and multi::array_ref, so I wanted to confirm that. I suppose it is different for subarrays. I can hardly imagine they would have the same range category.
The search box leads me to: "a flatted view of all the elements rearranged canonically. A.elements()[0] → A[0][0], A.elements()[1] → A[0][1], etc. The type of the result is not a subarray but a special kind of range. Takes no argument."
It takes me to subarray::elements, but that doesn't imply that array::elements would have the same contract. "special kind of range" is not the level of detail that I expected. I think you owe the user the range category.
Regards, &rzej;
sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
The docs compare this library to the existing Boost.MultiIndex in the following way: Boost.MultiArray <https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html> is the original multidimensional array library shipped with Boost. This library can replace Boost.MultiArray in most contexts, it even fulfills the concepts of boost::multi_array_concepts::ConstMultiArrayConcept and …::MutableMultiArrayConcept. Boost.MultiArray has technical and semantic limitations that are overcome in this library, regarding layouts and references; it doesn’t support value-semantics, iterator support is limited, and it has other technical problems. What semantic limitations and technical problems do you have in mind? A more detailed comparison would be helpful in the docs. For instance, a reader would like to be convinced what they should choose Boost.Multi, given that Boost.MultiIndex is older and probably better battle-tested. Regards, &rzej;
niedz., 25 sty 2026 o 18:54 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
The docs compare this library to the existing Boost.MultiIndex in the following way:
Boost.MultiArray <https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html> is the original multidimensional array library shipped with Boost. This library can replace Boost.MultiArray in most contexts, it even fulfills the concepts of boost::multi_array_concepts::ConstMultiArrayConcept and …::MutableMultiArrayConcept. Boost.MultiArray has technical and semantic limitations that are overcome in this library, regarding layouts and references; it doesn’t support value-semantics, iterator support is limited, and it has other technical problems.
What semantic limitations and technical problems do you have in mind? A more detailed comparison would be helpful in the docs. For instance, a reader would like to be convinced what they should choose Boost.Multi, given that Boost.MultiIndex is older and probably better battle-tested.
Sorry, I meant the comparison with Boost.MultiArray, not MultiIndex. Regards, &rzej;
On Sun, Jan 25, 2026 at 10:03 AM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
niedz., 25 sty 2026 o 18:54 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
The docs compare this library to the existing Boost.MultiIndex in the following way:
Boost.MultiArray <https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html> is the original multidimensional array library shipped with Boost. This library can replace Boost.MultiArray in most contexts, it even fulfills the concepts of boost::multi_array_concepts::ConstMultiArrayConcept and …::MutableMultiArrayConcept. Boost.MultiArray has technical and semantic limitations that are overcome in this library, regarding layouts and references; it doesn’t support value-semantics, iterator support is limited, and it has other technical problems.
What semantic limitations and technical problems do you have in mind?
technical problems: - layouts are heap (new) allocated. (BMA tried to be too smart regarding subarrays construction) - slices work through an internal, inefficient, type erasure mechanism if I remember correctly - iterators barely work with any algorithm. definitively not with algorithms that require mutation (eg sort) - BMA doesn’t work with stateful allocators or fancy pointers (eg interprocess memory), semantic of BMA: (some consequence of the technical points above) - references of references of values (subarrays of subarrays of arrays) have their lifetimes attached to the intermediate subarray. This doesn’t allow to go recursively through subblocking, subarrays of subarray cannot be safely returned from functions effectively. This is an unfortunate result of the design, surprises anybody that tries to make serious use of the library and it is basically unfixable. - BMA Arrays don’t have value semantics because assignment only works on arrays of equal sizes (arrays inherit the assignment precondition of subarrays, as we discussed value semantics implies no preconditions on assignment) - no move semantics, If I remember correctly - There is no flattening or true “elements” range. unfortunately I cannot say that Multi fulfills the strict concepts of BMA anymore because BMA has a shape method that returns a non owning pointer to the sizes or strides. Since Multi returns this by value there is no way I can’t match the semantics of a returned pointer. The BMA concept was overespecific. A more detailed comparison would be helpful in the docs. For instance, a
reader would like to be convinced what they should choose Boost.Multi, given that Boost.MultiIndex is older and probably better battle-tested.
Sorry, I meant the comparison with Boost.MultiArray, not MultiIndex.
That was clear. If I add the points above would be helpful? A summary of BMA is “be prepared to be surprised by its behavior and limitations, and don’t try generic programming” If you are interested you can try converting any Multi examples to BMA. The similarities are only skin deep. I wish Ron Garcia can participate in this thread to hear about his take on this, but I haven’t seen him around in Boost for more than a decade. If I remember correctly Glenn Fernandez did some retrofitting less than 10 years ago, but i don’t remember the details, something related to allocators or move semantics. Don’t get me wrong BMA was a tour de force at the time. Thanks A
niedz., 25 sty 2026 o 21:57 Alfredo Correa <alfredo.correa@gmail.com> napisał(a):
On Sun, Jan 25, 2026 at 10:03 AM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
niedz., 25 sty 2026 o 18:54 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
The docs compare this library to the existing Boost.MultiIndex in the following way:
Boost.MultiArray <https://www.boost.org/doc/libs/1_82_0/libs/multi_array/doc/user.html> is the original multidimensional array library shipped with Boost. This library can replace Boost.MultiArray in most contexts, it even fulfills the concepts of boost::multi_array_concepts::ConstMultiArrayConcept and …::MutableMultiArrayConcept. Boost.MultiArray has technical and semantic limitations that are overcome in this library, regarding layouts and references; it doesn’t support value-semantics, iterator support is limited, and it has other technical problems.
What semantic limitations and technical problems do you have in mind?
technical problems: - layouts are heap (new) allocated. (BMA tried to be too smart regarding subarrays construction) - slices work through an internal, inefficient, type erasure mechanism if I remember correctly - iterators barely work with any algorithm. definitively not with algorithms that require mutation (eg sort) - BMA doesn’t work with stateful allocators or fancy pointers (eg interprocess memory),
semantic of BMA: (some consequence of the technical points above) - references of references of values (subarrays of subarrays of arrays) have their lifetimes attached to the intermediate subarray. This doesn’t allow to go recursively through subblocking, subarrays of subarray cannot be safely returned from functions effectively. This is an unfortunate result of the design, surprises anybody that tries to make serious use of the library and it is basically unfixable. - BMA Arrays don’t have value semantics because assignment only works on arrays of equal sizes (arrays inherit the assignment precondition of subarrays, as we discussed value semantics implies no preconditions on assignment) - no move semantics, If I remember correctly - There is no flattening or true “elements” range.
Thanks. It looks like at least some of them (move semantics) could be fixed in BMA. Do I get it right that it has been attempted and concluded to be impossible?
unfortunately I cannot say that Multi fulfills the strict concepts of BMA anymore because BMA has a shape method that returns a non owning pointer to the sizes or strides. Since Multi returns this by value there is no way I can’t match the semantics of a returned pointer. The BMA concept was overespecific.
Understood. It is likely the right decision not to try too hard to fit the suboptimal concept. But given what you said, is the following excerpt from the docs (section "Appendix") 100% correct? *This library can replace Boost.MultiArray in most contexts, it even fulfills the concepts of boost::multi_array_concepts::ConstMultiArrayConcept and …::MutableMultiArrayConcept. *
A more detailed comparison would be helpful in the docs. For instance, a
reader would like to be convinced what they should choose Boost.Multi, given that Boost.MultiIndex is older and probably better battle-tested.
Sorry, I meant the comparison with Boost.MultiArray, not MultiIndex.
That was clear. If I add the points above would be helpful?
It would. The purpose of such comparisons in the docs is to help a potential user make a decision: why should I use this library rather than that one? Also, what would help is a short code snippet that demonstrates the problem with BMA, and how the corresponding code in your library doesn't have the problem.
A summary of BMA is “be prepared to be surprised by its behavior and limitations, and don’t try generic programming”
If you are interested you can try converting any Multi examples to BMA. The similarities are only skin deep.
I wish Ron Garcia can participate in this thread to hear about his take on this, but I haven’t seen him around in Boost for more than a decade.
Even if he is not around, he might be interested in offering feedback. Has he been contacted?
If I remember correctly Glenn Fernandez did some retrofitting less than 10 years ago, but i don’t remember the details, something related to allocators or move semantics.
Don’t get me wrong BMA was a tour de force at the time.
Sure. This is clear. This is how we evolve things. Regards, &rzej;
sob., 10 sty 2026 o 00:52 Andrzej Krzemienski <akrzemi1@gmail.com> napisał(a):
Hi Everyone, Given the recent announcement of the upcoming Boost.Multi review, I wanted to offer some initial feedback.
I reported my feedback on broadcasting in a GitHub issue: https://github.com/correaa/boost-multi/issues/124 Regards, &rzej;
participants (2)
-
Alfredo Correa -
Andrzej Krzemienski