C++ Array Overview

Dynamic array handling in C++ traditionally relies on std::vector, which provides convenient automatic memory management but comes with limitations in specialized environments. The standard library implementation uses dynamic allocation with opaque memory policies, making it unsuitable for embedded systems, real-time applications, or projects requiring deterministic memory behavior and MISRA C++ compliance.

The C++ Array module in this library provides a lightweight, allocator-aware generic array container implemented in C++ and declared in array.hpp. Unlike std::vector, this container:

  • Integrates directly with custom allocator hierarchies (heap, arena, buddy, slab)

  • Explicitly tracks element count, allocated capacity, and the owning allocator

  • Enforces initialization through a factory pattern preventing uninitialized arrays

  • Provides automatic cleanup via RAII with custom deleters

  • Returns explicit success/error state using the Expected<T> pattern

  • Supports in-place sorting, reversal, slicing, and cumulative transformations

The only dependencies this module has within the CSalt library are the allocator.hpp, error.hpp, iter_dir.hpp, and pointers.hpp files, making it suitable for integration into larger systems while maintaining minimal coupling.

Allocator Integration

By integrating directly with the Allocator base class defined in allocator.hpp, the array container supports multiple allocation strategies without changing the public API:

  • HeapAllocator — Standard heap allocation via operator new

  • ArenaAllocator — Sequential bump allocation with bulk deallocation

  • BuddyAllocator — Power-of-two block allocation with coalescing

  • SlabAllocator — Fixed-size object pools for uniform allocations

  • Custom allocators — Any user-defined class inheriting from Allocator

This design enables deterministic memory behavior suitable for embedded, real-time, or safety-regulated environments where allocation patterns must be controlled and predictable. The user can also develop their own allocators as long as they conform to the Allocator base class.

Factory Pattern and RAII

Unlike traditional constructors, array creation uses a static factory function Array::init() that returns Expected<Array<T>*>. This design:

  • Prevents creation of uninitialized or invalid arrays

  • Provides explicit error handling at the point of creation

  • Enables proper two-phase initialization (object allocation + buffer allocation)

  • Returns typed error objects (ArgumentError, MemoryError) with descriptive messages

Memory cleanup is automatic through the ArrayDeleter custom deleter, which works seamlessly with UniquePtr to provide RAII semantics:

// Automatic cleanup when UniquePtr goes out of scope
{
    cslt::HeapAllocator allocator;
    auto result = cslt::Array<int>::init(8, allocator);
    if (result.hasValue()) {
        cslt::UniquePtr<cslt::Array<int>, cslt::ArrayDeleter<int>> arr(result.value());
        arr->push_back(1);
        arr->push_back(2);
        arr->push_back(3);
    } // Array and buffer automatically freed here
}

Growth Strategy

The array grows automatically when a push operation requires more capacity. Growth is tiered to avoid runaway allocation at large sizes while maintaining fast ramp-up at small sizes:

Current capacity

New capacity

0 elements

1

< 1,024 elements

2× current

< 8,192 elements

1.5× current (cap + cap/2)

< 65,536 elements

1.25× current (cap + cap/4)

≥ 65,536 elements

current + 256 (linear increment)

A single reallocation is issued per growth event, and the array’s size and capacity are always kept consistent through the public API.

Element Type Requirements

The Array<T> container supports any element type T, but the internal implementation selects between two code paths at compile time based on std::is_trivially_copyable_v<T>:

  • Trivially copyable T (int, double, plain structs with no user-defined constructors or destructors) — shift and copy operations use std::memmove / std::memcpy, which the compiler can vectorise.

  • Non-trivial T — shift and copy operations use placement-new and explicit destructor calls to honour the full object lifecycle.

This distinction is invisible to the caller — the public API is identical for both cases.

Sorting

The sort() method provides an in-place iterative quicksort with the following properties:

  • Median-of-three pivot selection to avoid worst-case behaviour on sorted input

  • Insertion sort fallback for partitions smaller than 10 elements

  • Tail-call optimisation keeping worst-case stack depth at O(log n)

  • Direction controlled by cslt::Direction::FORWARD (ascending) or cslt::Direction::REVERSE (descending)

  • Comparator accepts any callable with signature int(const T&, const T&)

