Re: Boost Digest, Vol 7730, Issue 1
Rainer Deyke via Boost <[email protected]>
| Newsgroups | gmane.comp.lib.boost.devel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> >> Subject: [boost] Re: [multi] Formal Review Begins >> To: [email protected] >> Message-ID: <[email protected]> >> 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_elementwise >> >> 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 - [email protected] _______________________________________________ Boost mailing list -- [email protected] To unsubscribe send an email to [email protected] https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/[email protected]/message/MYX2RBEJTWW5JJ7TXV3EQDTKZBMDJYZP/