Reference
Fundamental types and concepts
The library interface presents several closely related C++ types (classes) representing arrays.
The fundamental types represent multidimensional containers (called array), references that can refer to subsets of these containers (called subarray), and iterators.
In addition, there are other classes for advanced uses, such as multidimensional views of existing buffers (called array_ref) and non-resizable owning containers (called dynamic_array).
When using the library, it is simpler to start from array, and other types are rarely explicitly used, especially if using auto;
however, it is convenient for documentation to present the classes in a different order since the classes subarray, array_ref, dynamic_array, and array have an is-a relationship (from left to right).
For example, array_ref has all the methods available to subarray, and array has all the operations of array_ref (and more).
Subarrays
A subarray-reference is part (or a whole) of another larger array, and they are represented by multi::subarray<T, D, P = T*> in the library.
It is important to understand that subarray s have referential semantics, their elements are not independent of the values of the larger arrays they are part of.
An instance of this class represents a subarray with elements of type T and dimensionality D, stored in memory described by the pointer type P.
(T, D, and P initials are used in this sense across the documentation.)
Instances of this class have reference semantics and behave like "language references" as much as possible.
As references, they cannot be rebinded or resized; assignments are always "deep".
They are characterized by a size that does not change in the lifetime of the reference.
They are usually the result of indexing over other multi::subarray 's and multi::array 's objects, typically of higher dimensions;
therefore, the library doesn’t expose constructors for this class.
The whole object can be invalidated if the original array is destroyed.
(Additionally, multi::const_subarray provides a similar interface to multi::subarray but it protects referenced elements from being modified.)
multi::subarray<T, D, P = T*> member types
subarray::… |
Description |
|---|---|
|
|
|
|
|
|
|
indexing type in the leading dimension (usually |
|
describe size (number of subarrays) in the leading dimension (signed version of pointer size type, usually std::diffptr_t) |
|
describe ranges of indices, constructible from braced indices types or from an extension_type. Can be continuous (e.g. {2, 14}) or strided (e.g. {2, 14, /every/ 3}) |
|
describe a contiguous range of indices, constructible from braced index (e.g. |
|
describe index differences in leading dimension (signed version of pointer size type, usually |
|
|
|
|
|
describe a random-access iterator in the leading dimension |
|
describe a random-access iterator in the leading dimension to constant data |
multi::subarray<T, D, P = T*> special member functions
subarray::… |
|
|---|---|
(constructors) |
not exposed; copy constructor is not available since the instances are not copyable; destructors are trivial since it doesn’t own the elements |
|
assigns the elements from the source; the sizes must match |
|
swaps the elements of two subarrays O(n) cost; the sizes must match |
It is important to note that assignments in this library are always "deep," and reference-like types cannot be rebound after construction.
(Reference-like types have corresponding pointer-like types that provide an extra level of indirection and can be rebound (just like language pointers);
these types are multi::array_ptr and multi::subarray_ptr corresponding to multi::array_ref and multi::subarray respectively.)
multi::subarray<T, D, P = T*> relational functions
|
Tells if elements of two |
|
Less-than/less-or-equal lexicographical comparison (requires elements to be comparable) |
|
Greater-than/greater-or-equal lexicographical comparison (requires elements to be comparable) |
It is important to note that, in this library, comparisons are also always "deep".
Lexicographical order is defined recursively, starting from the first dimension index and from left to right.
For example, A < B if A[0] < B[0], or A[0] == B[0] and A[1] < B[1], or …, etc.
Lexicographical order applies naturally if the extensions of A and B are different; however, their dimensionalities must match.
(See sort examples).
multi::subarray<T, D, P = T*> shape access
|
returns a tuple with the sizes in each dimension |
|
returns a tuple with the extensions in each dimension |
|
returns the number of subarrays contained in the first dimension |
|
returns a contiguous index range describing the set of valid indices |
|
returns the total number of elements |
multi::subarray<T, D, P = T*> element access
|
access specified element by index (single argument), returns a |
|
access first element (undefined result if array is empty). Takes no argument. |
|
access last element (undefined result if array is empty). Takes no argument. |
|
When used with zero arguments, it returns a subarray reference representing the whole array. |
|
When used with one argument, access a specified element by index (return a |
-
subarray::operator()(i, j, k, …), as inS(i, j, k)for indicesi,j,kis a synonym forA[i][j][k], the number of indices can be lower than the total dimension (e.g.,Scan be 4D). Each index argument lowers the dimension by one. -
subarray::operator()(ii, jj, kk), the arguments can be indices or ranges of indices (index_rangemember type). This function allows positional-aware ranges. Each index argument lowers the rank by one. A special range is given bymulti::_, which means "the whole range" (also spelledmulti::all). For example, ifSis a 3D of sizes 10-by-10-by-10,S(3, {2, 8}, {3, 5})gives a reference to a 2D array where the first index is fixed at 3, with sizes 6-by-2 referring the subblock in the second and third dimension. Note thatS(3, {2, 8}, {3, 5})(6-by-2) is not equivalent toS[3]({2, 8})({3, 5})(2-by-10). -
operator()()(no arguments) gives the same array but always as a subarray type (for consistency),S()is equivalent toS(S.extension())and, in turn toS(multi::_)orS(multi::all).
multi::subarray<T, D, P = T*> structure access
These member functions are generally used for accessing details of the internal data structure (layout) interfacing with C-libraries.
subarray::… |
|
|---|---|
|
returns a single layout object with stride and size information |
|
direct access to underlying memory pointer ( |
|
return the stride value of the leading dimension, e.g |
|
returns a tuple with the strides defining the internal layout |
multi::subarray<T, D, P = T*> iterators
subarray::… |
|
|---|---|
|
returns (const) iterator to the beginning |
|
returns (const) iterator to the end |
multi::subarray<T, D, P = T*> subarray/array generators
These operations generate different ways to view the elements of a (sub)array, but without copying elements or allocate)
subarray::… |
(these operations do not copy elements or allocate) |
|---|---|
|
returns a view of higher dimensionality ( |
|
a view of higher dimensionality resulting from partitioning the original into subarrays of a certain length |
|
(takes one integer argument |
|
creates a view of the array, where each element is transformed according to a function (first and only argument) |
|
a flatted view of all the elements rearranged canonically. |
|
a view ( |
|
a view of the original array with the first elements |
|
a view ( |
|
a view of higher dimensionality resulting by splitting the original range in a certain number of parts (complementary to |
|
(takes two index arguments |
|
(takes one integer argument |
|
produces a view where the underlying pointer constructed by |
|
with no arguments: underlying elements are reinterpreted as type T2, element sizes ( |
This function creates an independent copy of any (sub)array view:
subarray::… |
(these operations do not copy elements or allocate) |
|---|---|
|
creates a concrete independent |
A reference subarray can be invalidated when its origin array is invalidated or destroyed.
For example, if the array from which it originates is destroyed or resized.
Array references
An array reference, or D-dimensional view of the contiguous pre-existing memory buffer are represented by object of type multi::array_ref<T, D, P = T*>.
This class doesn’t manage the elements it contains, and it has reference semantics (it can’t be rebound, assignments are deep, and have the same size restrictions as subarray)
Since array_ref is-a subarray, it inherits all the class methods and types described before and, in addition, it defines these members below.
Member types |
same as for |
| Member functions | same as for subarray plus … |
|---|---|
(constructors) |
|
Element access |
same as for |
Structure access |
same as for |
Iterators |
same as for |
Capacity |
same as for |
Creating views |
same as for |
Creating arrays |
same as for |
Relational functions |
same as for |
An array_ref can be invalidated if the original buffer is deallocated.
Dynamic arrays
A dynamic array is a D-dimensional array that manages an internal memory buffer, and it is represented by multi::dynamic_array<T, D, Alloc = std::allocator<T>>.
This class owns the elements it contains; it has restricted value semantics because assignments are restricted to sources with equal sizes.
Memory is requested by an allocator of type Alloc (standard allocator by default).
It supports stateful and polymorphic allocators, which are the default for the special type multi::pmr::dynamic_array.
For most uses, a multi::array should be preferred instead.
The main feature of this class is that its iterators, subarrays, and pointers do not get invalidated unless the whole object is destroyed.
In this sense, it is semantically similar to a C-array, except that elements are allocated from the heap.
It can be useful for scoped uses of arrays and multithreaded programming and to ensure that assignments do not incur allocations.
The C++ core guidelines proposed a similar (albeit one-dimensional) class, called linkL:http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#gslowner-ownership-pointers[gsl::dyn_array].
Member types |
same as for |
| Member functions | same as for array_ref plus … |
|---|---|
(constructors) |
|
(destructor) |
Destructor deallocates memory and destroy the elements |
|
assigns the elements from the source, sizes must match. |
Element access |
same as for |
Structure access |
same as for |
Iterators |
same as for |
Capacity |
same as for |
Creating views |
same as for |
Creating arrays |
same as for |
Relational functions |
same as for |
Arrays
An array of integer positive dimension D has value semantics if its element type T has value semantics, and it is represented by multi::array<T, D, Alloc = std::allocator<T>>.
It supports stateful and polymorphic allocators, which is implied for the special type multi::pmr::array<T, D>.
Member types |
same as for |
| Member functions | |
|---|---|
(constructors) |
|
(destructor) |
Destructor deallocates memory and destroy the elements |
|
assigns for a source |
|
assigns the elements from the source; the sizes don’t need to must match, in which case allocates |
|
swaps the elements from the source O(1); the sizes don’t need to match |
Element access |
same as for |
Structure access |
same as for |
Iterators |
same as for |
Capacity |
same as for |
Creating views |
same as for |
Creating arrays |
same as for |
Relational functions |
same as for |
| Manipulation | |
|---|---|
|
Erases all elements from the container. The array is resized to zero size. |
|
Changes the size of the array to new extensions. |
Iterators
The library offers random-access iterator to subarrays of dimension D - 1. and they are represented by types of the form multi::[sub]array<T, D, P>::(const_)iterator.
These are generally used to interact with or implement algorithms.
They can be default constructed but do not expose other constructors since they are generally created from begin or end, manipulated arithmetically, operator--, operator+` (pre and postfix), or random jumps `operator/operator- and operator+=/operator-=.
They can be dereferenced by operator* and index access operator[], returning objects of lower dimension subarray<T, D, … >::reference (see above).
Note that this is the same type for all related arrays, for example, multi::array<T, D, P >::(const_)iterator.
iterator can be invalidated when its original array is invalidated, destroyed or resized.
An iterator that stems from dynamic_array becomes invalid only if the original array was destroyed (e.g. out-of-scope).
Type Requirements
The library design tries to impose the minimum possible requirements over the types that parameterize the arrays. Array operations assume that the contained type (element type) are regular (i.e. different element represent disjoint entities that behave like values). Pointer-like random access types can be used as substitutes of built-in pointers. (Therefore, pointers to special memory and fancy-pointers are supported.)
Linear Sequences: Pointers
An array_ref can reference an arbitrary random access linear sequence (e.g. memory block defined by pointer and size).
This way, any linear sequence (e.g. raw memory, std::vector, std::queue) can be efficiently arranged as a multidimensional array.
std::vector<double> buffer(100);
multi::array_ref<double, 2> A({10, 10}, buffer.data());
A[1][1] = 9.0;
assert( buffer[11] == 9.0 ); // the target memory is affected
Since array_ref does not manage the memory associated with it, the reference can simply dangle if the buffer memory is reallocated (e.g. by vector-resize in this case).
Special Memory: Pointers and Views
array 's manage their memory behind the scenes through allocators, which can be specified at construction.
It can handle special memory, as long as the underlying types behave coherently, these include fancy pointers (and fancy references).
Associated fancy pointers and fancy reference (if any) are deduced from the allocator types.
Allocators and Fancy Pointers
Specific uses of fancy memory are file-mapped memory or interprocess shared memory.
This example illustrates memory persistency by combining with Boost.Interprocess library.
The arrays support their allocators and fancy pointers (boost::interprocess::offset_ptr).
#include <boost/interprocess/managed_mapped_file.hpp>
using namespace boost::interprocess;
using manager = managed_mapped_file;
template<class T> using mallocator = allocator<T, manager::segment_manager>;
decltype(auto) get_allocator(manager& m) {return m.get_segment_manager();}
template<class T, auto D> using marray = multi::array<T, D, mallocator<T>>;
int main() {
{
manager m{create_only, "mapped_file.bin", 1 << 25};
auto&& arr2d = *m.construct<marray<double, 2>>("arr2d")(marray<double, 2>::extensions_type{1000, 1000}, 0.0, get_allocator(m));
arr2d[4][5] = 45.001;
}
// imagine execution restarts here, the file "mapped_file.bin" persists
{
manager m{open_only, "mapped_file.bin"};
auto&& arr2d = *m.find<marray<double, 2>>("arr2d").first;
assert( arr2d[7][8] == 0. );
assert( arr2d[4][5] == 45.001 );
m.destroy<marray<double, 2>>("arr2d");
}
}
(See also, examples of interactions with the CUDA Thrust library to see more uses of special pointer types to handle special memory.)
Transformed views
Another kind of use of the internal pointer-like type is to transform underlying values.
These are useful to create "projections" or "views" of data elements.
In the following example a "transforming pointer" is used to create a conjugated view of the elements.
In combination with a transposed view, it can create a hermitian (transposed-conjugate) view of the matrix (without copying elements).
We can adapt the library type boost::transform_iterator to save coding, but other libraries can be used also.
The hermitized view is read-only, but with additional work, a read-write view can be created (see multi::::hermitized in multi-adaptors).
constexpr auto conj = [](auto const& c) {return std::conj(c);};
template<class T> struct conjr : boost::transform_iterator<decltype(conj), T*> {
template<class... As> conjr(As const&... as) : boost::transform_iterator<decltype(conj), T*>{as...} {}
};
template<class Array2D, class Complex = typename Array2D::element_type>
auto hermitized(Array2D const& arr) {
return arr
.transposed() // lazily tranposes the array
.template static_array_cast<Complex, conjr<Complex>>(conj) // lazy conjugate elements
;
}
int main() {
using namespace std::complex_literals;
multi::array A = {
{ 1.0 + 2.0i, 3.0 + 4.0i},
{ 8.0 + 9.0i, 10.0 + 11.0i}
};
auto const& Ah = hermitized(A);
assert( Ah[1][0] == std::conj(A[0][1]) );
}
To simplify this boilerplate, the library provides the .element_transformed(F) method that will apply a transformation F to each element of the array.
In this example, the original array is transformed into a transposed array with duplicated elements.
multi::array<double, 2> A = {
{1.0, 2.0},
{3.0, 4.0},
};
auto const scale = [](auto x) { return x * 2.0; };
auto B = + A.transposed().element_transformed(scale);
assert( B[1][0] == A[0][1] * 2 );
Since element_transformed is a reference-like object (transformed view) to the original data, it is important to understand the semantics of evaluation and possible allocations incurred.
As mentioned in other sections using auto and/or + appropriately can lead to simple and efficient expressions.
| Construction | Allocation of `T`s | Initialization (of `T`s) | Evaluation (of fun) |
Notes |
|---|---|---|---|---|
|
Yes |
No |
Yes |
Implicit conversion to |
|
Yes (and move, or might allocate twice if types don’t match) |
No |
Yes |
Not recommended |
|
Yes |
No |
Yes |
Explicit conversion to |
|
Yes |
No |
Yes |
Types and dimension are deduced, result is contiguous, preferred |
|
No |
No |
No (delayed) |
Result is effective a reference, may dangle with |
|
No |
No |
No (delayed) |
Result is effective a reference, may dangle with |
|
Yes |
Yes (during construction) |
Yes |
"Two-step" construction. |
| Assignment | Allocation of `T`s | Initialization (of `T`s) | Evaluation (of fun) |
Notes |
|---|---|---|---|---|
|
No, if sizes match |
Possibly (when |
Yes |
|
|
Yes |
Possibly (when |
Yes |
Not recommended. |