[multi] Formal Review Begins
Dear All, The review of Multi by Alfredo Correa begins today, March 5th and goes through March 15th, 2026. Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. Code: https://github.com/correaa/boost-multi Docs: https://correaa.github.io/boost-multi/multi/intro.html For reviewers, please use the master branch. Please provide feedback on the following general topics: - What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? Do you already use it in industry? - Did you try to use the library? With which compiler(s)? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? - Are you knowledgeable about the problem domain? Ensure to explicitly include with your review: ACCEPT, REJECT, or CONDITIONAL ACCEPT (with acceptance conditions). Additionally, if you would like to submit your review privately, which I will anonymize for the review report, you may send it directly to me at: matt_at_mattborland.com. Matt Borland Review Manager
Hello, Here is my review of Boost.Multi's design and documentation. First things first, I've used Clang++20, and built the examples using the CMake script provided in the repository. Now to the design rationale: So firstly, te library is straightforward and intuitive to use, as seen by the documentation at: https://correaa.github.io/boost-multi/multi/technical.html#technical_indexin... ```cpp // given the values of i and k and accumulating variable acc ... for(long j = 0; j != M; ++j) {acc += A[i][j][k];} ``` This is easy to read and understand, which I appreciate when reading GPU-related source code. One critique I would do on this would be to add syntactic sugar to the loop operations (using free functions like `boost::multi::accumulate` for example!) but that's optional in my opinion. As a bonus a signature for the free-function accumulate could be like: ```cpp void accumulate(auto& i, auto& b, auto& e, auto& pred) { for (auto j = b; j != e; ++j) { pred(A[i][j][k]); } } ``` Secondly, everything is organized as it should: Adapters, Algorithms, and Library details, belong to their respective directories and don't seem to overreach so far. Nothing else to add here except to be careful about algorithms and not overreach here. Thirdly, The library is quite verbose, but it's expected so it's not really a problem in my opinion. So in conclusion, I would ACCEPT the library as is, but free-functions and syntactic sugar would be great as well! Best, Amlal amlalelmahrouss@icloud.com
A couple good questions were posed today on Reddit: "I see the change so it now instead of saying mdspan is incompatible with GPUs, it says it is but in a way that is "ad-hoc, needs markings, [and has] no pointer-type propagation" in contrast to Boost.Multi which is "via flatten views (loop fusion), thrust-pointers/-refs". Those terse words pack a lot of meaning, which I spend a while pondering, but I expect I could spend several weeks fleshing out more fully if I had the time! I think "needs markings" refers to code using mdspan needing annotations like __device__, although I see such annotations in the examples in CUDA examples of Boost.Multi's docs (as well as in Boost.Multi's library code itself), so I am unsure why mdspan code would be described as "needs markings" but not Boost.Multi. But more broadly, I think I see the idea is that Boost.Multi has more pythonic ergonomics, whereas mdspan is more a flexible vocabulary type with roughly zero overhead. This raises the several questions I don't see answered in Boost.Multi's docs: (1) How much overhead does using Boost.Multi add to GPU work compared to raw pointers or mdspan? The mdspan paper has microbenchmarks comparing it to raw pointers, showing it adds roughly zero-overhead. Getting that to be the case drove much of the design of mdspan. (2) How big of an advantage are Boost.Multi's ergonomics? When I read that mdspan lacks "thrust-pointers" it isn't obvious to me if that matters or not. I think perhaps an example showing the core ergonomic advantage of Boost.Multi could help clarify this. That would also help clarify if the limitations to mdspan are fundamental or it just needs some helper code which could be libraryitized. Which brings me to the final question -- (3) Should Boost.Multi be built around the std::mdspan and std::mdarray vocab types? It is preferable to use standardized vocabulary types unless there is a good argument why not, and in this case, I cannot tell if there is. An AccessorPolicy to mdspan can customize it with non-raw handles and fancy references, so Boost.Multi's doc saying mdspan doesn't support "pointer-type propagation" isn't quite right, it just needs some helper code in a library somewhere to make that happen. Could Boost.Multi be written to be that helper code, and if so, would that be a better approach?" Below is Alfredo's response to the above post: " 0) “needs markings” means “needs a custom version of mdspan with markings” 1) no expected overhead, all specifics of GPU pointers are compile time. GPU arrays are recognized as GPU by its pointer types; there is no runtime metadata on them. if mdspan accessor parameter can control the pointer types and that can be done easily I would say is not different then. 2) ergonomics: Multi works with all STL algorithms, all Thrust algorithms, (dispatching can be automatic and compile-time), and all Ranges algorithms 3) Multi should be interoperable with mdspan (and it is) and future mdarray. Implemented based on them? is not something practical, first because it will depend on the C++ version when they are available, also there are specific choices that makes it extremely difficult such as retrofitting iterators on mdspan and changing the “pointer” semantics of mdspan. mdarray is an adaptor on top of a container, this is quite a different approach than the one taken by Multi, that affects the level of control of initializing data. Implementing Multi on top of mdspan and mdarray would be fighting up hill. also will need to coordinate mdspan and mdarray which are separate sublibraries, one of which is only available in C++26. " Matt
A couple good questions were posed today on Reddit:
A followup from both parties on Reddit: The statement that there should be "no expected overhead" seems incorrect to me. Am I missing something? Consider references to a dynamic 2 dimensional object, the sort of thing that gets copied around a lot. ```c++ using M = std::mdspan<double, std::extents<size_t, std::dynamic_extent, std::dynamic_extent>>; using R = boost::multi::array_ref<double, 2>; ``` I measure: ```c++ sizeof(M) = 24 sizeof(R) = 72 M trivially copyable: true R trivially copyable: false ``` You can confirm this here: https://godbolt.org/z/n95Ws9KW5 So there is overhead making it 3x bigger, but surely there will also be runtime overhead from copying them around, including from host to GPU, and probably more register pressure. I think this example reflects the common case well. If the dimensions are known at compile time, the advantage of mdspan is greater. If the layout is strided, then the advantage is less. So dynamic and contiguous is the common situation, but also, an average example of the extra overhead. edit: I measure the size of decltype(std::declval<R&>().begin())to be 64 bytes; I was thinking in some cases the iterator gets passed instead of the array_ref. A bit smaller but not by a lot. And again Alfredo's Response: 0) These are good points but the original question was if there is a cost to pay for using typed-GPU-pointers instead of raw pointers, and the answer is still no. 1) The new question is about the size of the reference object. Yes, Multi's array-reference occupy more stack bytes than span, this is because they are more general and in principle they can hold padded data for example (which is going to be implemented in a next version). This extra sizes may not be reflected because reference-array are never in the heap and the compiler is able to optimize a lot in these structures. (the mdspan shouldn't be in heap also IMO, but I digress). Yes, it can bring extra bytes across compilation units, AFAIK, or yes when passing to GPU kernels (which I think is your point), but then the question do really want to pass reference-arrays to kernels. My opinion is not, you "pass" array in a different way, which is documented. array_ref's are not copy constructible so it won't work even if you try, (well, there is a hack but I don't recommend it). In summary, array-references live in the stack and can be heavily optimized, array-references are not meant to be passed as kernel arguments. 2) array-references are not copy constructible, this is by design to keep value and reference semantics clearly separated. So, it is not trivially-copy-constructible simply because it is not copy-constructible, not because it does something strange. And of course array-references are not trivially assignable, this is because assignment is deep (actual code needs to be executed), not shallow like the reseating of span or mdspan. This is again to maintain the separation between values and references. This properties and are documented.
On 3/5/26 14:26, Matt Borland via Boost wrote:
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. I have my own multidimensional array type that I use extensively, so I thought it would be interesting to compare my own efforts to the proposed library.
The proposed library uses an array-of-arrays model. By contrast, my own library uses an array-indexed-by-vector model. This makes iteration a lot more convenient with my type. I do a lot of iteration in my own code, so this is kind of a big deal. Compare: My multi_array type: multi_array<int, 2> a({5, 2}); for (auto &element: a) { element = 5; } for (auto index: a.get_bounds()) { a[index] = get<0>(index) + get<1>(index) * 10; } The proposed library: multi::array<int, 2> a({5, 2}); for (auto &row: a) { for (auto &element: row) { element = 5; } } auto [rows, columns] = a.extensions(); for (auto row_index: rows) { for (auto column_index: columns) { a[row_index][column_index] = row_index + column_index * 10; } } Aside from more compact code, one advantage of this kind of flattened access is that it makes it possible to write non-recursive generic functions that work on arrays with different dimensionality. It should be possible to add this kind of flat iteration to the proposed library without breaking any existing functionality. The proposed library makes extensive use of array views. This is useful both for performance and functionality, but it's also dangerous because it can lead to unintentional data aliasing and dangling pointers. My own approach is to just have the single owning multi_array type and no array views at all. This means a few extra unnecessary copies of data, but for my usage the performance penalty is completely acceptable. The proposed library provides just one function for changing the extents of an existing array: reextent. My type has member functions for inserting and removing rows/columns/planes/whatever-the-n-dimensional-equivalent-is, and I use them all the time. This is no big deal, since this functionality can be provided by external free functions. -- Rainer Deyke - rainerd@eldwood.com
sob., 7 mar 2026 o 10:58 Rainer Deyke via Boost <boost@lists.boost.org> napisał(a):
On 3/5/26 14:26, Matt Borland via Boost wrote:
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. I have my own multidimensional array type that I use extensively, so I thought it would be interesting to compare my own efforts to the proposed library.
The proposed library uses an array-of-arrays model. By contrast, my own library uses an array-indexed-by-vector model. This makes iteration a lot more convenient with my type. I do a lot of iteration in my own code, so this is kind of a big deal. Compare:
My multi_array type:
multi_array<int, 2> a({5, 2}); for (auto &element: a) { element = 5; } for (auto index: a.get_bounds()) { a[index] = get<0>(index) + get<1>(index) * 10; }
The proposed library:
multi::array<int, 2> a({5, 2}); for (auto &row: a) { for (auto &element: row) { element = 5; } } auto [rows, columns] = a.extensions(); for (auto row_index: rows) { for (auto column_index: columns) { a[row_index][column_index] = row_index + column_index * 10; } }
On the other hand, Boost.Multi is designed so that one usually doesn't need to do manual iteration. Your examples can be expressed as: multi::array<int, 2> a({5, 2}, 5); // start with 5's std::ranges::fill(a.elements(), 6); // fill with 6's multi::array<int, 2> b = restricted([](int i, int j) { return i * j * 10; }, multi::extensions_t{5, 2}); // start with index-dependent values Regards, &rzej;
On Sat, Mar 7, 2026, at 10:41 PM, Andrzej Krzemienski via Boost wrote:
multi::array<int, 2> a({5, 2}, 5); // start with 5's std::ranges::fill(a.elements(), 6); // fill with 6's multi::array<int, 2> b = restricted([](int i, int j) { return i * j * 10; }, multi::extensions_t{5, 2}); // start with index-dependent values
Regards, &rzej;
Not a review, but I have seen `multi::extensions_t` and `a.extensions()` and wonder whether any reviewers have already mentioned that that name seems off? I'm not a native speaker, but with a bit of math background and experience with other [multi-dimensional] libraries I expect these to be extent, extents, extents_t (see e.g. https://en.cppreference.com/w/cpp/types/extent.html, https://www.boost.org/doc/libs/latest/libs/multi_array/doc/user.html#sec_dim...) It would be nice to adhere to common jargon instead potentially creating friction/confusion for users coming to Boost.Multi. Just a note in case you want to consider it. Seth
Hello Seth, Yes `multi::extensions_t` and `a.extensions()` does read as off. But Boost.Multi has already a container called 'extents' (https://github.com/correaa/boost-multi/blob/b7f4dcf08b18628e1ae7aba9f44f7124...) Which does make use of `extensions_type`. From what I understand so far about the library's design, `extensions_type serves as an underlying type of the `extents` type, which could explain the odd naming. Best, Amlal
niedz., 8 mar 2026 o 05:35 Amlal El Mahrouss via Boost < boost@lists.boost.org> napisał(a):
Hello Seth, Yes `multi::extensions_t` and `a.extensions()` does read as off. But Boost.Multi has already a container called 'extents' ( https://github.com/correaa/boost-multi/blob/b7f4dcf08b18628e1ae7aba9f44f7124... ) Which does make use of `extensions_type`.
From what I understand so far about the library's design, `extensions_type serves as an underlying type of the `extents` type, which could explain the odd naming.
I agree with Seth's position here. The correct English word for the concept is `extents`, and this is what the users should see. There is even no clash with using the same name for the public type and the member function: multi::extents ext = arr.extents(); Boost.Multi has a PR issue for that: https://github.com/correaa/boost-multi/issues/127 If the library needs a "private detail" type for being a base of something, because this is a "private detail", it can afford to have a longer name, like "basic_extents". BTW, the above ticket also shows how the different initialization syntax choices can lead to bugs. Regards, &rzej;
Hi Andrzej, Firstly, Yes indeed I agree to both parts, I should mention that I am not an English native speaker as well. And secondly, regarding the PR, I agree that dropping extensions_t to extents and thus dropping the suffix _t. I have nothing to add here. However, I am curious about your "private detail" part of your message, I think we should still keep the name short in most cases, unless it's a container (like basic_extents as you mentioned). Can you elaborate about that last part? Best, Amlal
niedz., 8 mar 2026 o 09:30 Amlal El Mahrouss via Boost < boost@lists.boost.org> napisał(a):
Hi Andrzej,
Firstly, Yes indeed I agree to both parts, I should mention that I am not an English native speaker as well.
And secondly, regarding the PR, I agree that dropping extensions_t to extents and thus dropping the suffix _t. I have nothing to add here.
However, I am curious about your "private detail" part of your message, I think we should still keep the name short in most cases, unless it's a container (like basic_extents as you mentioned).
Can you elaborate about that last part?
Sure. I made an assumption that because header multi/detail/extents.hpp has"detail" in its path, it follows the Boost practice and represents an implementation detail (as opposed to a component exposed to the customers). This was reinforced by my seeing "namespace detail" in the header contents. But now, upon closer inspection, I can see that class extents are not in that namespace. And now I am confused. Usually, it is the Reference section that determines what is officialand what is the implementation detail. But this library doesn't have an adequate Reference section. Regards, &rzej;
Best, Amlal _______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/GAEGFG6O...
Ah yes, It's also something I noticed as well, that makes me lean towards a CONDITIONAL ACCEPT now. Best, Amlal
On 3/7/26 22:41, Andrzej Krzemienski via Boost wrote:
sob., 7 mar 2026 o 10:58 Rainer Deyke via Boost <boost@lists.boost.org> napisał(a):
On 3/5/26 14:26, Matt Borland via Boost wrote:
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory. I have my own multidimensional array type that I use extensively, so I thought it would be interesting to compare my own efforts to the proposed library.
The proposed library uses an array-of-arrays model. By contrast, my own library uses an array-indexed-by-vector model. This makes iteration a lot more convenient with my type. I do a lot of iteration in my own code, so this is kind of a big deal. Compare:
My multi_array type:
multi_array<int, 2> a({5, 2}); for (auto &element: a) { element = 5; } for (auto index: a.get_bounds()) { a[index] = get<0>(index) + get<1>(index) * 10; }
The proposed library:
multi::array<int, 2> a({5, 2}); for (auto &row: a) { for (auto &element: row) { element = 5; } } auto [rows, columns] = a.extensions(); for (auto row_index: rows) { for (auto column_index: columns) { a[row_index][column_index] = row_index + column_index * 10; } }
On the other hand, Boost.Multi is designed so that one usually doesn't need to do manual iteration. Your examples can be expressed as:
multi::array<int, 2> a({5, 2}, 5); // start with 5's std::ranges::fill(a.elements(), 6); // fill with 6's multi::array<int, 2> b = restricted([](int i, int j) { return i * j * 10; }, multi::extensions_t{5, 2}); // start with index-dependent values
That's great for these trivial examples, which I based on similar trivial examples in the Boost.Multi documentation. Not so much if I'm trying to run a Gaussian blur on my data. Or run a cellular automaton. Or even just render a 2D tile map on the screen. -- Rainer Deyke - rainerd@eldwood.com
This is my formal review of Boost.Multi. On 3/5/26 14:26, Matt Borland via Boost wrote:
- What is your evaluation of the design?
While the array-of-arrays view of a multidimensional array can (occasionally) be useful, I find the lack of direct element addressing using a tuple/array/vector type problematic. The rotated/unrotated/transposed member functions are specific cases of the more general concept of changing the order of the dimensions. A simple direct function should be added. The use of "rotated" to switch the order of dimensions is unfortunate because it conflicts with the meaning of rotating an image, i.e. turning this: {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} ...into this: {{7, 4, 1}, {8, 5, 2}, {9, 6, 3}} Instead, it turns it into this: {{1, 4, 7}, {2, 5, 8}, {3, 6, 9}} ...which, geometrically seen, is flipping operation over the diagonal, not a rotation. "taked" is not a word in the English language. Based on the context, I think the correct word is "taken". The use of unary operators + and ~ on arrays conflicts with the potential elementwise use of the same operators: using boost::multi::elementwise; boost::multi_array<my_class, 2> a{2, 3}; auto b = -a; // Creates a lazily evaluated array that applies // operator- to its elements. auto c = +a; // Creates an eager copy of the array, without applying // operator+ to its elements. The name "dynamic_array" implies that it is more dynamic than plain "array", but the opposite is true. This class should be renamed, possibly to "fixed_array" or "static_array". The boost::multi::array::reextent function is not clearly defined. Given this input: {{1, 2}, {3, 4}} what happens if I call reextent({3, 1}, 0)? Do I get this: {{1, 2, 0}} or do I get this: {{1, 2, 3}} I don't like the name "restriction" for a lazy array. I don't like the name "extensions" for what I would call "extents". The factory function "restricted" takes its argument in the opposite order from how the class "restriction" takes its template arguments. Why? The constructors of boost::multi::dynamic_array and boost::multi::array, as well as the function boost::multi::array::reextent, require copy construction. It would be more flexible to allow emplacement from any set of variables that can be used to construct an element, similar to std::vector::emplace_back.
- What is your evaluation of the implementation?
I only took a cursory look at it, mostly to make up for deficiencies in the documentation.
- What is your evaluation of the documentation?
It's pretty bad. 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. Also on a technical level, the right side of some of the tables is cut off on my web browser even though there is blank space to the right of the table. I am forced to rely on horizontal scrolling to see the whole table, and there isn't even a horizontal scroll bar. The tutorial recommends using "auto &&" or "auto const &" for holding subarrays instead of "auto". I disagree. A subarray is not a reference, no matter how much it tries to act like one, and holding it by reference is a good way to get a dangling reference. I am fully aware that local variables of reference can extend the lifetime of a temporary, but I consider it messy to rely on this unless absolutely necessary. The reference for functions does not include the full function prototype, with return type and argument types. The boost/multi/elementwise.hpp header is not documented at all in the reference section. The documentation for (I assume) boost::multi::subarray::const_element_ref just calls it element_ref. 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? The return types of boost::multi::subarray::elements and boost::multi::array_ref::elements should be documented. The reference for boost::multi::dynamic_array::elements says that it is overridden from its base type. This is not true, it inherits from boost::multi::array_ref::elements (which does override boost::multi::subarray::elements). The references for dynamic_array and array have a bunch of references to array_ref and dynamic_array respectively for functionality that is ultimately inherited from subarray. It would be better to point these references directly to where the functionality is defined. 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. boost::multi::inplace_array is mentioned once but never documented. "operator⇐" should be "operator<=".
- What is your evaluation of the potential usefulness of the library? Do you already use it in industry?
I think a good multidimensional array type is very useful, and I use my own multidimensional array type all the time. The question is if boost::multi::array qualifies as a good multidimensional array type.
- Did you try to use the library? With which compiler(s)? Did you have any problems?
No.
- How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
I spent most of the day on it.
- Are you knowledgeable about the problem domain?
I've written my own multidimensional array class and I've used it extensively.
Ensure to explicitly include with your review: ACCEPT, REJECT, or CONDITIONAL ACCEPT (with acceptance conditions).
I would love to have a good multidimensional array library in Boost. Unfortunately, Boost.Multi is not such a library in its present state. At the very least, I would need the following changes before I can accept the library: - The documentation needs to be completed. - I want to either be able to index the array using a tuple/array/vector type, or a damn good rationale or why this isn't possible. Unfortunately the problems with the documentation are so severe that I feel that another review is needed after the documentation is completed before the library can be accepted. I therefore vote to REJECT this library at the current time in its current state. -- Rainer Deyke - rainerd@eldwood.com
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.
Also on a technical level, the right side of some of the tables is cut off on my web browser even though there is blank space to the right of the table. I am forced to rely on horizontal scrolling to see the whole table, and there isn't even a horizontal scroll bar.
Could you please open issues for these two technical problems including the platform, browser, and screenshots if possible against https://github.com/boostorg/boostlook/issues? Multi uses these boostlook stylesheets, so these same issues will be present on other Boost libraries.
Unfortunately the problems with the documentation are so severe that I feel that another review is needed after the documentation is completed before the library can be accepted. I therefore vote to REJECT this library at the current time in its current state.
Thank you for taking the time to review and provide comments on the library. Matt
On Thu, Mar 5, 2026 at 9:34 PM Matt Borland via Boost <boost@lists.boost.org> wrote:
Dear All,
The review of Multi by Alfredo Correa begins today, March 5th and goes through March 15th, 2026.
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory.
Code: https://github.com/correaa/boost-multi Docs: https://correaa.github.io/boost-multi/multi/intro.html
For reviewers, please use the master branch.
Please provide feedback on the following general topics:
- What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? Do you already use it in industry? - Did you try to use the library? With which compiler(s)? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? - Are you knowledgeable about the problem domain?
Below is my review of boost.multi: ## Design It also looks like a mature library written for use in a particular code base with different style one might expect from a boost library. Part of that is also that authors (reviewer included) add things to libraries that are useful for the early users but not really worth including in a boost library. I think the names are off, e.g. `multi::array` behaves like a `std::vector`. I would recommend a rename them as follows: - `array` -> `vector` - `array_ref` -> `span` - `inplace_array` -> `array` Furthermore, the `subarray` shouldn't exist, but just be a `span`, i.e. the same type as `array_ref`. I would also just remove the `dynamic_array` type. First of all: it's not dynamic, since it can't dynamically change its size. Building this type with a `unique_ptr` and a `array_ref` is trivial. I don't think there's any need. I find the implementation a bit odd. `subarray` inherits `const_subarray` (which is also public) and then just casts away `const`. I don't get it, why couldn't that just behave like `span` with `const T` and `T`? I'd imagine a `array_ref<T, 3>` just needs to hold a `array<size_t, 2> strides_` as members. What is the point of `extensions()` [probably should be `indeces`] ? Is this just so I can use a range based for loop instead of the old-school one? I think it's unfortunate that `array_ref` only accepts a `pointer` and a set of `extends`. I would really like to have a "contiguous container + strides" constructor: std::vector<int> vec {1,2,3,7,8,9,4,5,6}; boost::multi::array_ref<int, 2> arr({3}, vec); // deduce the number of rows The types have special handling of an array size of `1`. I don't know why this is needed at all, what's the use-case? Why is `data()` restricted to the single dimension array? I don't like the algorithms (`diagonal, `slice`, etc.) being members. I prefer them as free functions in old-school fashion like so: auto slice(array_ref<T, N> &, size_t first, size_t last) Or array_ref<T, N-1> diagonal(array_ref<T, N>); For those who fance daisy-chains a `std::ranges` compatible API could be provided. Is the term `restriction` a general one or specific to this library? It looks like `generator` or `expressions` might be better terms, but maybe it's domain specific terminology I am unaware of. ## Implementation I find the code hard to read because it's full of indirections and base classes that seem to have a single use. I think all of this could be simplified, but since it's not mine to maintain, that doesn't matter for this review. There are some oddities in the code, liks this: constexpr auto operator=(array_ref&& other) && noexcept(std::is_nothrow_copy_assignable_v<T>) -> array_ref& { if(this == std::addressof(other)) { return *this; } // lints(cert-oop54-cpp) operator=(std::as_const(other)); return *this; } If we're doing this check, why is it only done in the non-const version? I also looked at a few test files and they looked sufficient. What's `multi/algorithm/redux.hpp` for? `multi/io.hpp` should #include `iosfwd` not `iostream`. ## Documentation The docs are alright, except for the reference section. E.g. we only get the function names, but not the argument list or return_type. ## Usefulness I like this library and I think I'll use it after the review independent of outcome. I currently hand-coding this stuff at the moment. ## Usage I used gcc 15.2 and all the tests passed. I tried a few minor things and it worked as expected. ## Effort I spend about 3 hours reading the docs, looking at the code and building some toy applications. ## Knowledge I am doing rather simple things with 2D & 3D arrays, so I should not be considered an expert here. ## Conclusion I am a bit conflicted, I see three major issues: 1. Many names are off, in a way that goes beyond bikeshedding. We have `std::array<int, 3>` and `multi::array<int, 3>`, and both do fundamentally different things. Restrictions don't restrict and extensions to extend. 2. Some conventions don't seem to fit boost. 3. The reference section is not helpful. The library is definitely useful, but too idiosyncratic for boost. Therefor, I recommend to CONDITIONAL ACCEPT the library with the following conditions: 1. Rename the types so they are intuitive to C++ developers, for example as described above. 2. Remove or generalize (make available to all N) functions currently only available for N =1 (e.g. data()) 3. Make array_ref and subarray the same type. 4. Provide algorithms as free functions. 5. Remove the `extensions()` function if it is the same as a C for-loop. 6. Remove `dynamic_array` or give it a better name like `unique_array`. I am not making the reference a part of the conditions, because "make it better" is not a condition that can be fulfilled without another review.
Therefor, I recommend to CONDITIONAL ACCEPT the library with the following conditions:
1. Rename the types so they are intuitive to C++ developers, for example as described above. 2. Remove or generalize (make available to all N) functions currently only available for N =1 (e.g. data()) 3. Make array_ref and subarray the same type. 4. Provide algorithms as free functions. 5. Remove the `extensions()` function if it is the same as a C for-loop. 6. Remove `dynamic_array` or give it a better name like `unique_array`.
Thank you for your review and comments Klemens. Your acceptance conditions are noted. Matt
Matt, Alfredo, et al. Here is my review of Boost.Multi documentation. For the record I am the technical writer for the C++ Alliance. Overall, the library should be usable with the current documentation, though there were quite a few issues that could be considered to improve it. Did largely like the tone of the text, the short and frequent examples, the correct use of formatting for code syntax, and the overall structure is about right. I appreciate the effort that has been put into this library so far. My notes: 1. Given that standard C++ already supports multi-dimensional arrays, I think it could be clearer in the Introduction what this library brings to the developer that is missing from the Standard. I would make the differences explicit - what features the Standard does not support. Perhaps this is just a wording change - from "Features of this library that aim to facilitate the manipulation of multidimensional arrays include:" to "Features of this library, that are not present in the Standard, that aim to facilitate the manipulation of multidimensional arrays include:". Though be careful to add further qualification to the features if any are present, in whole or in part, in the Standard. Is the Standard known for making "unnecessary copies" for example, if so, articulate this issue. Same deeper explanation for your other features. 2. In the Introduction again, I would have liked some more discussion on use cases. Multi-dimensional arrays are often used in real-time simulations for example, to provide physical data super-quickly without the need for equations or other computationally expensive approach. If there are other compelling examples (AI, finance, astrophysics, etc.), great to be specific and list them out. *This does have the effect of drawing in developers working in those areas - particularly if you can link the unique features of the library mentioned above to those use cases.* 3. Nit: typo - "althoug": The library’s primary concern is with the storage and logic structure of data. It doesn’t make algebraic or geometric assumptions about the arrays and their elements. (*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. In the Intro there is this statement: Multi is a header-only library and C++17 or later is required. Good info, but other requirements are scattered throughout the doc. Perhaps have a "Requirements" sub-heading and add OS/Compiler or other requirements? Later on in the doc there is: The code requires any modern C++ compiler (or CUDA compiler) with standard C++17 support; for reference,... All statements like this belong in the Requirements section, which itself should follow the Introduction. 5. Nit. Odd, truncated sentence in the table section: Why is dimensionality static and the sizes are dynamic? *Sizes are fixed or * 6. Navigation. Scroll to the end of a page there is no navigation options - great to see " *next*" and "*previous*" page links. Currently a bit tedious to have to relocate my place in the TOC. 7. Almost no linking to outside sources. For example, all the following sentences could contain a link (as could many others): Testing the library requires *Boost.Core* (headers), The library works out-of-the-box in combination with the *Thrust *library. The library works out of the box with Eric Niebler’s *Range-v3 *library, *CUDA *is a dialect of C++ that allows writing pieces of code for GPU execution, or with the standard *MDSpan* *proposal *std::mdspan. In the *next sectio*n instead we will see an example of arrays owned by the library. [link to next section where this example is] 8. Like the links to Compiler Explorer, but not the linking word "*online*" or "*live*" - doesn't say what the link is to - "Compiler Explorer" or whatever the title of the destination is would be much clearer. 9. Nits. Remove unnecessary (and slightly condescending) words - *simple *or *a simple* adds no meaning (same for the word "very"), two examples: Once installed, other CMake projects (targets) can depend on Multi by adding *a* *simple *add_subdirectory ... change this to: Once installed, other CMake projects (targets) can depend on Multi by adding add_subdirectory ... Same for the following, and any other use of simply or very: —something manual indexing *simply *cannot do cleanly at scale. 10. Only add brackets where really necessary, they are an obstacle to smooth reading, such as here: (Although most of the examples use numeric elements for conciseness, the library is designed to hold general types (e.g. non-numeric, non-trivial types, like std::string, other containers or, in general, user-defined value-types.) A (mutable) array can be assigned (The Cereal XML and Boost XML output would have a similar structure.) (de)serializing [spell it out "when serializing or deserializing a ..."] - and many more examples. 11. Perhaps "Getting Started" is a friendlier title than *Primer (basic usage)* and "Tutorials" better than *Tutorial (advanced usage)*. However, the tutorials are not really tutorials unless they contain numerical steps that a student can follow. As it is - *Advanced Usage Examples*, or a similar title, is more accurate. 12. Nit. Typo: Because this array is not *controled* by the library, this type of object is called an array-reference. Correct spelling is controlled. 13. Nit. Typo: why the colon? Note that this is also different from creating a named *reference:* 14. Style nit. Make reference material impersonal - not "In my experience, however, the following" more "Usage patterns can produce .....". Library documentation is formal and not a blog post, so "opinions" are mostly inappropriate - expressing factually with conditions is more appropriate. 15. Typo - sentence starting with lower case: *which* uses the default Thrust device backend ( 16. Typo - upper case C needed: Why is *c++*17 chosen as the minimum? 17. Not crazy about punctuation in headings, for example: *{fmt}* - would be better as "Formatting" *Copy, and assigment (, and aliasing)* is ugly, "Copy, Assignment and Aliasing" works better Perhaps use Title case for headings - capitalize nouns, verbs, adjectives, not articles, prepositions or conjunctions. Avoid punctuation if at all possible. 18. Not sure about the following example code: Compile time evaluation constexpr auto trace() { multi::array<int, 2> arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; arr[2][2] = 10; return std::accumulate(arr.diagonal().begin(), arr.diagonal().end()); } static_assert( trace() == 4 + 2 + 10 ); If arr[2][2] is 10, shouldn't the assert be "1 + 5 + 10", or have I misunderstood "diagonal"? 19. Reference - I agree with earlier comments that the Reference should be more complete. Each function/method should have its own page (or section) to include a brief sentence on its purpose, then syntax, parameters, return values, errors and exceptions, remarks for a fuller description if necessary, and for bonus points an example, or link to an example, of its use. 20. Appendix - How to's is not appendix material - belongs after or part of tutorials (perhaps renamed to "Advanced Use Cases" or similar). Currently my feelings are that this library should have a CONDITIONAL ACCEPTANCE that the features added are a significant enough improvement over the current Standard, and that those improvements are clearly articulated in the Introduction section of the docs. And some work on the Headings, Reference and Navigation issues would help a lot. Hope this helps. - best and good luck with the project! - Peter Turcan On Sun, Mar 8, 2026 at 7:49 AM Matt Borland via Boost <boost@lists.boost.org> wrote:
Therefor, I recommend to CONDITIONAL ACCEPT the library with the following conditions:
1. Rename the types so they are intuitive to C++ developers, for example as described above. 2. Remove or generalize (make available to all N) functions currently only available for N =1 (e.g. data()) 3. Make array_ref and subarray the same type. 4. Provide algorithms as free functions. 5. Remove the `extensions()` function if it is the same as a C for-loop. 6. Remove `dynamic_array` or give it a better name like `unique_array`.
Thank you for your review and comments Klemens. Your acceptance conditions are noted.
Matt _______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/VMXE23M3...
Currently my feelings are that this library should have a CONDITIONAL ACCEPTANCE that the features added are a significant enough improvement over the current Standard, and that those improvements are clearly articulated in the Introduction section of the docs. And some work on the Headings, Reference and Navigation issues would help a lot.
Hope this helps. - Peter Turcan
Thank you as always for the thorough review of the Documentation. Your conditions and notes are noted. Matt
On 3/8/26 15:08, Klemens Morgenstern via Boost wrote:
I think the names are off, e.g. `multi::array` behaves like a `std::vector`.
It really doesn't. In math, a vector is a 1D array of coordinates, representing a magnitude and a direction. std::vector is already poorly named, since it doesn't provide any of the mathematical operations of a real vector, but does at least keep the core concepts: it is an array, and it is 1D. A 2D array in math is a matrix, not a vector. In addition, multi::array doesn't provide the core features of std::vector, such as push_back or insert or erase. -- Rainer Deyke - rainerd@eldwood.com
On Wed, Mar 11, 2026 at 8:21 PM Rainer Deyke via Boost < boost@lists.boost.org> wrote:
On 3/8/26 15:08, Klemens Morgenstern via Boost wrote:
I think the names are off, e.g. `multi::array` behaves like a `std::vector`.
It really doesn't.
In math, a vector is a 1D array of coordinates, representing a magnitude and a direction. std::vector is already poorly named, since it doesn't provide any of the mathematical operations of a real vector, but does at least keep the core concepts: it is an array, and it is 1D.
A 2D array in math is a matrix, not a vector.
In addition, multi::array doesn't provide the core features of std::vector, such as push_back or insert or erase.
Fair point, plus vector in computing sounds like SIMD to most non-C++ people these days. How about `tensor` ?
-- Rainer Deyke - rainerd@eldwood.com
_______________________________________________ Boost mailing list -- boost@lists.boost.org To unsubscribe send an email to boost-leave@lists.boost.org https://lists.boost.org/mailman3/lists/boost.lists.boost.org/ Archived at: https://lists.boost.org/archives/list/boost@lists.boost.org/message/3CEJKGPD...
Dear All,
The review of Multi by Alfredo Correa begins today, March 5th and goes through March 15th, 2026.
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory.
Code: https://github.com/correaa/boost-multi Docs: https://correaa.github.io/boost-multi/multi/intro.html
For reviewers, please use the master branch.
Please provide feedback on the following general topics:
- What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? Do you already use it in industry? - Did you try to use the library? With which compiler(s)? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? - Are you knowledgeable about the problem domain?
Ensure to explicitly include with your review: ACCEPT, REJECT, or CONDITIONAL ACCEPT (with acceptance conditions).
Additionally, if you would like to submit your review privately, which I will anonymize for the review report, you may send it directly to me at: matt_at_mattborland.com.
This is a reminder that we are now past the halfway mark in the review period which will close on Sunday. If you have not submitted a review and would like to, or are thinking about writing one, please keep this timeline in mind. Matt
On Thu, 5 Mar 2026 at 14:37, Matt Borland via Boost <boost@lists.boost.org> wrote:
Dear All,
The review of Multi by Alfredo Correa begins today, March 5th and goes through March 15th, 2026.
Hi all, This is my review of the proposed Boost.Multi. Thanks Alfredo for submitting the library and Matt for managing the review.
- What is your evaluation of the potential usefulness of the library? Do you already use it in industry?
I think that it has the potential to be very useful. I don't use it today, but having it would have been beneficial for one of my former clients. IMO a library like this should have a place in Boost.
- What is your evaluation of the design?
I think the library got its core decisions right. Iterating, indexing and slicing are elegant. Lazy arrays are great. Interaction with std::mdspan could be better. At the moment, we have a godbolt link with some non-trivial code. mdspan is already standardized (C++23) and available on some standard libraries. Functions for easy interoperability should be provided. C++26 linalg [1] is based on std::mdspan, so providing this will be important in the future. As other reviews have mentioned, the reference section is incomplete, and there are a lot of types and functions in the public boost::multi namespace that are not documented. This creates a gap on what I can evaluate in this review. I don't know if many of these functions (e.g. data(), data_elements()) are supposed to be used by the end user or not. Aside from this, there are a number of surprising decisions that have the potential for creating trouble: * Indexing uses signed rather than unsigned integers. From a conversation with the author in Slack, signed-ness was chosen to allow negative indices (like Python does). The problem is that the actual type is buried within multiple layers of type aliases. One of these aliases, multi::size_t, is actually signed, making things even more confusing. I'd advise to: * Try to implement the negative indexing feature before anything else. It doesn't sound like a trivial thing, and should probably be done before committing to API stability. * Remove as many type aliases as possible. Use std::ptrdiff_t everywhere, which immediately tells the user the type that they're using. Especially multi::size_t. * Arrays overload the "address of" (operator&). I don't think this is good practice at all. It's also not mentioned in the docs. With my system fmtlib (v9.1), printing subarrays fails to compile because of this. While it's true that fmtlib should be using std::addressof instead of raw operator&, we could avoid the problem altogether. * Functions like strided() can lead to non-obvious undefined behavior. I don't think this should be the case. If the performance gain (after measuring) is significant, an strided_unsafe() can be added. Docs mention "The third case is that of iterators of transposed array." regarding UB, which I don't really understand. * Initializing and resizing arrays leads to uninitialized elements by default. I think this default is confusing, especially because there is a multi::uninitialized_elements tag. I'd expect that elements are initialized (safe default) unless I explicitly use multi::uninitialized_elements ("I know what I'm doing"). * Many library functions and classes have lots of template parameters. I'd advise using C++20 concepts (properly guarded in C++17 builds) to improve error messages and document the requirements for each type. * I personally don't like unary operator+ for making copies. It's not communicating intent. I'd go for what numpy.ndarray does and call it array::copy(). * Subarrays and array references are not copyable. This is unusual, since all reference types I know of (span, mdspan, string_view, url_view, mysql::rows_view) are copyable. It looks like the library tries to make the type appear like a reference, but I don't think that's wise - exotic references are not language references, no matter how hard you try. I would make subarrays copyable to avoid surprises. Maybe this is my lack of understanding, but subarray and array_ref seem similar enough to be implemented using the same class. - What is your evaluation of the implementation? It's difficult to follow, and it has a non-trivial amount of technical debt. Some points to note: * include/ contains many files that are not headers. It contains docs, build scripts, CMakeLists.txt and empty files. include/ shouldn't contain any of this because it's installed with the library to the consumer. Some of the headers under "adaptors/" seem to be public API, others seem to be examples or tests. * There are many files containing commented leftover code and #if 0 preprocessor blocks. * There is an unscoped, undocumented NOEXCEPT_ASSIGNMENT macro in array.hpp. This needs either to be renamed to BOOST_MULTI_NOEXCEPT_ASSIGNMENT and documented, or (better) removed. * Same with MULTI_USE_HIP (although I understand that this one may be unavoidable). * The min and max functions need to be guarded against macro substitutions. * None of the examples seem to be built by CI, and some are not in the CMakeLists.txt either. They need cleanup and comments to explain what's going on. * A considerable amount of the test suite is missing from the test Jamfile. This is problematic because your main source of compiler coverage happens by running b2, not CMake. * "examples/" should be renamed to "example/". * There seems to be an operator referencing dynamic_array_cast, but I can't find this function defined anywhere. It seems to be mentioned by the docs, but the function isn't there. * What's the rationale behind implementing a custom tuple type? - What is your evaluation of the documentation? As other reviewers mentioned, the reference needs to be reworked. I won't insist here, since it's already been mentioned. Public adaptor code needs to appear in the reference, too. Same for config macros that might be defined by the user. The discussion is useful and explains the rationales well. I'd change the "Advanced usage" section title, since constructing an array is not advanced usage. I'd also try to introduce concepts incrementally. For instance, slicing should probably come before assignment, since you use slicing to discuss assignment. Some minor points: * I'd advise using snake_case for variables in the examples. * https://correaa.github.io/boost-multi/multi/tutorial.html#tutorial_init Prefer std::unique_ptr to raw new and delete in the examples. * Some sections refer to the "std::mdspan proposal". It's already in the standard, so this should be updated. * The BIP example in the interoperability section seems to contain unbalanced scopes (unmatched '{}' characters). * https://correaa.github.io/boost-multi/multi/interop.html#interop_substitutab... " In a departure from standard containers, elements are left initialized..." should be uninitialized. * https://correaa.github.io/boost-multi/multi/tutorial.html#tutorial_slices_an... "Other notations are available, for example this is equivalent to A(multi::" contains a rendering error. * https://correaa.github.io/boost-multi/multi/interop.html#interop_serializati... "Large datasets tend to be serialized slowly for archives with heavy formatting. Here it is a comparison of speeds when serializing and deserializing a 134 MB 4-dimensional array of with random double elements." Should be "Here is..." and "4-dimensional array of random double elements". * "Gettings started" should be "Getting started" in the nav bar. * reinterpret_cast_array is mentioned in the docs but it does not exist. - Did you try to use the library? With which compiler(s)? Did you have any problems? I've run some of the examples in the docs in my Ubuntu 24.04 machine with clang-22, C++23, libc++ and CMake in Debug mode. No problems aside from the fmtlib problem with subarrays already discussed. - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? I've read the documentation, cloned the library, scanned the source code for things that called my attention and built some toy examples. I haven't delved into the GPU adaptors because it's outside my knowledge. I've spent around 8h with the review. - Are you knowledgeable about the problem domain? Not an expert. I've done some very basic scientific programming in C++. My main expertise is networking.
Affiliation disclosure
I'm currently affiliated with the C++ Alliance.
Ensure to explicitly include with your review: ACCEPT, REJECT, or CONDITIONAL ACCEPT (with acceptance conditions).
My current recommendation is to REJECT the library at this point. This is not a hard rejection. I'd like to see this library being reviewed again in 2-3 months, when the points raised in the review are fixed. Similar to what we did with Boost.Decimal. I was close to recommending conditional acceptance though, so please keep up the good work. Regards, Rubén. [1] https://en.cppreference.com/w/cpp/numeric/linalg.html
On 3/12/26 17:02, Ruben Perez via Boost wrote:
* Subarrays and array references are not copyable. This is unusual, since all reference types I know of (span, mdspan, string_view, url_view, mysql::rows_view) are copyable. It looks like the library tries to make the type appear like a reference, but I don't think that's wise - exotic references are not language references, no matter how hard you try. I would make subarrays copyable to avoid surprises.
I would agree, except that this would actually break some major uses of subarrays. For example: multi::array<int, 2> a({5, 5}), b({5, 5}); a({0, 2}, {0, 2}) = b({0, 2}, {0, 2}); This copies a 2×2 square from one array to another array. a's operator() returns a subarray. b's operator() returns another subarray. The first subarray's operator= copies the data from b to a. This can only work if subarray::operator= copies the actual data instead of changing the array the subarray refers to. -- Rainer Deyke - rainerd@eldwood.com
On Thu, 12 Mar 2026 at 20:04, Rainer Deyke via Boost <boost@lists.boost.org> wrote:
On 3/12/26 17:02, Ruben Perez via Boost wrote:
* Subarrays and array references are not copyable. This is unusual, since all reference types I know of (span, mdspan, string_view, url_view, mysql::rows_view) are copyable. It looks like the library tries to make the type appear like a reference, but I don't think that's wise - exotic references are not language references, no matter how hard you try. I would make subarrays copyable to avoid surprises.
I would agree, except that this would actually break some major uses of subarrays. For example:
multi::array<int, 2> a({5, 5}), b({5, 5}); a({0, 2}, {0, 2}) = b({0, 2}, {0, 2});
This copies a 2×2 square from one array to another array. a's operator() returns a subarray. b's operator() returns another subarray. The first subarray's operator= copies the data from b to a. This can only work if subarray::operator= copies the actual data instead of changing the array the subarray refers to.
Fair. I stand corrected.
Dear All,
The review of Multi by Alfredo Correa begins today, March 5th and goes through March 15th, 2026.
Multi is a modern C++ library that provides manipulation and access of data in multidimensional arrays for both CPU and GPU memory.
Code: https://github.com/correaa/boost-multi Docs: https://correaa.github.io/boost-multi/multi/intro.html
For reviewers, please use the master branch.
Please provide feedback on the following general topics:
- What is your evaluation of the design? - What is your evaluation of the implementation? - What is your evaluation of the documentation? - What is your evaluation of the potential usefulness of the library? Do you already use it in industry? - Did you try to use the library? With which compiler(s)? Did you have any problems? - How much effort did you put into your evaluation? A glance? A quick reading? In-depth study? - Are you knowledgeable about the problem domain?
Ensure to explicitly include with your review: ACCEPT, REJECT, or CONDITIONAL ACCEPT (with acceptance conditions).
Additionally, if you would like to submit your review privately, which I will anonymize for the review report, you may send it directly to me at: matt_at_mattborland.com.
This is a reminder that the review period for Mutli ends tomorrow. Matt
participants (8)
-
Amlal El Mahrouss -
Andrzej Krzemienski -
Klemens Morgenstern -
Matt Borland -
Peter Turcan -
Rainer Deyke -
Ruben Perez -
Seth