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/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.