arr->sort([](const int& a, const int& b) { return (a > b) - (a < b); },
          cslt::Direction::FORWARD);

Reversal

The reverse() method reverses the array in place. Three compile-time paths are selected based on element type and size:

  • Single-byte trivially copyable T — delegates to simd_reverse_uint8(), which selects the best available SIMD back-end at compile time (AVX-512, AVX2, AVX, SSE4.1, SSSE3, SSE2, NEON, SVE, SVE2) or a scalar fallback.

  • Multi-byte trivially copyable T — scalar memcpy-swap loop, which the compiler will typically auto-vectorise for common element sizes.

  • Non-trivial T — move-swap loop that respects construction and destruction semantics throughout.

Array Class

template<typename T>
class Array

Allocator-backed generic dynamic array container.

Provides a resizable array container that uses custom allocators for memory management. Both the Array struct and its internal data buffer are allocated through the provided allocator.

Key features:

  • Custom allocator support (heap, arena, buddy, slab, etc.)

  • Generic element type via template parameter

  • Tiered growth strategy to avoid runaway allocation at large sizes

  • Safe insertion and removal via push/pop family of methods

  • Bounds-checked read via operator[](size_t) const returning Expected<T>

  • Bounds-checked write via set(size_t, const T&) returning Expected<bool>

  • Copy factory via copy(); move construction explicitly deleted

  • RAII-based memory management through init factory function

Growth tiers (in elements):

  • 0 -> 1

  • < 1024 -> 2x

  • < 8192 -> 1.5x (cap + cap/2)

  • < 65536 -> 1.25x (cap + cap/4)

  • >= 65536 -> cap + 256 (linear increment)

Shift strategy:

  • Trivially copyable T: std::memmove (compiler-vectorisable)

  • Non-trivial T: placement-new / explicit destructor loop

Usage pattern:

  • Must be initialised via the static factory function init()

  • Cannot be constructed directly (private constructor)

  • Automatically manages both the Array object and its buffer memory

  • Cleaned up through ArrayDeleter

cslt::HeapAllocator allocator;
auto result = cslt::Array<int>::init(8, allocator);

if (result.hasValue()) {
    cslt::UniquePtr<cslt::Array<int>, cslt::ArrayDeleter<int>> arr(result.value());

    arr->push_back(1);
    arr->push_back(2);
    arr->push_back(3);

    (*arr)[1] = 99;            // overwrite index 1
    auto r = (*arr)[1];        // read index 1 -> Expected<int>
    arr->pop_back();           // removes 3
}

Note

This class cannot be instantiated directly. Use the init() factory function to create instances.

Template Parameters:

T – Element type stored in the array

Public Functions

inline bool push_back(const T &value) noexcept

Append an element to the end of the array.

Grows the buffer using the tiered strategy if at capacity, then copy-constructs the new element at the end.

arr->push_back(42);
Parameters:

value – Element to copy into the array

Returns:

true on success, false if reallocation failed

inline bool push_front(const T &value) noexcept

Insert an element at the beginning of the array.

Grows the buffer if at capacity, shifts all existing elements one position to the right via memmove (trivial T) or a placement-new loop (non-trivial T), then copy-constructs the new element at index 0.

arr->push_front(42);
Parameters:

value – Element to copy into the array

Returns:

true on success, false if reallocation failed

inline bool push_any(size_t index, const T &value) noexcept

Insert an element at a caller-specified index.

Grows the buffer if at capacity, shifts all elements at [index, len_) one position to the right, then copy-constructs the new element at index.

// array contains {1, 2, 4}
arr->push_any(2, 3);  // array becomes {1, 2, 3, 4}
Parameters:
  • index – Zero-based insertion position. Must be <= size(). index == 0 is equivalent to push_front(). index == size() is equivalent to push_back().

  • value – Element to copy into the array

Returns:

true on success, false if index is out of range or reallocation failed

inline bool pop_back() noexcept

Remove the last element from the array.

Calls the destructor of the last element and decrements len_. The buffer capacity is not reduced.

arr->pop_back();
Returns:

true if an element was removed, false if the array was empty

inline bool pop_front() noexcept

Remove the element at the beginning of the array.

Destroys the element at index 0, shifts all remaining elements one position to the left, then decrements len_. The buffer capacity is not reduced.

