C++ String Overview

String handling in C++ traditionally relies on std::string, 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++ String module in this library provides a lightweight, allocator-aware string container implemented in C++ and declared in string.hpp. Unlike std::string, this container:

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

  • Explicitly tracks string length, allocated capacity, and the owning allocator

  • Enforces initialization through a factory pattern preventing uninitialized strings

  • Provides automatic cleanup via RAII with custom deleters

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

The only dependency this module has within the CSalt library is the allocator.hpp, error.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 string 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 it conforms to the Allocator base class.

Factory Pattern and RAII

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

  • Prevents creation of uninitialized or invalid strings

  • 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 StringDeleter custom deleter, which works seamlessly with UniquePtr to provide RAII semantics:

// Automatic cleanup when UniquePtr goes out of scope
{
    auto result = cslt::String::init("example", 0, allocator);
    if (result.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(result.value());
        // Use the string...
    } // String and buffer automatically freed here
}

String Class

class String

Allocator-backed string container.

Provides a string container that uses custom allocators for memory management. The string and its internal buffer are both allocated through the provided allocator.

Key features:

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

  • Null-terminated C-string compatibility

  • Capacity management with optional pre-allocation

  • Safe truncation when capacity is insufficient

  • RAII-based memory management through init factory functions

Usage pattern:

  • Must be initialized via static factory function init()

  • Cannot be constructed directly (private constructor)

  • Automatically manages both string object and buffer memory

  • Cleaned up through custom deleter

// Create with heap allocator
cslt::HeapAllocator allocator;
auto str_result = cslt::String::init("hello", 0, allocator);

if (str_result.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(str_result.value());
    std::cout << str->c_str() << std::endl;  // prints "hello"
    std::cout << "length: " << str->size() << std::endl;
    
    // String is automatically cleaned up when UniquePtr goes out of scope
}

Note

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

Public Functions

char operator[](size_t index) const noexcept

Read-only index access to a single character.

Provides bounds-checked read access to individual characters. Only indices within the logical length [0, len_) are valid. The null terminator at position len_ is intentionally excluded.

cslt::HeapAllocator alloc;
auto r = cslt::String::init("hello", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    char c = (*s)[1];  // 'e'
}
Error behaviour:

If index >= len_ or the internal buffer is null, ‘\0’ is returned as a safe sentinel. This keeps the operator noexcept without invoking undefined behaviour on an out-of-bounds access.

See also

operator[](size_t) for write access

Parameters:

index – Zero-based position within the used string (0 to len_-1)

Returns:

The character at that position

char &operator[](size_t index) noexcept

Read-write index access to a single character.

Provides bounds-checked write access to individual characters. Only indices within the logical length [0, len_) are valid. The null terminator at position len_ cannot be modified through this operator.

cslt::HeapAllocator alloc;
auto r = cslt::String::init("hello", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    (*s)[1] = 'a';  // "hallo"
}
Error behaviour:

If index >= len_ or the internal buffer is null, a reference to an internal static sentinel ‘\0’ character is returned. Assigning to this sentinel is silently discarded, which preserves both noexcept and defined behaviour on out-of-bounds writes.

See also

operator[](size_t) const for read-only access

Parameters:

index – Zero-based position within the used string (0 to len_-1)

Returns:

Reference to the character at that position

inline const char *c_str() const noexcept

Get the internal null-terminated C string.

Returns a pointer to the underlying character buffer. The pointer remains valid until the String is destroyed.

auto r = cslt::String::init("example", 0, allocator);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    const char* text = s->c_str();
    std::cout << text << std::endl;
}
Returns:

Pointer to null-terminated character buffer

inline size_t size() const noexcept

Get the logical length of the string.

Returns the number of characters stored in the container, not including the null terminator.

auto r = cslt::String::init("hello", 0, allocator);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    std::cout << "length = " << s->size() << std::endl;  // 5
}
Returns:

Number of characters excluding the null terminator

inline size_t capacity() const noexcept

Get the total allocated buffer size.

Returns the number of bytes allocated for the internal buffer, including space reserved for the null terminator.

auto r = cslt::String::init("test", 100, allocator);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    std::cout << "capacity = " << s->capacity() << std::endl;  // 101
}
Returns:

Total allocated bytes including the null terminator

inline Allocator *allocator() const noexcept

Get the allocator used by this string.

Returns the allocator that was used to create this string and will be used to free it.

auto r = cslt::String::init("test", 0, allocator);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    Allocator* alloc = s->allocator();
    // Can query allocator properties...
}
Returns:

Pointer to the allocator

bool concat(const char *str) noexcept

Concatenate a C-string to the end of this string.

Appends the contents of str to the end of the current string. The function automatically handles buffer growth when necessary, using the allocator’s realloc() method if available, or falling back to allocate-copy-free pattern.

