On 3/11/26 03:21, Alfredo Correa via Boost wrote:
Dear Rainer,
Thank you again for expanding your review and answer my points. Here is an answer to your last email.
Message: 6 Date: Tue, 10 Mar 2026 13:37:06 +0100 From: Rainer Deyke <rdeyke@gmail.com> Subject: [boost] Re: [multi] Formal Review Begins To: boost@lists.boost.org Message-ID: <10op39i$hvt$1@ciao.gmane.io> Content-Type: text/plain; charset=UTF-8; format=flowed
On 3/9/26 20:30, Alfredo Correa via Boost wrote:
The proposed library uses an array-of-arrays model.
Multi supports both models array-of-array and flattened view this is prominently mentioned in the documentation, in the Iteration section (
https://correaa.github.io/boost-multi/multi/tutorial.html#tutorial_iteration
)
My bad. I wrote my first message before fully reading the documentation, so I (somehow) missed the elements function.
No problem. Sometimes the documentation are not engaging enough to make everything evident at first glance.
I changed the "Getting Started" section to mention this right away.
For example, I mention that:
"""" Arrays can be viewed simultaneously as a nested array or as flat set of elemets. ``` assert( A.elements().size() == 6 ); // total number of elements
assert( A.elements()[6] == 6 ); assert( &A.elements()[6] == &A[1][2] ); ``` """ Then in the Advanced Usage.Iteration section I now mention your examplesm, and more, using the iterability of the extents object.
https://correaa.gitlab.io/boost-multi/multi/tutorial.html#tutorial_iteration
for(auto indices : a.extensions().elements() ) { apply(a, indices) = get<0>(indices) + 10*get<1>(indices); }
I don't see subarray::extensions()::elements in the documentation at all. In fact, the reference documentation for subarray::extensions() read "returns a tuple with the extensions in each dimension", and std::tuple does not have a elements member function. I see one reference to multi::extensions_t and two references to subarray::extensions_type, neither of which mentions an elements member function. So this one isn't my fault.
I didn't think of using std::apply here. It's a clever solution that only works because arrays can use operator() for indexing instead of the more obvious operator[]. That said, I would still prefer the direct syntax "a[indeces]".
As I said in the correction to my first email, you don't have to use `apply` (there is a `apply_square` planned too). The preferred way is: ``` for(auto [i, j] : a.extensions().elements() ) { a[i][j] = i + 10*j; } ```
The geometric meaning of "rotate" is not limited to 2D. I can rotate an
object in 3D, although I have a lot more choices for the axis of rotation.
The ironic thing is that index rotating can be a geometric rotation of a tensor or an array (after all it is a relabelling of the axis). So the name is not that bad after all.
.rotate_indices would be very verbose. I wish there was a pair of unary operator in C++ that can do rotation (of indices) by one and unrotation by one. Something like "a <<" or "a >>".
The boost::multi::array::reextent function is not clearly defined.
You will obtain {{1, 2, 0}}, a default element will appear at a[0][2] because a[0][2] was not part of the original array.
Your second option depends on some interpretation of the array as flat array of numbers.
I agree that this is the correct behavior, but the documentations should be more explicit.
ok, it is now clarified.
But you can do this better with restrictions, and second, variadic templates in a constructor can mess up think in a nasty way. multi::array({5, 2}, arg1, arg2, arg3); (to construct elements T{arg1, arg2, arg3}).
Using a restriction eliminates the need for copy construction, but it still requires move construction. I admit that storing non-movable types in a multidimensional array is very niche.
Well, restrictions could in principle return r-values. Yes, it is very niche and it is very difficult to make it work and provide useful guarantees.
Having said that I can add tagged constructor, `multi::array(std::in_place, {5, 2}, arg1, arg2, arg3);` if you think it is something that you need.
That would solve the problem.
Yes, but it will also prevent extra parameters like allocators. I can experiment a bit with this idea.
Also remember that Multi supports partially formed objects. Which in principle means that types that only truly valid after assignment can be manipulated efficiently.
On a technical level, the web page doesn't accept arrow keys or page up/down for scrolling, so I have to rely use my mouse for scrolling.
I am able to navigate, scroll up and down with arrow keys, page up/down, and space. I am using a state of the art (I am told), documentation format (Antora).
All I can say is that it doesn't work for me on Firefox, my default browser. It does work on Chromium, so this could be a browser incompatibility issue or a conflict with one of my browser extensions.
ok, the bug has been reported.
The boost/multi/elementwise.hpp header is not documented at all in the reference section.
It is documented here:
https://correaa.github.io/boost-multi/multi/tutorial.html#tutorial_elementwi...
Yes, but there's no list of functions/operators that the header includes. Scrolling through the section, I see operators +, *, and /, as well as function "exp". What other operators are there? I assume that binary and unary - exist and unary + doesn't, but what about bitwise operators? Any functions besides "exp"?
The documentation for (I assume) boost::multi::subarray::const_element_ref just calls it element_ref.
For restrictions? not necessarily, depends on the actual element returned, but what is your question?
On the page, https://correaa.github.io/boost-multi/multi/reference.html#ref_subarray, second table, there are two entries for "element_ref". One reads "Reference to element type, deduced from pointer type, typically T&", the other reads "Constant reference to element type, deduced from pointer type, typically T const&". This is an error in the documentation. The second entry should be labeled "const_element_ref", not "element_ref".
yes, you are right. fixed.
boost::multi::subarray::index_range and boost::multi::subarray::extension_type are only described in very vague terms. What are the members of these types?
index_range is whatever type you put in the parenthesis notation to indicate a range of elements
A( {2, 4} , 5 ); // is the array {A(2 , 5 ), A(3, 5} }
{2, 4} is converted to index_range.
Sure, but can I do anything else with an index range? Can I access its members? Can I iterate over it?
yes, yes, and yes. (The members are .front(), .back(), .begin(), .end(), size())
Extension type is whatever is returned by the .extension() function (this is like .size() but returns a range)
And it apparently has a member function "elements" that is completely undocumented, given your earlier example:
for(auto indices : a.extensions().elements() ) { apply(a, indices) = get<0>(indices) + 10*get<1>(indices); }
yes, it will be documented
The return types of boost::multi::subarray::elements and boost::multi::array_ref::elements should be documented.
I will, yes
Well the type is a complicated type, that for the most part you don't need to know about.
multi::elements_range_t<int*, multi::layout_t<2, long int> >
It depends on other template parameters. It suffices to know that is a random access range.
So it meets the requirements of std::ranges::random_access_range. Does it provide indexed access through operator[]? Does copying it produce a deep or a shallow copy? Is it returned by value or reference?
yes, the copy is deep (it is also immutable). Returns a value, it is a lazy view.
I don't care about the exact type. I care about what properties the type has.
Fair enough.
boost::multi::restriction has a member function called "home" which "returns an indexable cursor". I don't know what this cursor is, what it does, or what members it has.
a cursor is "a pointer-semantic object to an array that forgot its extents" :) Most users should not need to use it.
Cursors are described in other sections. I will add a section on the tutorial, its uses are pretty advances. For arrays, It is for context in which you can't use pass-by-reference.
I see a very brief mention of cursors in the Interoperability/CUDA section (which I skipped over because I am not familiar with CUDA). They are described as "a multidimensional generalization of iterators". Based on that description, I would expect being able to point cursors at different parts of the array (possibly by adding N-dimensional vectors to the cursor), but there is no such example given. They are also described as references to arrays that can be indexed and cheaply copied, but subarray and array_ref can already be used for the same purpose.
Nope, because subarray and array_ref are not copyable! That is a fundamental design of the library, they are not spans.
Regardless of the purpose of cursors, I expect them to be documented in the reference section. Other sections of the documentation are fine for what they are, but the reference section should be able to stand alone.
Yes, that is comming, I am rewriting the reference section.
It seems that the main technical criticism was regarding features that you can opt-in, like views, but that you are not forced to use.
I actually have fairly few technical criticisms. My main problem is with the documentation is not only inadequate, but inadequate to the point where I can't even review key design elements without reading the source code because these design elements are not adequately documented. For example, I feel that there should be bitwise operators in multi::elementwise for completeness, but I would have to read the source code to find out if they already exist or not.
I didn't go overboard because people already complained that there was a lot of operator overloading. So I am now overloading the most requested ones, "I want my D = A + B*C " But you are right, for consistency almost all operators should be there, starting from logical ones.
The other main issue is naming things, which is of course one of the two hard problems in computer science. Everything else is basically nitpicking on my part.
I am collecting alternatives names for everything, so if you have suggestions, please let me know. exfents in place of extensions is underway.
Direct element access is something that you consider very useful and a deal-breaker for any multidimensional library.
I hope I convinced you that element address is a first class citizen in the library and it was a priority since day one.
Yes, the element address issue is mostly resolved. I still don't like having to use "std::apply", but I can live with it.
No need to make compromises:
``` for(auto [i, j] : a.extensions().elements() ) { a[i][j] = i + 10*j; } ```
That's two variables again. I want to use a single variable. I use vectors (in the mathematical sense, fixed size) to index multidimensional arrays all the time. constexpr int spatial_index_granularity = 16; multi::array<std::set<spatial_object *>, 2> spatial_index; void move_object(spatial_object *o) { spatial_index[o->position / spatial_index_granularity].erase(o); o->position += o->velocity; spatial_index[o->position / spatial_index_granularity].insert(o); } I can make this work with apply without adding additional variables: void move_object(spatial_object *o) { std::apply( spatial_index, o->position / spatial_index_granularity).erase(o); o->position += o->velocity; std::apply( spatial_index, o->position / spatial_index_granularity).insert(o); } Using structured bindings also works, but it's more lines of code: void move_object(spatial_object *o) { { // compound statement to constrain scope of extra variables auto [x, y] = o->position / spatial_index_granularity; spatial_index[x, y].erase(o); } o->position += o->velocity; { // compound statement to constrain scope of extra variables auto [x, y] = o->position / spatial_index_granularity; spatial_index[x, y].insert(o); } }
Is there anything specific I could tackle for a recommendation for conditional acceptance, or you think it is irrecoverable? If you manage to vastly improve the documentation by Saturday, I might be able to write another review in Sunday.
-- Rainer Deyke - rainerd@eldwood.com