arr->pop_front();
Returns:

true if an element was removed, false if the array was empty

inline bool pop_any(size_t index) noexcept

Remove the element at a caller-specified index.

Destroys the element at index, shifts all elements at (index, len_) one position to the left, then decrements len_. The buffer capacity is not reduced.

// array contains {1, 2, 3, 4}
arr->pop_any(2);  // array becomes {1, 2, 4}
Parameters:

index – Zero-based position of the element to remove. Must be < size(). index == 0 is equivalent to pop_front(). index == size()-1 is equivalent to pop_back().

Returns:

true on success, false if the array is empty or index is out of range

inline void clear() noexcept

Destroy all live elements and reset the populated length to zero.

Calls the destructor of every live element in reverse order, then sets len_ to 0. The allocated buffer and its capacity are preserved so the Array can be reused without a further allocation.

arr->clear();
// arr->size() == 0, arr->capacity() unchanged
inline Expected<bool> set(size_t index, const T &value) noexcept

Write or append an element at a given index.

Three cases are handled:

  • index < len_ : destroys the existing element and copy-constructs value in its place

  • index == len_ : calls _ensure_capacity(), copy-constructs value, and increments len_

  • index > len_ : returns an OutOfBoundsError

arr->set(0, 42);            // overwrite existing element
arr->set(arr->size(), 99);  // append via set
Parameters:
  • index – Zero-based position to write to. Must be <= size().

  • value – Element to copy into the array at index

Returns:

Expected<bool> — true on success; error on failure

inline Expected<T> operator[](size_t index) const noexcept

Read-only access to an element at a given index.

auto r = (*arr)[1];
if (r.hasValue()) {
    int val = r.value();
}

Parameters:

index – Zero-based position to read from. Must be < size().

Returns:

Expected<T> containing a copy of the element on success, or an error if index is out of the populated range

inline const T *data() const noexcept

Return a const pointer to the beginning of the element buffer.

The pointer is valid until the next push operation that triggers a reallocation.

const int* ptr = arr->data();
for (size_t i = 0; i < arr->size(); ++i)
    std::cout << ptr[i] << '\n';
Returns:

Const pointer to the first element, or nullptr if the array has not been successfully initialised

inline size_t size() const noexcept

Return the number of elements currently stored.

Returns:

Element count

inline size_t capacity() const noexcept

Return the current element capacity of the buffer.

Returns:

Capacity in number of elements

inline bool is_empty() const noexcept

Return true if no elements are currently stored.

if (arr->is_empty()) { ... }

Returns:

true if size() == 0

inline bool is_full() const noexcept

Return true if the populated length equals the allocated capacity.

Note that a push operation on a full array will attempt to grow the buffer automatically. is_full() is useful for callers that need to know whether the next push will trigger a reallocation.

if (arr->is_full()) { ... }
Returns:

true if size() == capacity()

inline bool is_ptr(const T *ptr) const noexcept

Return true if a pointer falls within the populated region and is aligned to an element boundary.

A pointer obtained after a reallocation (e.g. following a push that triggered a grow) may no longer be valid; callers are responsible for not retaining pointers across reallocations.

const int* p = &(*arr->data());
if (arr->is_ptr(p)) { ... }
Parameters:

ptr – Pointer to test

Returns:

true if ptr points to a valid element in [data_, data_ + len_) and satisfies (ptr - data_) % sizeof(T) == 0

inline bool concat(const Array<T> &other) noexcept

Append all elements of another Array to the end of this array.

Iterates over the populated elements of other and appends each one via push_back(), which uses the tiered growth strategy as needed. If any individual push_back() fails (reallocation error), the method returns false immediately; elements already appended before the failure remain in the array.

// a contains {1, 2, 3}, b contains {4, 5, 6}
a->concat(*b);
// a now contains {1, 2, 3, 4, 5, 6}

Note

Self-concatenation (passing this array as other) is safe because push_back() always reads from other before writing, and the snapshot of other.len_ is taken once before the loop.

Parameters:

otherArray whose elements are appended to this array

Returns:

true on success, false if any reallocation fails

inline void reverse() noexcept

Reverse the order of all elements in the array in place.