Key behaviors:

  • Always maintains null termination

  • Detects and handles self-aliasing (when str points within current buffer)

  • Prevents size_t overflow in length calculations

  • Returns immediately if str is empty (no-op, returns true)

  • Grows buffer capacity as needed

Buffer growth strategy:

  • If allocator supports realloc(), uses in-place reallocation

  • Otherwise, allocates new buffer, copies data, and frees old buffer

  • Pre-allocating capacity via init() can reduce reallocations

cslt::HeapAllocator allocator;
auto r = cslt::String::init("Hello", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    // Basic concatenation
    bool success = str->concat(" world");
    if (success) {
        std::cout << str->c_str() << std::endl;  // "Hello world"
        std::cout << str->size() << std::endl;    // 11
    }
    
    // Multiple concatenations
    str->concat("!");
    str->concat(" How are you?");
    std::cout << str->c_str() << std::endl;  // "Hello world! How are you?"
    
    // Self-aliasing example (safe)
    str->concat(str->c_str());  // Doubles the string
    
    // Empty string (no-op)
    str->concat("");  // Returns true, no change
}
Self-aliasing:

The function safely handles cases where str points to a substring of the current buffer. It detects this condition and creates a temporary copy before any reallocation occurs.

Error conditions:

Returns false if:

  • str is nullptr

  • Internal buffer is null (corrupted String)

  • Allocator is null

  • Length overflow would occur (len + strlen(str) + 1 > SIZE_MAX)

  • Memory allocation fails during buffer growth

Thread safety:

Not thread-safe. External synchronization required for concurrent access.

// Pre-allocate capacity to minimize reallocations
cslt::HeapAllocator allocator;
auto r = cslt::String::init("", 100, allocator);  // 100 bytes capacity

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> path(r.value());
    
    // Multiple appends without reallocation
    path->concat("/usr");
    path->concat("/local");
    path->concat("/bin");
    
    std::cout << path->c_str() << std::endl;  // "/usr/local/bin"
    std::cout << "Size: " << path->size() << std::endl;
    std::cout << "Capacity: " << path->capacity() << std::endl;  // Still 101
}
// Error handling
cslt::HeapAllocator allocator;
auto r = cslt::String::init("test", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    // Null pointer - returns false
    if (!str->concat(nullptr)) {
        std::cerr << "Failed to concat null pointer" << std::endl;
    }
    
    // String unchanged after failure
    std::cout << str->c_str() << std::endl;  // Still "test"
}

See also

concat(const String&) for String-to-String concatenation

See also

init() for capacity pre-allocation

Parameters:

str – Null-terminated C-string to append

Returns:

true if concatenation succeeded, false on failure

bool concat(const String &str) noexcept

Concatenate another String object to the end of this string.

Convenience overload that appends the contents of another String object. This method delegates to concat(const char*) internally, extracting the C-string from the provided String.

The source string (str) is not modified and remains independent. Only the contents are copied, not ownership or allocator references.

cslt::HeapAllocator allocator;

auto r1 = cslt::String::init("Hello", 0, allocator);
auto r2 = cslt::String::init(" world", 0, allocator);

if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str1(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str2(r2.value());
    
    // Concatenate String objects
    bool success = str1->concat(*str2);
    if (success) {
        std::cout << str1->c_str() << std::endl;  // "Hello world"
        std::cout << str2->c_str() << std::endl;  // " world" (unchanged)
    }
}
Error conditions:

Returns false if:

Thread safety:

Not thread-safe. External synchronization required for concurrent access to either string.

// Building a sentence from words
cslt::HeapAllocator allocator;

auto r1 = cslt::String::init("The", 50, allocator);
auto r2 = cslt::String::init(" quick", 0, allocator);
auto r3 = cslt::String::init(" brown", 0, allocator);
auto r4 = cslt::String::init(" fox", 0, allocator);

