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>patternSupports 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 newArenaAllocator — 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.
Recommended Allocators
Different allocators are appropriate depending on how arrays are constructed, resized, and accessed:
HeapAllocator
Best for:
general-purpose dynamic arrays
frequent resizing and reallocation
applications where simplicity and correctness are priorities
Arrays rely on contiguous memory and often require reallocation during growth. The heap allocator provides flexible and predictable behavior.
ArenaAllocator
Best for:
append-only arrays
batch construction followed by full discard
intermediate data structures in algorithms
Arena allocation is efficient when arrays are built once and not resized frequently. Reallocation-heavy workloads may result in unused memory.
BuddyAllocator
Best for:
large arrays with dynamic resizing
systems where memory fragmentation must be controlled
long-lived arrays with periodic growth
The buddy allocator handles large contiguous allocations well and helps manage fragmentation compared to a general heap.
PoolAllocator / SlabAllocator
Best for:
fixed-capacity arrays
small arrays with uniform sizes
performance-critical paths with predictable memory use
These allocators are not well suited for resizable arrays but can be very efficient when capacity is known in advance.
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 usestd::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) orcslt::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 ArrayAllocator-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
valuein its placeindex == 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
indexis 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
ptrpoints 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
otherand 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 fromotherbefore writing, and the snapshot of other.len_ is taken once before the loop.
- Parameters:
other – Array 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) noexceptSort 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 noexceptReturn 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
Array is empty (EmptyError)
- 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 noexceptReturn 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
Array is empty (EmptyError)
- 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
valueand 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
Value not found in the array (OutOfBoundsError)
- Parameters:
value – Element to search for. Comparison is performed by comparing the raw byte representation of each array element against the raw bytes of
valueusing 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 noexceptSearch 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
Value not found in the array (OutOfBoundsError)
- 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
eqat each positioneq – 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) noexceptSearch for
valueusing binary search and return its index.Implements a standard iterative binary search. When
is_sortedis 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
valuethe 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
Value not found (OutOfBoundsError)
Array is empty (OutOfBoundsError)
- 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
cmpanddir. 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) noexceptSearch for the bracketing pair of indices surrounding
valuein the sorted array.When
valueexists in the array both bounds are the index of the first (lowest) matching element.Returns OutOfBoundsError when:
the array is empty
valueis less than the smallest element (no lower bound)
valueis 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
Array is empty (OutOfBoundsError)
Value below the smallest element — no lower bound (OutOfBoundsError)
Value above the largest element — no upper bound (OutOfBoundsError)
- 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
cmpanddir. 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:
capacity == 0 (ArgumentError)
Allocation of the Array struct fails (MemoryError)
Allocation of the element buffer fails (MemoryError)
- 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);
- 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);
- template<typename Func>
static inline Expected<Array<T>*> cumulative(const Array<T> &src, Func add, Allocator &allocator) noexceptProduce 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:
Array is empty (EmptyError)
Allocation of the output struct or buffer fails (MemoryError)
- 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:
- 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) noexceptProduce 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:
start >= end (ArgumentError)
end > len_ (OutOfBoundsError)
Allocation failure (MemoryError)
- 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