11#include <initializer_list>
45 static std::atomic<std::uint64_t> counter{0};
55template <
class T, std::
size_t Align>
class AlignedAllocator :
public std::allocator<T> {
58 using size_type = std::size_t;
60 using const_pointer =
const T *;
64 using is_always_equal = std::true_type;
65 using propagate_on_container_move_assignment = std::true_type;
66 using propagate_on_container_copy_assignment = std::true_type;
67 using propagate_on_container_swap = std::true_type;
69 template <
typename U>
struct rebind {
70 using other = AlignedAllocator<U, Align>;
73 AlignedAllocator() noexcept = default;
75 template <typename U> AlignedAllocator(const AlignedAllocator<U, Align> & ) noexcept {}
77 pointer allocate(size_type n) {
82 const size_type bytes = n *
sizeof(T);
83 const size_type aligned_bytes = (bytes + Align - 1) / Align * Align;
85 if (
auto *ptr =
static_cast<pointer
>(std::aligned_alloc(Align, aligned_bytes))) {
88 throw std::bad_alloc();
91 void deallocate(pointer ptr, size_type )
noexcept { std::free(ptr); }
94template <
class T,
class U, std::
size_t Align>
95bool operator==(
const AlignedAllocator<T, Align> &,
const AlignedAllocator<U, Align> &)
noexcept {
99template <
class T,
class U, std::
size_t Align>
100bool operator!=(
const AlignedAllocator<T, Align> &,
const AlignedAllocator<U, Align> &)
noexcept {
136template <
class T, std::
size_t N, Layout L = Layout::Contig>
class NDArray {
137 static_assert(N >= 1,
"NDArray rank must be >= 1");
144 static_assert(std::is_arithmetic_v<T> && !std::is_same_v<T, bool>,
145 "NDArray element type must be arithmetic and not bool");
149 template <
class U, std::
size_t M, Layout LL>
friend class NDArray;
266 assert(this->
m_ndarray->m_mutable &&
"write to read-only borrow");
280 template <Layout L2 = L>
282 NDArray(std::initializer_list<std::size_t> dims)
285 assert(dims.size() == N);
287 std::size_t size = 1;
288 for (
auto d : dims) {
292 m_strides = computeContiguousStrides(m_shape);
294 m_data = m_vec.data();
307 template <Layout L2 = L>
309 explicit NDArray(std::array<std::size_t, N> shape)
310 : m_data(
nullptr), m_base(
nullptr), m_shape(shape), m_offset(0),
312 std::size_t size = 1;
313 for (std::size_t k = 0; k < N; ++k) {
316 m_strides = computeContiguousStrides(m_shape);
318 m_data = m_vec.data();
327 : m_vec(other.m_vec), m_shape(other.m_shape), m_strides(other.m_strides),
328 m_offset(other.m_offset), m_storage(other.m_storage), m_mutable(other.m_mutable) {
335 : m_vec(std::move(other.m_vec)), m_shape(other.m_shape), m_strides(other.m_strides),
336 m_offset(other.m_offset), m_storage(other.m_storage), m_mutable(other.m_mutable) {
339 other.m_data =
nullptr;
340 other.m_base =
nullptr;
345 if (
this == &other) {
349 m_shape = other.m_shape;
350 m_strides = other.m_strides;
351 m_offset = other.m_offset;
352 m_storage = other.m_storage;
353 m_mutable = other.m_mutable;
361 if (
this == &other) {
364 m_vec = std::move(other.m_vec);
365 m_shape = other.m_shape;
366 m_strides = other.m_strides;
367 m_offset = other.m_offset;
368 m_storage = other.m_storage;
369 m_mutable = other.m_mutable;
372 other.m_data =
nullptr;
373 other.m_base =
nullptr;
378 NDArray(clustering::detail::BorrowedTag, T *
data, T *base, std::array<std::size_t, N> shape,
379 std::array<std::ptrdiff_t, N> strides, std::ptrdiff_t offset,
bool isMutable) noexcept
380 : m_data(
data), m_base(base), m_vec(), m_shape(shape), m_strides(strides), m_offset(offset),
383 assert(offset == 0 && strides == computeContiguousStrides(shape) &&
384 "Contig NDArray requires contiguous strides and zero offset");
398 template <Layout L2 = L>
401 assert(
index < m_shape[0]);
411 template <Layout L2 = L>
414 assert(
index < m_shape[0]);
426 static_assert(
sizeof...(Ix) == N,
"operator() requires exactly N indices");
427 assert(m_mutable &&
"write to read-only borrow");
428 return m_data[computeElementOffset(std::index_sequence_for<Ix...>{}, ix...)];
432 template <
class... Ix>
const T &
operator()(Ix... ix)
const noexcept {
433 static_assert(
sizeof...(Ix) == N,
"operator() requires exactly N indices");
434 return m_data[computeElementOffset(std::index_sequence_for<Ix...>{}, ix...)];
444 assert(m_mutable &&
"write to read-only borrow");
445 return m_data[
index];
479 return m_offset == 0 && m_strides == computeContiguousStrides(m_shape);
489 [[nodiscard]]
bool isMutable() const noexcept {
return m_mutable; }
504 const T *
data() const noexcept {
return m_data; }
515 assert(m_mutable &&
"write to read-only borrow");
526 [[nodiscard]] T *
baseData() const noexcept {
return m_base; }
534 template <std::
size_t A>
bool isAligned() const noexcept {
535 return (
reinterpret_cast<std::uintptr_t
>(m_data) % A) == 0;
550 assert(
isAligned<A>() &&
"alignedData<A>() requires A-byte aligned data");
551 return static_cast<T *
>(__builtin_assume_aligned(m_data, A));
556 assert(
isAligned<A>() &&
"alignedData<A>() requires A-byte aligned data");
557 return static_cast<const T *
>(__builtin_assume_aligned(m_data, A));
569 template <Layout L2 = L>
572 return NDArray(clustering::detail::BorrowedTag{}, ptr, ptr, shape,
573 computeContiguousStrides(shape), 0,
true);
582 template <Layout L2 = L>
584 static NDArray borrow(
const T *ptr, std::array<std::size_t, N> shape)
noexcept {
585 auto *mutPtr =
const_cast<T *
>(ptr);
586 return NDArray(clustering::detail::BorrowedTag{}, mutPtr, mutPtr, shape,
587 computeContiguousStrides(shape), 0,
false);
596 template <Layout L2 = L>
599 std::array<std::ptrdiff_t, N> strides)
noexcept {
600 return NDArray(clustering::detail::BorrowedTag{}, ptr, ptr, shape, strides, 0,
true);
604 template <Layout L2 = L>
607 std::array<std::ptrdiff_t, N> strides)
noexcept {
608 auto *mutPtr =
const_cast<T *
>(ptr);
609 return NDArray(clustering::detail::BorrowedTag{}, mutPtr, mutPtr, shape, strides, 0,
false);
615 template <std::
size_t M = N>
618 return borrow(ptr, std::array<std::size_t, 1>{n});
622 template <std::
size_t M = N>
625 return borrow(ptr, std::array<std::size_t, 1>{n});
641 template <Layout L2 = L>
644 std::array<std::ptrdiff_t, N> stridesInBytes,
646 std::array<std::ptrdiff_t, N> element_strides{};
647 for (std::size_t k = 0; k < N; ++k) {
648 assert(stridesInBytes[k] %
static_cast<std::ptrdiff_t
>(
sizeof(T)) == 0 &&
649 "borrowBytes requires byte strides divisible by sizeof(T)");
650 element_strides[k] = stridesInBytes[k] /
static_cast<std::ptrdiff_t
>(
sizeof(T));
652 return NDArray(clustering::detail::BorrowedTag{}, ptr, ptr, shape, element_strides, 0,
662 template <std::
size_t M = N>
665 return borrow(s.data(), std::array<std::size_t, 1>{s.size()});
669 template <std::
size_t M = N>
672 return borrow(s.data(), std::array<std::size_t, 1>{s.size()});
682 template <std::
size_t M = N>
686 clustering::detail::BorrowedTag{}, m_data, m_base,
687 std::array<std::size_t, 2>{m_shape[1], m_shape[0]},
688 std::array<std::ptrdiff_t, 2>{m_strides[1], m_strides[0]}, m_offset, m_mutable);
692 template <std::
size_t M = N>
696 clustering::detail::BorrowedTag{},
const_cast<T *
>(m_data),
const_cast<T *
>(m_base),
697 std::array<std::size_t, 2>{m_shape[1], m_shape[0]},
698 std::array<std::ptrdiff_t, 2>{m_strides[1], m_strides[0]}, m_offset,
false);
707 template <std::
size_t M = N>
710 assert(i < m_shape[0]);
711 std::array<std::size_t, N - 1> new_shape{};
712 std::array<std::ptrdiff_t, N - 1> new_strides{};
713 for (std::size_t k = 0; k + 1 < N; ++k) {
714 new_shape[k] = m_shape[k + 1];
715 new_strides[k] = m_strides[k + 1];
717 return NDArray<T, N - 1, L>(clustering::detail::BorrowedTag{},
718 m_data + m_offset + (
static_cast<std::ptrdiff_t
>(i) * m_strides[0]),
719 m_base, new_shape, new_strides, 0, m_mutable);
723 template <std::
size_t M = N>
726 assert(i < m_shape[0]);
727 std::array<std::size_t, N - 1> new_shape{};
728 std::array<std::ptrdiff_t, N - 1> new_strides{};
729 for (std::size_t k = 0; k + 1 < N; ++k) {
730 new_shape[k] = m_shape[k + 1];
731 new_strides[k] = m_strides[k + 1];
733 return NDArray<T, N - 1, L>(clustering::detail::BorrowedTag{},
734 const_cast<T *
>(m_data) + m_offset +
735 (
static_cast<std::ptrdiff_t
>(i) * m_strides[0]),
736 const_cast<T *
>(m_base), new_shape, new_strides, 0,
false);
745 template <std::
size_t M = N>
748 assert(j < m_shape[1]);
750 clustering::detail::BorrowedTag{},
751 m_data + m_offset + (
static_cast<std::ptrdiff_t
>(j) * m_strides[1]), m_base,
752 std::array<std::size_t, 1>{m_shape[0]}, std::array<std::ptrdiff_t, 1>{m_strides[0]}, 0,
757 template <std::
size_t M = N>
760 assert(j < m_shape[1]);
762 clustering::detail::BorrowedTag{},
763 const_cast<T *
>(m_data) + m_offset + (
static_cast<std::ptrdiff_t
>(j) * m_strides[1]),
764 const_cast<T *
>(m_base), std::array<std::size_t, 1>{m_shape[0]},
765 std::array<std::ptrdiff_t, 1>{m_strides[0]}, 0,
false);
775 std::size_t end)
noexcept {
776 assert(axis < N && begin <= end && end <= m_shape[axis]);
777 std::array<std::size_t, N> new_shape = m_shape;
778 new_shape[axis] = end - begin;
780 clustering::detail::BorrowedTag{},
781 m_data + m_offset + (
static_cast<std::ptrdiff_t
>(begin) * m_strides[axis]), m_base,
782 new_shape, m_strides, 0, m_mutable);
787 std::size_t end)
const noexcept {
788 assert(axis < N && begin <= end && end <= m_shape[axis]);
789 std::array<std::size_t, N> new_shape = m_shape;
790 new_shape[axis] = end - begin;
792 clustering::detail::BorrowedTag{},
793 const_cast<T *
>(m_data) + m_offset + (
static_cast<std::ptrdiff_t
>(begin) * m_strides[axis]),
794 const_cast<T *
>(m_base), new_shape, m_strides, 0,
false);
804 std::array<std::size_t, N> new_shape{};
805 std::array<std::ptrdiff_t, N> new_strides{};
806 std::ptrdiff_t advance = 0;
807 for (std::size_t k = 0; k < N; ++k) {
808 const std::size_t end = std::min(ranges[k].end, m_shape[k]);
809 const std::size_t begin = ranges[k].begin;
810 const std::ptrdiff_t step = ranges[k].step;
811 assert(begin <= end && step > 0);
812 new_shape[k] = step == 1 ? (end - begin)
813 : (end - begin +
static_cast<std::size_t
>(step) - 1) /
814 static_cast<std::size_t
>(step);
815 new_strides[k] = m_strides[k] * step;
816 advance +=
static_cast<std::ptrdiff_t
>(begin) * m_strides[k];
819 m_data + m_offset + advance, m_base, new_shape,
820 new_strides, 0, m_mutable);
825 std::array<std::size_t, N> new_shape{};
826 std::array<std::ptrdiff_t, N> new_strides{};
827 std::ptrdiff_t advance = 0;
828 for (std::size_t k = 0; k < N; ++k) {
829 const std::size_t end = std::min(ranges[k].end, m_shape[k]);
830 const std::size_t begin = ranges[k].begin;
831 const std::ptrdiff_t step = ranges[k].step;
832 assert(begin <= end && step > 0);
833 new_shape[k] = step == 1 ? (end - begin)
834 : (end - begin +
static_cast<std::size_t
>(step) - 1) /
835 static_cast<std::size_t
>(step);
836 new_strides[k] = m_strides[k] * step;
837 advance +=
static_cast<std::ptrdiff_t
>(begin) * m_strides[k];
840 clustering::detail::BorrowedTag{},
const_cast<T *
>(m_data) + m_offset + advance,
841 const_cast<T *
>(m_base), new_shape, new_strides, 0,
false);
850 std::array<std::size_t, N> new_shape{};
851 std::array<std::ptrdiff_t, N> new_strides{};
852 for (std::size_t k = 0; k < N; ++k) {
854 new_shape[k] = m_shape[perm[k]];
855 new_strides[k] = m_strides[perm[k]];
858 new_shape, new_strides, m_offset, m_mutable);
863 permute(
const std::array<std::size_t, N> &perm)
const noexcept {
864 std::array<std::size_t, N> new_shape{};
865 std::array<std::ptrdiff_t, N> new_strides{};
866 for (std::size_t k = 0; k < N; ++k) {
868 new_shape[k] = m_shape[perm[k]];
869 new_strides[k] = m_strides[perm[k]];
872 const_cast<T *
>(m_data),
const_cast<T *
>(m_base),
873 new_shape, new_strides, m_offset,
false);
886 template <std::
size_t M>
888 assert(
isContiguous() &&
"view<M> requires a contiguous source");
889 assert(productOfShape(shape) == numel() &&
"view<M> must preserve element count");
891 clustering::detail::BorrowedTag{}, m_data, m_base, shape,
892 NDArray<T, M, Layout::Contig>::computeContiguousStrides(shape), 0, m_mutable);
896 template <std::
size_t M>
898 assert(
isContiguous() &&
"view<M> requires a contiguous source");
899 assert(productOfShape(shape) == numel() &&
"view<M> must preserve element count");
901 clustering::detail::BorrowedTag{},
const_cast<T *
>(m_data),
const_cast<T *
>(m_base), shape,
902 NDArray<T, M, Layout::Contig>::computeContiguousStrides(shape), 0,
false);
916 assert(productOfShape(shape) == numel() &&
"reshape<M> must preserve element count");
919 clustering::detail::BorrowedTag{}, m_data, m_base, shape,
920 NDArray<T, M, Layout::Contig>::computeContiguousStrides(shape), 0, m_mutable);
923 copyToContiguous(result.
data());
928 template <std::
size_t M>
930 assert(productOfShape(shape) == numel() &&
"reshape<M> must preserve element count");
933 clustering::detail::BorrowedTag{},
const_cast<T *
>(m_data),
const_cast<T *
>(m_base),
934 shape, NDArray<T, M, Layout::Contig>::computeContiguousStrides(shape), 0,
false);
937 copyToContiguous(result.
data());
951 clustering::detail::BorrowedTag{}, m_data, m_base, m_shape,
952 NDArray<T, N, Layout::Contig>::computeContiguousStrides(m_shape), 0, m_mutable);
955 copyToContiguous(result.
data());
963 clustering::detail::BorrowedTag{},
const_cast<T *
>(m_data),
const_cast<T *
>(m_base),
964 m_shape, NDArray<T, N, Layout::Contig>::computeContiguousStrides(m_shape), 0,
false);
967 copyToContiguous(result.
data());
980 copyToContiguous(result.
data());
995 std::stringstream ss;
996 ss <<
"NDarray<" <<
typeid(T).name() <<
", " << N <<
">(";
997 for (
auto d : m_shape) {
1002 const std::size_t total = numel();
1004 std::array<std::size_t, N> idx{};
1005 for (std::size_t flat = 0; flat < total; ++flat) {
1006 std::ptrdiff_t off = m_offset;
1007 for (std::size_t k = 0; k < N; ++k) {
1008 off +=
static_cast<std::ptrdiff_t
>(idx[k]) * m_strides[k];
1010 ss << m_data[off] <<
", ";
1011 for (std::size_t k = N; k-- > 0;) {
1012 if (++idx[k] < m_shape[k]) {
1020 ss <<
"size: " << total <<
"\n";
1032 static std::array<std::ptrdiff_t, N>
1033 computeContiguousStrides(
const std::array<std::size_t, N> &shape) {
1034 std::array<std::ptrdiff_t, N> s{};
1036 for (std::size_t k = N - 1; k > 0; --k) {
1037 s[k - 1] = s[k] *
static_cast<std::ptrdiff_t
>(shape[k]);
1042 template <std::
size_t M>
1043 static std::size_t productOfShape(
const std::array<std::size_t, M> &shape)
noexcept {
1044 std::size_t size = 1;
1045 for (std::size_t k = 0; k < M; ++k) {
1051 std::size_t numel() const noexcept {
return productOfShape(m_shape); }
1056 void copyToContiguous(T *dst)
const noexcept {
1057 const std::size_t total = numel();
1062 std::memcpy(dst, m_data + m_offset, total *
sizeof(T));
1065 std::array<std::size_t, N> idx{};
1066 for (std::size_t flat = 0; flat < total; ++flat) {
1067 std::ptrdiff_t off = m_offset;
1068 for (std::size_t k = 0; k < N; ++k) {
1069 off +=
static_cast<std::ptrdiff_t
>(idx[k]) * m_strides[k];
1071 dst[flat] = m_data[off];
1072 for (std::size_t k = N; k-- > 0;) {
1073 if (++idx[k] < m_shape[k]) {
1081 template <std::size_t... Ks,
class... Ix>
1082 std::size_t computeElementOffset(std::index_sequence<Ks...>, Ix... ix)
const noexcept {
1083 std::ptrdiff_t off = m_offset;
1084 ((off +=
static_cast<std::ptrdiff_t
>(ix) * m_strides[Ks]), ...);
1085 return static_cast<std::size_t
>(off);
1090 std::vector<T, clustering::detail::AlignedAllocator<T, 64>> m_vec;
1091 std::array<std::size_t, N> m_shape;
1092 std::array<std::ptrdiff_t, N> m_strides;
1093 std::ptrdiff_t m_offset;
1106template <
class T, std::
size_t NA, Layout LA, std::
size_t NB, Layout LB>
1108 return a.baseData() == b.baseData();
Provides read-write access to NDArray elements.
Accessor(NDArray< T, N, Layout::Contig > &ndarray, std::size_t index, std::size_t dim)
Constructs an Accessor for an NDArray.
Accessor & operator=(T value) noexcept
Assigns a value to the element at the accessor's position.
Accessor operator[](std::size_t index) noexcept
Provides access to the next dimension of the NDArray.
std::size_t m_index
Index in the flat representation of the array.
NDArray< T, N, Layout::Contig > * m_ndarray
Pointer to the NDArray.
std::size_t m_dim
Current dimension of the accessor.
BaseAccessor(NDArray< T, N, Layout::Contig > *ndarray, std::size_t index, std::size_t dim)
Constructs a BaseAccessor for a given NDArray, index, and dimension.
const T * data() const
Returns a pointer to the element data.
Provides read-only access to NDArray elements.
ConstAccessor(const NDArray< T, N, Layout::Contig > &ndarray, std::size_t index, std::size_t dim)
Constructs a ConstAccessor for a constant NDArray.
ConstAccessor operator[](std::size_t index) const noexcept
Provides access to the next dimension of the NDArray.
ConstAccessor(const ConstAccessor &other)=default
Defaulted copy constructor; accessors are lightweight and trivially copyable.
size_t index() const noexcept
Returns the flat index in the NDArray corresponding to the accessor.
Represents a multidimensional array (NDArray) of a fixed number of dimensions N and element type T.
T & flatIndex(std::size_t index) noexcept
Provides direct access to the flat underlying array at a specific index.
NDArray< T, N, Layout::Contig > clone() const
Returns a freshly-allocated owned contiguous array with deep-copied contents.
const T & flatIndex(std::size_t index) const noexcept
Provides read-only access to the flat underlying array at a specific index.
const T * alignedData() const noexcept
Read-only overload of alignedData<A>; attaches the same alignment hint to the pointer.
NDArray< T, N, Layout::MaybeStrided > slice(const std::array< Range, N > &ranges) noexcept
Borrowed multi-axis slice; each Range applies to its corresponding axis.
NDArray< T, N - 1, L > row(std::size_t i) const noexcept
Read-only row view; mirrors the mutable overload and flips m_mutable off.
NDArray(std::initializer_list< std::size_t > dims)
Constructs a contiguous owned NDArray with specified dimensions.
static NDArray borrow(const T *ptr, std::array< std::size_t, N > shape) noexcept
Borrows a read-only contiguous buffer as an NDArray.
NDArray< T, M, Layout::Contig > view(std::array< std::size_t, M > shape) noexcept
Returns a borrowed contiguous rank-M view over the same buffer with shape shape.
bool isContiguous() const noexcept
Reports whether the array's runtime layout is row-major contiguous with zero offset.
T * data() noexcept
Provides read-write access to the internal data array.
size_t dim(std::size_t index) const noexcept
Returns the size of a specific dimension of the NDArray.
NDArray< T, 2, Layout::MaybeStrided > t() noexcept
Transposes a rank-2 NDArray into a borrowed view with swapped axes.
NDArray< T, 1, Layout::MaybeStrided > col(std::size_t j) noexcept
Returns a borrowed rank-1 view of column j of a rank-2 array.
std::ptrdiff_t strideAt(std::size_t index) const noexcept
Returns the stride (in elements) for dimension index.
static NDArray fromSpan(std::span< T > s) noexcept
Explicit std::span adapter for rank-1 borrows.
T & operator()(Ix... ix) noexcept
Direct multi-index element access via strides.
NDArray< T, 1, Layout::MaybeStrided > col(std::size_t j) const noexcept
Read-only column view; mirrors the mutable overload and flips m_mutable off.
static NDArray borrowBytes(T *ptr, std::array< std::size_t, N > shape, std::array< std::ptrdiff_t, N > stridesInBytes, bool isMutable) noexcept
Borrow a buffer whose strides are expressed in bytes (NumPy's convention).
NDArray< T, M, Layout::Contig > reshape(std::array< std::size_t, M > shape)
Returns a contiguous rank-M array with shape shape, copying only when needed.
bool isOwned() const noexcept
Reports whether the array owns its underlying buffer.
static NDArray borrow1D(const T *ptr, std::size_t n) noexcept
Read-only rank-1 convenience borrow; mirrors the mutable borrow1D.
NDArray< T, N, Layout::MaybeStrided > slice(std::size_t axis, std::size_t begin, std::size_t end) noexcept
Borrowed half-open slice along a single axis.
NDArray< T, 2, Layout::MaybeStrided > t() const noexcept
Read-only transpose; the returned view carries m_mutable = false.
NDArray< T, N, Layout::MaybeStrided > slice(const std::array< Range, N > &ranges) const noexcept
Read-only multi-axis slice; mirrors the mutable overload with m_mutable = false.
friend bool operator==(const NDArray &, const NDArray &)=delete
NDArray< T, N - 1, L > row(std::size_t i) noexcept
Returns a borrowed view of row i with the leading dimension dropped.
friend bool operator!=(const NDArray &, const NDArray &)=delete
NDArray(NDArray &&other) noexcept
Move constructor; steals m_vec and re-seats m_data for owned storage.
NDArray(std::array< std::size_t, N > shape)
Constructs a contiguous owned NDArray from a runtime std::array of dimensions.
static NDArray borrow1D(T *ptr, std::size_t n) noexcept
Rank-1 convenience borrow; avoids the std::array<size_t, 1>{n} boilerplate.
T * baseData() const noexcept
Returns the original (non-advanced) base pointer for storage-identity comparisons.
static NDArray fromSpan(std::span< const T > s) noexcept
Read-only span adapter; delegates to the read-only borrow overload.
static NDArray borrow(const T *ptr, std::array< std::size_t, N > shape, std::array< std::ptrdiff_t, N > strides) noexcept
Read-only strided borrow; flips m_mutable off so writes through the view assert.
NDArray< T, N, Layout::MaybeStrided > slice(std::size_t axis, std::size_t begin, std::size_t end) const noexcept
Read-only single-axis slice; mirrors the mutable overload with m_mutable = false.
static NDArray borrow(T *ptr, std::array< std::size_t, N > shape) noexcept
Borrows a contiguous buffer as an NDArray without taking ownership.
bool isAligned() const noexcept
Tests whether data() is aligned to A bytes.
NDArray & operator=(NDArray &&other) noexcept
Move assignment; steals m_vec and re-seats m_data for owned storage.
NDArray(const NDArray &other)
Copy constructor; re-seats m_data against m_vec for owned storage.
NDArray< T, M, Layout::Contig > reshape(std::array< std::size_t, M > shape) const
Read-only rank-M reshape; aliases on contiguous sources, copies otherwise.
std::string debugDump() const
Returns a formatted string representing the contents of the NDArray.
const T & operator()(Ix... ix) const noexcept
Read-only multi-index element access via strides; mirrors the mutable overload.
NDArray & operator=(const NDArray &other)
Copy assignment; re-seats m_data against m_vec for owned storage.
NDArray< T, M, Layout::Contig > view(std::array< std::size_t, M > shape) const noexcept
Read-only rank-M view; mirrors the mutable overload with m_mutable = false.
NDArray< T, N, Layout::MaybeStrided > permute(const std::array< std::size_t, N > &perm) const noexcept
Read-only permuted view; mirrors the mutable overload with m_mutable = false.
static NDArray borrow(T *ptr, std::array< std::size_t, N > shape, std::array< std::ptrdiff_t, N > strides) noexcept
Borrows a strided buffer as an NDArray without taking ownership.
NDArray< T, N, Layout::Contig > contiguous()
Returns a contiguous rank-N array with the same shape, copying only when needed.
NDArray< T, N, Layout::Contig > contiguous() const
Read-only contiguous view; aliases on contiguous sources, copies otherwise.
NDArray< T, N, Layout::MaybeStrided > permute(const std::array< std::size_t, N > &perm) noexcept
Borrowed view with axes reordered by perm.
const T * data() const noexcept
Provides read-only access to the internal data array.
T * alignedData() noexcept
Returns data() with an alignment hint of A bytes applied.
bool isMutable() const noexcept
Reports whether writes through operator(), Accessor, or flatIndex are allowed.
bool operator!=(const AlignedAllocator< T, Align > &, const AlignedAllocator< U, Align > &) noexcept
std::atomic< std::uint64_t > & alignedAllocCallCount() noexcept
Process-global counter of non-empty AlignedAllocator::allocate calls.
bool operator==(const AlignedAllocator< T, Align > &, const AlignedAllocator< U, Align > &) noexcept
bool sameStorage(const NDArray< T, NA, LA > &a, const NDArray< T, NB, LB > &b) noexcept
Returns true when a and b share the same underlying allocation.
NDArrayStorage
Tag indicating whether an NDArray owns its buffer or borrows memory from elsewhere.
Layout
Compile-time layout tag for NDArray.