For trivially copyable T the reversal is delegated to simd_reverse_uint8(), which is resolved at compile time to the best available SIMD back-end (AVX-512, AVX2, AVX, SSE4.1, SSSE3, SSE2, NEON, SVE, SVE2) or a portable scalar fallback. The function treats each element as an opaque bag of sizeof(T) bytes and swaps element positions — it does not reverse the bytes within an individual element.

For non-trivial T, a scalar swap loop is used that respects move construction and destruction semantics.

Arrays with fewer than two elements are left unchanged.

// arr contains {1, 2, 3, 4, 5}
arr->reverse();
// arr now contains {5, 4, 3, 2, 1}
template<typename Func>
inline bool sort(Func cmp, Direction dir) noexcept

Sort the array in place using an iterative quicksort.

Implements an iterative median-of-three quicksort with an insertion sort fallback for partitions smaller than 10 elements and tail-call optimisation to keep stack depth at O(log n) in the worst case. Element swaps use memcpy for trivially copyable T and move construction / destruction for non-trivial T.

arr->sort([](const int& a, const int& b) { return a - b; },
          cslt::Direction::FORWARD);   // ascending

arr->sort([](const int& a, const int& b) { return a - b; },
          cslt::Direction::REVERSE);   // descending
Template Parameters:

Func – Callable with signature int(const T&, const T&). Must return negative if a < b, zero if a == b, and positive if a > b — identical to the C qsort comparator convention. Lambdas, functors, and plain function pointers are all accepted.

Parameters:
  • cmp – Comparator callable

  • dir – Direction::FORWARD for ascending order; Direction::REVERSE for descending order

Returns:

true on success; false if the array is uninitialised or contains fewer than two elements (no-op in those cases)

template<typename Func>
inline Expected<T> min(Func cmp) const noexcept

Return the minimum element in the array.

Any callable is accepted: lambda, functor, or plain function pointer. For T == uint8_t the comparator is ignored — the SIMD back-end uses hardware min directly.

Scalar types — subtract or use the branchless idiom:

// int array — branchless (avoids signed overflow risk of a - b)
auto r = arr->min([](const int& a, const int& b) {
    return (a > b) - (a < b);
});

// float array — branchless
auto r = arr->min([](const float& a, const float& b) {
    return (a > b) - (a < b);
});
Comparator examples

Struct / class types — compare on a specific field:

// Particle struct with fields mass, charge, id
// Find the particle with the lowest mass
auto r = arr->min([](const Particle& a, const Particle& b) {
    return (a.mass > b.mass) - (a.mass < b.mass);
});

// Find the particle with the most negative charge
auto r = arr->min([](const Particle& a, const Particle& b) {
    return (a.charge > b.charge) - (a.charge < b.charge);
});

File-scope comparator function (reusable across multiple calls):

static int cmp_particle_mass(const Particle& a, const Particle& b) {
    return (a.mass > b.mass) - (a.mass < b.mass);
}
auto r = arr->min(cmp_particle_mass);
if (r.hasValue()) {
    Particle lightest = r.value();
}

Error conditions

Template Parameters:

Func – Callable with signature int(const T&, const T&). The comparator must satisfy:

  • returns a negative value when a < b

  • returns zero when a == b

  • returns a positive value when a > b

Parameters:

cmp – Comparator callable

Returns:

Expected<T> containing a copy of the minimum element on success, or an EmptyError if the array has no elements

template<typename Func>
inline Expected<T> max(Func cmp) const noexcept

Return the maximum element in the array.

Any callable is accepted: lambda, functor, or plain function pointer. For T == uint8_t the comparator is ignored — the SIMD back-end uses hardware max directly.

Scalar types — use the branchless idiom:

// int array
auto r = arr->max([](const int& a, const int& b) {
    return (a > b) - (a < b);
});

// float array
auto r = arr->max([](const float& a, const float& b) {
    return (a > b) - (a < b);
});
Comparator examples

Struct / class types — compare on a specific field:

// Particle struct with fields mass, charge, id
// Find the particle with the highest mass
auto r = arr->max([](const Particle& a, const Particle& b) {
    return (a.mass > b.mass) - (a.mass < b.mass);
});

// Find the particle with the highest charge
auto r = arr->max([](const Particle& a, const Particle& b) {
    return (a.charge > b.charge) - (a.charge < b.charge);
});

