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 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 it conforms to the Allocator base class.
Recommended Allocators
Different allocators are appropriate depending on how strings are created, resized, and destroyed:
HeapAllocator
Best for:
general-purpose string manipulation
variable-length strings with frequent resizing
host-side applications and utilities
debugging string behavior independent of allocator complexity
Strings often reallocate and copy memory during growth. A heap allocator provides predictable behavior and is the easiest to reason about during development and testing.
ArenaAllocator
Best for:
short-lived string collections (e.g., parsing, tokenization)
append-only or immutable string usage
batch construction followed by bulk discard
This is highly efficient when strings are not individually freed. However, repeated resizing may lead to unused memory if older buffers cannot be reclaimed.
BuddyAllocator
Best for:
dynamically sized strings with controlled fragmentation
systems requiring deterministic allocation patterns
workloads with frequent allocation and release
Since strings often grow geometrically, a buddy allocator can help reduce fragmentation compared to a general heap in long-running systems.
PoolAllocator / SlabAllocator
Best for:
fixed-capacity strings
preallocated buffers
embedded systems with strict memory layouts
These allocators are less suitable for dynamically growing strings, but can be effective when string sizes are bounded or known in advance.
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:
- 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
- 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:
str’s internal buffer is null (corrupted String)
Any condition that would cause concat(const char*) to fail
- 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
- Parameters:
str – String 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:
- 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:
- 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:
other – String 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
- 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" } }
- 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 } }
- 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:
needle – String 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:
delim – String 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" }See also
- 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" }See also
- 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:
substring – 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)
- 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
patternin 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:
- 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
tokenusing 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
See also
- 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" } }See also
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:
ArgumentError if cstr is nullptr
Propagates any allocation errors from the allocator (typically MemoryError)
See also
StringDeleter for cleanup semantics
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
s – String to print
- Returns:
Reference to
osfor chaining