if (r1.hasValue() && r2.hasValue() && r3.hasValue() && r4.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> sentence(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> word2(r2.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> word3(r3.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> word4(r4.value());
    
    sentence->concat(*word2);
    sentence->concat(*word3);
    sentence->concat(*word4);
    
    std::cout << sentence->c_str() << std::endl;  // "The quick brown fox"
    std::cout << sentence->size() << std::endl;    // 19
}
// Mixing C-string and String concatenation
cslt::HeapAllocator allocator;

auto r1 = cslt::String::init("Hello", 0, allocator);
auto r2 = cslt::String::init("world", 0, allocator);

if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str1(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str2(r2.value());
    
    str1->concat(" ");        // C-string
    str1->concat(*str2);      // String object
    str1->concat("!");        // C-string
    
    std::cout << str1->c_str() << std::endl;  // "Hello world!"
}
// Using different allocators (allowed but not common)
cslt::HeapAllocator heap_alloc;
cslt::ArenaAllocator arena_alloc(1024);

auto r1 = cslt::String::init("heap: ", 0, heap_alloc);
auto r2 = cslt::String::init("arena", 0, arena_alloc);

if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str1(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str2(r2.value());
    
    // Works fine - only copies content, not allocator
    str1->concat(*str2);
    std::cout << str1->c_str() << std::endl;  // "heap: arena"
    
    // str1 still uses heap_alloc for any growth
    // str2 still uses arena_alloc
}

See also

concat(const char*) for C-string concatenation

See also

c_str() to extract the C-string from a String object

Parameters:

strString object to append

Returns:

true if concatenation succeeded, false on failure

int8_t compare(const char *str) const noexcept

Lexicographically compare this string with a C-string.

Comparison semantics:

  • Characters are compared as unsigned bytes (0-255)

  • Comparison proceeds left-to-right until a difference is found

  • If one string is a prefix of the other, the shorter is considered less

  • Null terminators are considered for the C-string parameter

  • The String’s logical length (len_) determines comparison extent

Return value interpretation:

  • -1: This string sorts before str (this < str)

  • 0: Strings are identical in content and length

  • 1: This string sorts after str (this > str)

  • -128: Error condition (null pointer detected)

cslt::HeapAllocator allocator;
auto r = cslt::String::init("hello", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    // Basic comparisons
    int8_t cmp1 = str->compare("hello");
    std::cout << "Compare 'hello' vs 'hello': " << (int)cmp1 << std::endl;  // 0
    
    int8_t cmp2 = str->compare("world");
    std::cout << "Compare 'hello' vs 'world': " << (int)cmp2 << std::endl;  // -1
    
    int8_t cmp3 = str->compare("apple");
    std::cout << "Compare 'hello' vs 'apple': " << (int)cmp3 << std::endl;  // 1
}
Edge cases:

  • Empty strings: Compare as equal if both are empty

  • Embedded nulls in String: Comparison stops at logical length, not null

  • C-string shorter than String: C-string is considered less

  • C-string longer than String: String is considered less

Error conditions:

Returns -128 if:

  • str parameter is nullptr

  • Internal buffer (str_) is null (corrupted String)

Performance:

  • O(n) where n = min(this->size(), strlen(str))

  • Early termination on first difference

  • Single pass, no allocation

Thread safety:

Thread-safe for concurrent reads if str is not being modified. Not thread-safe if this String is being modified concurrently.

// Using for sorting
cslt::HeapAllocator allocator;

std::vector<cslt::UniquePtr<cslt::String, cslt::StringDeleter>> strings;

auto r1 = cslt::String::init("zebra", 0, allocator);
auto r2 = cslt::String::init("apple", 0, allocator);
auto r3 = cslt::String::init("mango", 0, allocator);

if (r1.hasValue() && r2.hasValue() && r3.hasValue()) {
    strings.push_back(cslt::UniquePtr<cslt::String, cslt::StringDeleter>(r1.value()));
    strings.push_back(cslt::UniquePtr<cslt::String, cslt::StringDeleter>(r2.value()));
    strings.push_back(cslt::UniquePtr<cslt::String, cslt::StringDeleter>(r3.value()));
    
    // Sort using compare
    std::sort(strings.begin(), strings.end(),
        [](const auto& a, const auto& b) {
            return a->compare(b->c_str()) < 0;
        });
    
    for (const auto& s : strings) {
        std::cout << s->c_str() << std::endl;
    }
    // Output: apple, mango, zebra
}

See also

compare(const String&) for String-to-String comparison

Parameters:

str – Null-terminated C-string to compare against

Returns:

int8_t comparison result:

  • -1 if this string is less than str

  • 0 if strings are equal

  • 1 if this string is greater than str

  • -128 if either string is null (error sentinel)

    Performs lexicographic comparison using unsigned byte values. The comparison follows standard strcmp semantics but returns a compact int8_t result with three possible values plus an error sentinel.

int8_t compare(const String &other) const noexcept

Lexicographically compare this string with another String object.

Comparison algorithm:

  • Compares min(this->len_, other.len_) characters byte-by-byte

  • Uses unsigned byte comparison (0-255)

  • If all common characters match, compares lengths

  • Early termination on first difference

Return value interpretation:

  • -1: This string sorts before other (this < other)

  • 0: Strings are identical in content and length

  • 1: This string sorts after other (this > other)

  • -128: Error condition (null internal buffer)

cslt::HeapAllocator allocator;

auto r1 = cslt::String::init("apple", 0, allocator);
auto r2 = cslt::String::init("banana", 0, allocator);

if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str1(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str2(r2.value());
    
    int8_t cmp = str1->compare(*str2);
    
    if (cmp < 0) {
        std::cout << "'" << str1->c_str() << "' comes before '" 
                  << str2->c_str() << "'" << std::endl;
        // Output: 'apple' comes before 'banana'
    }
}
Advantages over C-string comparison:

  • No need to scan for null terminator in other

  • Handles embedded nulls correctly (compares up to logical length)

  • More efficient for long strings with early differences

  • Type-safe (no risk of comparing with invalid C-string)

Performance:

  • O(n) where n = min(this->size(), other.size())

  • No strlen() calls needed

  • Early termination on first difference

  • Can be SIMD-optimized (see implementation notes)

Error conditions:

Returns -128 if:

  • This String’s internal buffer (str_) is null

  • Other String’s internal buffer is null

Thread safety:

Thread-safe for concurrent reads if neither string is being modified. Not thread-safe if either String is being modified concurrently.

// Equality testing
cslt::HeapAllocator allocator;

auto r1 = cslt::String::init("test", 0, allocator);
auto r2 = cslt::String::init("test", 0, allocator);
auto r3 = cslt::String::init("Test", 0, allocator);

if (r1.hasValue() && r2.hasValue() && r3.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str1(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str2(r2.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str3(r3.value());
    
    bool equal1 = (str1->compare(*str2) == 0);
    std::cout << "str1 == str2: " << equal1 << std::endl;  // true
    
    bool equal2 = (str1->compare(*str3) == 0);
    std::cout << "str1 == str3: " << equal2 << std::endl;  // false
}

See also

compare(const char*) for C-string comparison

Note

For SIMD-optimized implementation, see string_compare_impl.cpp

Parameters:

otherString object to compare against

Returns:

int8_t comparison result:

  • -1 if this string is less than other

  • 0 if strings are equal

  • 1 if this string is greater than other

  • -128 if either string’s buffer is null (error sentinel)

    Performs lexicographic comparison of two String objects. This is the preferred method for comparing String instances as it uses the stored length information for optimization.

void reset() noexcept

Reset the string to an empty state.

Sets the string length to zero and null-terminates at position 0. The allocated buffer is preserved and can be reused for future operations.

Key behaviors:

  • Sets len_ to 0

  • Places ‘\0’ at position 0

  • Preserves allocated capacity

  • O(1) constant time operation

cslt::HeapAllocator allocator;
auto r = cslt::String::init("hello world", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    std::cout << "Before: " << str->c_str() << std::endl;  // "hello world"
    std::cout << "Size: " << str->size() << std::endl;      // 11
    
    str->reset();
    
    std::cout << "After: " << str->c_str() << std::endl;   // ""
    std::cout << "Size: " << str->size() << std::endl;      // 0
    std::cout << "Capacity: " << str->capacity() << std::endl;  // Unchanged
    
    // Reuse the buffer
    str->concat("new content");
    std::cout << "Reused: " << str->c_str() << std::endl;  // "new content"
}
Performance:

O(1) - No memory allocation or deallocation occurs.

See also

~String() to free the buffer

See also

init() to create a String with specific capacity

Expected<String*> copy() const noexcept

Create a deep copy using this string’s allocator.

Creates an independent copy with the same content using the same allocator as the original. The copy has its own buffer and can be modified independently.

Key behaviors:

  • Creates new String object and buffer

  • Uses this->allocator_ for the copy

  • Copy capacity matches original length

  • O(n) where n is the string length

cslt::HeapAllocator allocator;
auto r = cslt::String::init("hello", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> original(r.value());
    
    // Create a copy
    auto copy_r = original->copy();
    
    if (copy_r.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> copy(copy_r.value());
        
        // Modify copy - original unchanged
        copy->concat(" world");
        
        std::cout << "Original: " << original->c_str() << std::endl;  // "hello"
        std::cout << "Copy: " << copy->c_str() << std::endl;          // "hello world"
    }
}

See also

copy(Allocator&) to copy using a different allocator

Returns:

Expected<String*> containing pointer to new String or error

Expected<String*> copy(Allocator &allocator) const noexcept

Create a deep copy using a specified allocator.

Creates an independent copy with the same content using the specified allocator. Useful for copying strings between different allocator contexts.

Key behaviors:

  • Creates new String object and buffer

  • Uses provided allocator for the copy

  • Copy capacity matches original length

  • O(n) where n is the string length

cslt::HeapAllocator heap_alloc;
cslt::ArenaAllocator arena_alloc(1024);

auto r = cslt::String::init("data", 0, heap_alloc);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> heap_str(r.value());
    
    // Copy from heap to arena allocator
    auto copy_r = heap_str->copy(arena_alloc);
    
    if (copy_r.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> arena_str(copy_r.value());
        
        std::cout << "Both have same content: " 
                  << (strcmp(heap_str->c_str(), arena_str->c_str()) == 0) 
                  << std::endl;  // true
        std::cout << "Different allocators: " 
                  << (heap_str->allocator() != arena_str->allocator()) 
                  << std::endl;  // true
    }
}

See also

copy() to copy using the same allocator

Parameters:

allocatorAllocator to use for the new String

Returns:

Expected<String*> containing pointer to new String or error

size_t find(const String &needle, const void *begin = nullptr, const void *end = nullptr, direction_t dir = FORWARD) const noexcept

Find substring within this string.

Searches for the first occurrence of needle within the specified range using SIMD-optimized substring search. Returns the offset from the start of the string (str_) where the needle begins.

Return values:

  • Offset (0 to len_-1) if found

  • SIZE_MAX if not found or error

  • 0 if needle is empty

Range parameters:

  • nullptr for begin/end uses entire string

  • Pointers must be within buffer (validated with is_ptr())

  • Range must be monotonic: begin <= end

cslt::HeapAllocator allocator;
auto r = cslt::String::init("hello world hello", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    auto needle_r = cslt::String::init("hello", 0, allocator);
    if (needle_r.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> needle(needle_r.value());
        
        // Find first occurrence
        size_t pos = str->find(*needle);
        std::cout << "Found at: " << pos << std::endl;  // 0
        
        // Find from position 1 onwards
        const void* start = str->c_str() + 1;
        pos = str->find(*needle, start);
        std::cout << "Found at: " << pos << std::endl;  // 12
        
        // Find in reverse (last occurrence)
        pos = str->find(*needle, nullptr, nullptr, REVERSE);
        std::cout << "Last at: " << pos << std::endl;  // 12
        
        // Find within specific range
        const void* begin = str->c_str() + 5;
        const void* end = str->c_str() + 15;
        pos = str->find(*needle, begin, end);
        std::cout << "In range: " << pos << std::endl;  // 12
        
        // Not found
        auto miss_r = cslt::String::init("xyz", 0, allocator);
        if (miss_r.hasValue()) {
            cslt::UniquePtr<cslt::String, cslt::StringDeleter> miss(miss_r.value());
            pos = str->find(*miss);
            if (pos == SIZE_MAX) {
                std::cout << "Not found" << std::endl;
            }
        }
    }
}
Performance:

O(n*m) worst case, but SIMD-optimized for typical cases.

See also

find(const char*, const void*, const void*, direction_t)

See also

is_ptr(const void*) for pointer validation

Parameters:
  • needleString to search for

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

  • dir – Search direction (default: FORWARD)

Returns:

Offset from beginning of string where needle was found, or SIZE_MAX if not found

size_t find(const char *needle, const void *begin = nullptr, const void *end = nullptr, direction_t dir = FORWARD) const noexcept

Find C-string within this string.

Convenience overload that accepts a C-string needle. Behavior is identical to find(const String&). Uses strlen() to determine needle length.

Return values:

  • Offset (0 to len_-1) if found

  • SIZE_MAX if not found or error

  • 0 if needle is empty

cslt::HeapAllocator allocator;
auto r = cslt::String::init("The quick brown fox jumps over the lazy dog", 0, allocator);

if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> str(r.value());
    
    // Simple find
    size_t pos = str->find("fox");
    std::cout << "Found 'fox' at: " << pos << std::endl;  // 16
    
    // Find from middle of string
    const void* start = str->c_str() + 20;
    pos = str->find("the", start);
    std::cout << "Found 'the' at: " << pos << std::endl;  // 31
    
    // Case sensitive - won't find "Fox"
    pos = str->find("Fox");
    if (pos == SIZE_MAX) {
        std::cout << "'Fox' not found (case sensitive)" << std::endl;
    }
    
    // Find last occurrence with REVERSE
    pos = str->find("the", nullptr, nullptr, REVERSE);
    std::cout << "Last 'the' at: " << pos << std::endl;  // 31
    
    // Find in limited range
    const void* begin = str->c_str() + 10;
    const void* end = str->c_str() + 25;
    pos = str->find("brown", begin, end);
    std::cout << "In range [10,25]: " << pos << std::endl;  // 10
    
    // Not found returns SIZE_MAX
    pos = str->find("cat");
    std::cout << "Result: " << (pos == SIZE_MAX ? "Not found" : "Found") << std::endl;
}
Performance:

O(n*m) worst case, but SIMD-optimized for typical cases.

See also

find(const String&, const void*, const void*, direction_t)

Parameters:
  • needle – C-string to search for (null-terminated)

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

  • dir – Search direction (default: FORWARD)

Returns:

Offset from beginning of string where needle was found, or SIZE_MAX if not found

size_t words(const String &word, const void *begin = nullptr, const void *end = nullptr) const noexcept

Count non-overlapping occurrences of a String within this string.

Counts how many times word appears in this string using the same non-overlapping, left-to-right semantics as the C word_count() function. Each match advances the cursor past the matched region before the next search begins.

Return value:

  • 0 if this string or word is empty/null, or word is not found

  • N number of non-overlapping matches within the optional range

cslt::HeapAllocator alloc;
auto r1 = cslt::String::init("one fish two fish red fish", 0, alloc);
auto r2 = cslt::String::init("fish", 0, alloc);
if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> haystack(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> needle(r2.value());
    size_t n = haystack->words(*needle);  // 3
}
Parameters:
  • word – The String to search for (case-sensitive)

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

Returns:

Number of non-overlapping occurrences; 0 on any error or if word is empty

size_t words(const char *word, const void *begin = nullptr, const void *end = nullptr) const noexcept

Count non-overlapping occurrences of a C-string within this string.

Convenience overload accepting a string literal or C-string. Behaviour is identical to words(const String&).

cslt::HeapAllocator alloc;
auto r = cslt::String::init("one fish two fish red fish", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    size_t n = s->words("fish");  // 3
}
Parameters:
  • word – Null-terminated C-string to search for (case-sensitive)

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

Returns:

Number of non-overlapping occurrences; 0 on any error or if word is empty

size_t tokens(const String &delim, const void *begin = nullptr, const void *end = nullptr) const noexcept

Count the number of tokens in this string using a String delimiter set.

A token is a maximal run of non-delimiter characters. Adjacent delimiters collapse — they do not produce empty tokens. An empty window returns 0. If delim is empty the entire window is treated as one token.

Return values:

  • 0 if the window is empty or contains only delimiters

  • N number of tokens

  • SIZE_MAX if str_ or delim.str_ is null, or if range pointers are invalid

cslt::HeapAllocator alloc;
auto r1 = cslt::String::init("one two three", 0, alloc);
auto r2 = cslt::String::init(" ", 0, alloc);
if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> d(r2.value());
    size_t n = s->tokens(*d);  // 3
}

See also

tokens(const char*, const void*, const void*)

Parameters:
  • delimString whose characters collectively form the delimiter set. Every character in delim is treated as an independent separator (multi-character delimiters are NOT treated as a unit — this mirrors strtok semantics, not strstr semantics).

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

Returns:

Number of tokens found, or SIZE_MAX on error

size_t tokens(const char *delim, const void *begin = nullptr, const void *end = nullptr) const noexcept

Count the number of tokens in this string using a C-string delimiter set.

Convenience overload accepting a string literal or C-string. Behaviour is identical to tokens(const String&).

cslt::HeapAllocator alloc;
auto r = cslt::String::init("one two three", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    size_t n = s->tokens(" ");  // 3
}

See also

tokens(const String&, const void*, const void*)

Parameters:
  • delim – Null-terminated C-string whose characters form the delimiter set

  • begin – Optional start of search range (default: start of string)

  • end – Optional end of search range (default: end of string)

Returns:

Number of tokens found, or SIZE_MAX on error

void uppercase(const void *begin = nullptr, const void *end = nullptr) noexcept

Convert ASCII letters in this string to uppercase in-place.

Converts every ASCII lowercase letter (a–z) in the specified window to its uppercase equivalent. Non-ASCII bytes and non-letter characters are left untouched. The operation is performed in-place using SIMD-accelerated routines where available.

The method is a no-op if:

  • str_ is null

  • begin and end resolve to an empty or inverted window

  • Either pointer falls outside the allocation

Range parameters:

  • nullptr for begin/end applies the conversion to the entire string

  • Pointers must lie within the allocated buffer (validated with is_ptr())

  • begin must be <= end

cslt::HeapAllocator alloc;
auto r = cslt::String::init("hello world", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    s->uppercase();
    std::cout << s->c_str() << std::endl;  // "HELLO WORLD"

    // Convert only the first five characters
    s = cslt::String::init("hello world", 0, alloc).value();
    s->uppercase(s->c_str(), s->c_str() + 5);
    std::cout << s->c_str() << std::endl;  // "HELLO world"
}

Parameters:
  • begin – Optional start of range to convert (default: start of string)

  • end – Optional end of range to convert (default: end of string)

void lowercase(const void *begin = nullptr, const void *end = nullptr) noexcept

Convert ASCII letters in this string to lowercase in-place.

Converts every ASCII uppercase letter (A–Z) in the specified window to its lowercase equivalent. Non-ASCII bytes and non-letter characters are left untouched. The operation is performed in-place using SIMD-accelerated routines where available.

The method is a no-op if:

  • str_ is null

  • begin and end resolve to an empty or inverted window

  • Either pointer falls outside the allocation

Range parameters:

  • nullptr for begin/end applies the conversion to the entire string

  • Pointers must lie within the allocated buffer (validated with is_ptr())

  • begin must be <= end

cslt::HeapAllocator alloc;
auto r = cslt::String::init("HELLO WORLD", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    s->lowercase();
    std::cout << s->c_str() << std::endl;  // "hello world"

    // Convert only the first five characters
    s = cslt::String::init("HELLO WORLD", 0, alloc).value();
    s->lowercase(s->c_str(), s->c_str() + 5);
    std::cout << s->c_str() << std::endl;  // "hello WORLD"
}

Parameters:
  • begin – Optional start of range to convert (default: start of string)

  • end – Optional end of range to convert (default: end of string)

void drop(const char *substring, const void *begin = nullptr, const void *end = nullptr) noexcept

Remove all non-overlapping occurrences of a substring from this string in-place, optionally consuming one trailing space after each match.

Scans the window in reverse order (rightmost match first) and removes each occurrence by shifting the suffix left with memmove. After each removal the logical length (size()) is reduced accordingly and the window end is clamped to the new used region before the next search.

Trailing-space elision: if the character immediately following a match is an ASCII space, that space is consumed along with the match. This keeps word-list strings tidy when a word is dropped from the middle.

The method is a no-op if:

  • str_ or substring is null

  • substring is empty

  • The window is empty or inverted

  • Either range pointer falls outside the allocation

cslt::HeapAllocator alloc;
auto r = cslt::String::init("one fish two fish red fish", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    s->drop("fish");
    std::cout << s->c_str() << std::endl;  // "one two red"
}

Parameters:
  • substring – C-string needle to search for and remove

  • begin – Optional start of search window (default: start of string)

  • end – Optional end of search window (default: end of string)

void drop(const String &substring, const void *begin = nullptr, const void *end = nullptr) noexcept

Remove all non-overlapping occurrences of a String substring from this string in-place, optionally consuming one trailing space.

Behaviour is identical to drop(const char*). See that overload for full semantics including trailing-space elision and window clamping.

cslt::HeapAllocator alloc;
auto r1 = cslt::String::init("one fish two fish red fish", 0, alloc);
auto r2 = cslt::String::init("fish", 0, alloc);
if (r1.hasValue() && r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r1.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> needle(r2.value());
    s->drop(*needle);
    std::cout << s->c_str() << std::endl;  // "one two red"
}

Parameters:
  • substringString needle to search for and remove

  • begin – Optional start of search window (default: start of string)

  • end – Optional end of search window (default: end of string)

bool replace(const char *pattern, const char *replacement, const void *begin = nullptr, const void *end = nullptr) noexcept

Replace all non-overlapping occurrences of a pattern with a replacement string, in-place.

Counts all non-overlapping occurrences of pattern in the window first, computes the exact new length, grows the buffer in a single allocation if necessary, then performs all substitutions using reverse-order search to minimise memmove distances.

On allocation failure the string content is left unchanged and false is returned. An empty pattern is defined as a no-op (returns true). An empty window (begin >= end) is also a no-op.

cslt::HeapAllocator alloc;
auto r = cslt::String::init("one two two three", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    s->replace("two", "four");
    std::cout << s->c_str() << std::endl;  // "one four four three"
}

Parameters:
  • pattern – C-string to search for (case-sensitive)

  • replacement – C-string to substitute in place of each match

  • begin – Optional start of search window (default: start of string)

  • end – Optional end of search window (default: end of string)

Returns:

true on success or if no replacements were needed; false on invalid arguments or allocation failure

bool replace(const String &pattern, const String &replacement, const void *begin = nullptr, const void *end = nullptr) noexcept

Replace all non-overlapping occurrences of a pattern with a replacement string, in-place.

Convenience overload accepting String objects. Behaviour is identical to replace(const char*, const char*, …).

cslt::HeapAllocator alloc;
auto rs = cslt::String::init("one two two three", 0, alloc);
auto rp = cslt::String::init("two", 0, alloc);
auto rr = cslt::String::init("four", 0, alloc);
if (rs.hasValue() && rp.hasValue() && rr.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(rs.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> p(rp.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> r(rr.value());
    s->replace(*p, *r);
    std::cout << s->c_str() << std::endl;  // "one four four three"
}

Parameters:
  • patternString to search for (case-sensitive)

  • replacementString to substitute in place of each match

  • begin – Optional start of search window (default: start of string)

  • end – Optional end of search window (default: end of string)

Returns:

true on success or if no replacements were needed; false on invalid arguments or allocation failure

Expected<String*> pop(const String &token, Allocator &allocator) noexcept

Find the last occurrence of a delimiter String, return everything to its right as a new String, and truncate this string to everything to its left.

Locates the last (rightmost) occurrence of token using a reverse search. On success:

  • A new String is allocated containing the text that follows the token.

  • This string is truncated in-place to the text that precedes the token.

  • The token itself is consumed and appears in neither result.

The caller owns the returned String and must free it via StringDeleter.

cslt::HeapAllocator alloc;
auto rs = cslt::String::init("one::two::three", 0, alloc);
auto rt = cslt::String::init("::", 0, alloc);
if (rs.hasValue() && rt.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(rs.value());
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> t(rt.value());

    auto rhs = s->pop(*t, alloc);
    if (rhs.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> right(rhs.value());
        // right->c_str() == "three"
        // s->c_str()     == "one::two"
    }
}
Error conditions (returns Expected with error):

  • str_ is null

  • token.str_ is null or token is empty

  • This string is empty

  • Token is not found

  • Allocation of the result String fails

Parameters:
  • token – Delimiter to search for (case-sensitive)

  • allocatorAllocator used to create the returned String

Returns:

Expected<String*> containing the right-hand fragment, or an error if the operation cannot be performed

Expected<String*> pop(const char *token, Allocator &allocator) noexcept

Find the last occurrence of a delimiter C-string, return everything to its right as a new String, and truncate this string to everything to its left.

Convenience overload accepting a C-string token. Behaviour is identical to pop(const String&, Allocator&).

cslt::HeapAllocator alloc;
auto rs = cslt::String::init("one::two::three", 0, alloc);
if (rs.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(rs.value());

    auto rhs = s->pop("::", alloc);
    if (rhs.hasValue()) {
        cslt::UniquePtr<cslt::String, cslt::StringDeleter> right(rhs.value());
        // right->c_str() == "three"
        // s->c_str()     == "one::two"
    }
}

Parameters:
  • token – Null-terminated delimiter to search for (case-sensitive)

  • allocatorAllocator used to create the returned String

Returns:

Expected<String*> containing the right-hand fragment, or an error if the operation cannot be performed

Public Static Functions

static Expected<String*> init(const char *cstr, size_t capacity_bytes, Allocator &allocator) noexcept

Initialize an allocator-backed string.

Creates a new String instance using the provided allocator.

Capacity semantics:

  • If capacity_bytes is 0, the allocation defaults to exactly the length of cstr plus space for the null terminator

  • If capacity_bytes is non-zero, the container allocates (capacity_bytes + 1) bytes to guarantee space for the terminator

  • If the requested capacity is smaller than the source string length, the stored string is truncated to fit and always null-terminated

Memory allocation:

  • Both the String object and its internal buffer are allocated through the provided allocator

  • The allocator reference is stored for cleanup

  • Memory must later be released through the StringDeleter

cslt::HeapAllocator allocator;

// Default capacity (fits exact string)
auto r1 = cslt::String::init("hello", 0, allocator);
if (r1.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r1.value());
    std::cout << s->c_str() << std::endl;  // "hello"
    std::cout << s->size() << std::endl;    // 5
    std::cout << s->capacity() << std::endl; // 6 (includes null)
}

// Pre-allocated capacity
auto r2 = cslt::String::init("hi", 100, allocator);
if (r2.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r2.value());
    std::cout << s->size() << std::endl;     // 2
    std::cout << s->capacity() << std::endl; // 101 (100 + null)
}

// Truncation case
auto r3 = cslt::String::init("hello world", 5, allocator);
if (r3.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r3.value());
    std::cout << s->c_str() << std::endl;  // "hello"
    std::cout << s->size() << std::endl;    // 5 (truncated)
}
Error conditions:

See also

StringDeleter for cleanup semantics

Parameters:
  • cstr – Null-terminated source C string

  • capacity_bytes – Requested payload capacity in characters (excludes null terminator)

  • allocatorAllocator to use for memory management

Returns:

Expected<String*> containing pointer to String or error

Friends

friend std::ostream &operator<<(std::ostream &os, const String &s) noexcept

Stream insertion operator — prints the string up to its logical length, not the null terminator.

Writes exactly len_ characters to the stream using std::ostream::write() rather than operator<<(const char*), which would stop at the first embedded null byte. Characters beyond len_ (including the null terminator) are never accessed.

The operator is a no-op if the internal buffer is null, leaving the stream in a valid state.

cslt::HeapAllocator alloc;
auto r = cslt::String::init("hello world", 0, alloc);
if (r.hasValue()) {
    cslt::UniquePtr<cslt::String, cslt::StringDeleter> s(r.value());
    std::cout << *s << std::endl;  // prints "hello world"
}
Parameters:
  • os – Output stream to write to

  • sString to print

Returns:

Reference to os for chaining