File-scope comparator function (reusable across multiple calls):

static int cmp_particle_mass(const Particle& a, const Particle& b) {
    return (a.mass > b.mass) - (a.mass < b.mass);
}
auto r = arr->max(cmp_particle_mass);
if (r.hasValue()) {
    Particle heaviest = r.value();
}

Error conditions

Template Parameters:

Func – Callable with signature int(const T&, const T&). The comparator must satisfy:

  • returns a negative value when a < b

  • returns zero when a == b

  • returns a positive value when a > b

Parameters:

cmp – Comparator callable

Returns:

Expected<T> containing a copy of the maximum element on success, or an EmptyError if the array has no elements

inline Expected<size_t> contains(const T &value) const noexcept

Search for the first element whose byte representation matches value and return its index.

Floating-point types are excluded because -0.0 and +0.0 are equal by value but differ in bit pattern, so memcmp-based search would produce incorrect results. Use the predicate overload (coming soon) for float, double, and long double.

Compile-time restriction

This overload is only available when T satisfies both:

  • std::is_trivially_copyable_v<T> == true

  • std::is_floating_point_v<T> == false

Structs with padding bytes are also subject to this caveat — padding content is unspecified by the standard and may differ between two otherwise identical objects. Prefer the predicate overload for structs unless you can guarantee there are no padding bytes.

SIMD dispatch

The search delegates to simd_contains_uint8(), which is resolved at compile time to the best available SIMD back-end (AVX-512, AVX2, AVX, SSE4.1, SSSE3, SSE2, NEON, SVE, SVE2) or a portable scalar fallback. For element sizes 1, 2, 4, and 8 bytes the SIMD path is taken; all other sizes fall through to a scalar memcmp loop.

Usage examples
// Search for an integer value
auto r = arr->contains(42);
if (r.hasValue()) {
    size_t idx = r.value();  // index of first 42
}

// Search for a plain struct (no padding, no floats)
struct Vec3i { int x, y, z; };
auto r = arr->contains(Vec3i{1, 2, 3});
Error conditions

Parameters:

value – Element to search for. Comparison is performed by comparing the raw byte representation of each array element against the raw bytes of value using simd_contains_uint8() (for the SIMD-accelerated path) or memcmp() (for the scalar remainder).

Returns:

Expected<size_t> containing the zero-based index of the first match on success, or an OutOfBoundsError if no match is found

template<typename Func>
inline Expected<size_t> contains(const T &value, Func eq) const noexcept

Search for the first element for which a caller-supplied equality predicate returns true and return its index.

Performs a linear scan from index 0 to size()-1, calling eq(value, data_[i]) at each position. No SIMD acceleration is applied — equality semantics are entirely defined by the caller’s predicate, so no byte-level optimisation is possible.

This overload is intended for:

  • Non-trivially-copyable types (classes with user-defined copy constructors or destructors)

  • Trivially copyable types where value equality differs from bitwise equality (e.g. structs with padding bytes)

  • Any type where a custom notion of equality is required (e.g. case-insensitive string matching, epsilon comparison)

Usage examples
// Non-trivial class with a user-defined copy constructor
auto r = arr->contains(target,
    [](const MyClass& a, const MyClass& b) {
        return a.id() == b.id();
    });
if (r.hasValue()) {
    size_t idx = r.value();
}

// Struct with padding — use operator== rather than memcmp
struct Padded { char c; int x; };   // likely has 3 padding bytes
auto r = arr->contains(needle,
    [](const Padded& a, const Padded& b) {
        return a.c == b.c && a.x == b.x;
    });

// File-scope predicate (reusable across multiple calls)
static bool eq_by_id(const MyClass& a, const MyClass& b) {
    return a.id() == b.id();
}
auto r = arr->contains(target, eq_by_id);
Error conditions

Template Parameters:

Func – Callable with signature bool(const T&, const T&). Must return true when the two arguments are considered equal. Any callable is accepted: lambda, functor, or plain function pointer.

Parameters:
  • value – Element to search for, passed as the first argument to eq at each position

  • eq – Equality predicate

Returns:

Expected<size_t> containing the zero-based index of the first match on success, or an OutOfBoundsError if no match is found

template<typename Func>
inline Expected<size_t> binary_search(const T &value, Func cmp, Direction dir, bool is_sorted) noexcept

Search for value using binary search and return its index.

Implements a standard iterative binary search. When is_sorted is false the array is sorted in place via the same quicksort used by sort() before the search begins, so subsequent calls with is_sorted == true are valid.

If multiple elements compare equal to value the index of the first (lowest) occurrence is returned. Once a match is found the search continues leftward until no earlier match exists.

Complexity

  • O(n log n) when is_sorted == false (sort step dominates)

  • O(log n) when is_sorted == true

Usage examples
auto cmp = [](const int& a, const int& b) {
    return (a > b) - (a < b);
};

// Array not yet sorted — sort in place then search
arr->binary_search(30, cmp, cslt::Direction::FORWARD, false);

// Array already sorted ascending — skip the sort step
auto r = arr->binary_search(30, cmp, cslt::Direction::FORWARD, true);
if (r.hasValue()) {
    size_t idx = r.value();
}

// Struct array sorted descending by x field
auto cmp_pt = [](const Point& a, const Point& b) {
    return (a.x > b.x) - (a.x < b.x);
};
auto r = arr->binary_search(target, cmp_pt,
                            cslt::Direction::REVERSE, true);
Error conditions

Template Parameters:

Func – Callable with signature int(const T&, const T&). Same convention as sort() and min()/max():

  • negative when a < b

  • zero when a == b

  • positive when a > b

Parameters:
  • value – Element to search for

  • cmp – Comparator defining the sort order

  • dir – Direction::FORWARD if the array is (or should be) sorted ascending; Direction::REVERSE if descending. Must be consistent with cmp.

  • is_sorted – Pass true if the array is already sorted in the order defined by cmp and dir. Pass false to sort the array in place before searching.

Returns:

Expected<size_t> containing the zero-based index of a matching element on success, or an OutOfBoundsError if no match is found or the array is empty.

template<typename Func>
inline Expected<std::pair<size_t, size_t>> bracketed_binary_search(const T &value, Func cmp, Direction dir, bool is_sorted) noexcept

Search for the bracketing pair of indices surrounding value in the sorted array.

When value exists in the array both bounds are the index of the first (lowest) matching element.

Returns OutOfBoundsError when:

  • the array is empty

  • value is less than the smallest element (no lower bound)

  • value is greater than the largest element (no upper bound)

    The algorithm performs a single binary search pass to locate the insertion point of value, then derives the lower and upper bound indices from the elements immediately surrounding that point.

Complexity

  • O(n log n) when is_sorted == false (sort step dominates)

  • O(log n) when is_sorted == true

Usage examples
// Array {10, 20, 30, 40, 50} — bracket 25
auto r = arr->bracketed_binary_search(25, cmp,
                                      cslt::Direction::FORWARD, true);
if (r.hasValue()) {
    size_t lo = r.value().first;   // index of 20
    size_t hi = r.value().second;  // index of 30
}

// Exact match — bracket 30
auto r = arr->bracketed_binary_search(30, cmp,
                                      cslt::Direction::FORWARD, true);
// r.value().first == r.value().second == 2 (index of 30)
Error conditions

Template Parameters:

Func – Callable with signature int(const T&, const T&). Same convention as sort() and binary_search():

  • negative when a < b

  • zero when a == b

  • positive when a > b

Parameters:
  • value – Element to bracket

  • cmp – Comparator defining the sort order

  • dir – Direction::FORWARD for ascending order; Direction::REVERSE for descending order. Must be consistent with cmp.

  • is_sorted – Pass true if the array is already sorted in the order defined by cmp and dir. Pass false to sort the array in place before searching.

Returns:

Expected<std::pair<size_t, size_t>> where:

  • first is the index of the largest element <= value (lower bound)

  • second is the index of the smallest element >= value (upper bound)

Public Static Functions

static inline Expected<Array<T>*> init(size_t capacity, Allocator &allocator) noexcept

Initialise an allocator-backed Array.

Allocates the Array object itself and its internal element buffer through the provided allocator using placement new, mirroring the String::init() pattern.

cslt::HeapAllocator alloc;
auto r = cslt::Array<float>::init(16, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::Array<float>, cslt::ArrayDeleter<float>> arr(r.value());
    arr->push_back(3.14f);
}
Error conditions:

Parameters:
  • capacity – Initial element capacity (must be > 0)

  • allocatorAllocator to use for memory management

Returns:

Expected<Array<T>*> containing a pointer to the Array, or an error

static inline Expected<Array<T>*> copy(const Array<T> &src, Allocator &allocator) noexcept

Create a deep copy of an existing Array using a caller-supplied allocator.

Allocates a fresh Array with the same capacity as src, then copy-constructs each live element. On any failure the partially constructed Array is cleaned up and an error is returned.

cslt::HeapAllocator alloc;
auto r  = cslt::Array<int>::init(4, alloc);
// ... push elements ...
auto r2 = cslt::Array<int>::copy(*r.value(), alloc);
Parameters:
Returns:

Expected<Array<T>*> containing a pointer to the new Array, or an error

static inline Expected<Array<T>*> copy(const Array<T> &src) noexcept

Create a deep copy of an existing Array using the source Array’s own allocator.

Convenience overload that forwards to copy(src, allocator) using the allocator already associated with src.

auto r2 = cslt::Array<int>::copy(*arr);
Parameters:

srcArray to copy from

Returns:

Expected<Array<T>*> containing a pointer to the new Array, or an error

template<typename Func>
static inline Expected<Array<T>*> cumulative(const Array<T> &src, Func add, Allocator &allocator) noexcept

Produce a new Array containing the prefix-sum (running total) of this array using a caller-supplied accumulation function.

Mirrors the C cumulative_array() prefix-sum algorithm:

  • result[0] = src[0] (seed — no identity element required)

  • result[i] = result[i-1] + src[i] for i >= 1

The output Array is allocated with exactly len_ slots (no extra capacity), making it a fixed-length snapshot.

auto r = cslt::Array<int>::cumulative(*src,
    [](int& accum, const int& elem) { accum += elem; },
    alloc);
Error conditions:

Template Parameters:

Func – Callable with signature void(T&, const T&). The first argument is the current output element (modified in place); the second is the incoming source element. Any callable is accepted: plain function, lambda, or functor.

Parameters:
  • add – Accumulation callable

  • allocatorAllocator used to back the returned Array

Returns:

Expected<Array<T>*> containing the cumulative Array on success, or an error

template<typename Func>
static inline Expected<Array<T>*> cumulative(const Array<T> &src, Func add) noexcept

Produce a new Array containing the prefix-sum of this array using the source array’s own allocator.

Convenience overload that forwards to cumulative(src, add, allocator) using the allocator already associated with this array.

auto r = cslt::Array<int>::cumulative(*src,
    [](int& accum, const int& elem) { accum += elem; });
Template Parameters:

Func – Callable with signature void(T&, const T&)

Parameters:

add – Accumulation callable

Returns:

Expected<Array<T>*> on success, or an error

static inline Expected<Array<T>*> slice(const Array<T> &src, size_t start, size_t end, Allocator &allocator) noexcept

Return a new Array containing the elements in [start, end)

Mirrors the C slice_array() semantics exactly:

  • start >= end is an ArgumentError

  • end > len_ is an OutOfBoundsError The returned Array has capacity == (end - start) and is a fixed-length snapshot of the source range. For trivially copyable T the copy uses std::memcpy; for non-trivial T each element is copy-constructed individually.

// array contains {0, 1, 2, 3, 4}
auto r = cslt::Array<int>::slice(*arr, 1, 4, alloc);
// result contains {1, 2, 3}
Error conditions:

Parameters:
  • start – Zero-based index of the first element to include

  • end – One-past-the-last index (exclusive upper bound). Must satisfy start < end <= size().

  • allocatorAllocator used to back the returned Array

Returns:

Expected<Array<T>*> containing the slice on success, or an error

static inline Expected<Array<T>*> slice(const Array<T> &src, size_t start, size_t end) noexcept

Return a new Array containing the elements in [start, end) using the source array’s own allocator.

Convenience overload that forwards to slice(src, start, end, allocator) using the allocator already associated with this array.

auto r = cslt::Array<int>::slice(*arr, 1, 4);
Parameters:
  • start – Zero-based index of the first element to include

  • end – One-past-the-last index (exclusive upper bound)

Returns:

Expected<Array<T>*> on success, or an error