This repository is StormByte-String.
It is not a replacement for std::string, std::wstring or ICU. Those already do their job better inside a single binary.
The point of this library is a small, owned string that is safe to return and store across a DLL / shared-object boundary. std::string / std::wstring are not: allocator, layout and CRT can differ on each side of the link on Windows. String / WString own a CString / WCString allocated by this module. Views (string_view, iterators, data()) and copies into std::string / std::wstring are inline in the caller, so the caller’s heap is the caller’s heap.
If the text never leaves the module that created it, use std::string.
It depends on StormByte (Base) 2.0.0 or later.
- String — UTF-8 text on
CString. Contiguous const iterators, implicitstd::string_view, explicitstd::string,operator<<. - WString — wide text on
WCString. Same shape withwchar_t/std::wstring_view/std::wstring/std::wostream. - Conversion — explicit
String↔WString(UTF-8; ill-formed input becomes U+FFFD). - Ordering — content
==/!=/<=>,swap,std::hash. A default object is null;""/L""is valid empty text. Null is not equal to empty. - Algorithms —
begin/end/data/sizeso<algorithm>andstd::rangesrun on the object. - Serialization —
Serializable<String>andSerializable<WString>write the same little-endian wire asstd::string/std::wstring. The blob is aStormByte::BinaryData.
On top of that, the types carry operations that show up constantly when text crosses a module boundary: ToUpper / ToLower, SanitizeNewlines, RemoveWhitespace, IsInteger, Split and Explode. Each one exists as a static and as an instance method. That list is not a Unicode toolkit and is not frozen; later releases can add more of the same kind.
ToUpper / ToLower map ASCII and Latin-1 Supplement. Any other code point is copied as-is.
| Module | Role | API |
|---|---|---|
| Base | Foundation every other module links | /StormByte |
| Buffer | FIFO, SharedFIFO, Ring, Producer/Consumer and multi-stage pipelines | /StormByte-Buffer |
| Config | Human-readable text and versioned binary documents (groups, lists, raw bytes) | /StormByte-Config |
| Crypto | Hash, compress, encrypt, sign and key agreement — Crypto++ never leaves the private tree | /StormByte-Crypto |
| Database | One API over SQLite, PostgreSQL and MariaDB | /StormByte-Database |
| Logger | Stream logger with levels, headers, human-readable sizes and redaction (ThreadedLog) |
/StormByte-Logger |
| Multimedia | Decode, encode and containers without raw FFmpeg types; codecs enabled only if present | /StormByte-Multimedia |
| Network | Framed packets, Client/Server, IPv4/IPv6 TCP and Buffer pipelines (compress/encrypt) | /StormByte-Network |
| String | This repository | /StormByte-String |
| System | Processes, pipes and environment variables across Linux, Windows and macOS | /StormByte-System |
- What this module does
- The rest of the suite
- Installation
- Usage
- String
- WString
- Conversion
- Case
- Newlines and whitespace
- Integer
- Split
- Explode
- Algorithms
- Serialization
- Contributing
- License
Needs a C++26 compiler, CMake 3.28 or newer, and Base 2.0.0.
git clone --recurse-submodules https://github.com/StormBytePP/StormByte-String.git
cd StormByte-String
cmake -S . -B build
cmake --build buildShared vs static follows CMake BUILD_SHARED_LIBS (declared in lib/, default ON). A plain configure builds the shared library. -DBUILD_SHARED_LIBS=OFF builds a static archive; on Windows the headers then do not use dllimport. The bundled Base tree follows the same mode.
A shared build keeps this library as its own .so / .dll. Under the LGPL that is usually the simpler way to ship: the user can replace that file. A static archive is folded into your binary. The LGPL still applies to this code; you must give the recipient a way to relink your product with a different build of this library. If that does not fit how you distribute the final product, a commercial license is available from the copyright holder (see License).
Headers:
#include <StormByte/string/string.hxx>
#include <StormByte/string/wstring.hxx>
#include <StormByte/string/serializable.hxx>Namespace root for the types is StormByte::String.
A default String / WString is null (operator bool is false, data() is null). Constructed from "" / L"" it is valid and empty.
#include <StormByte/string/string.hxx>
#include <iostream>
#include <string>
using StormByte::String::String;
int main() {
String text("hello");
if (text)
std::cout << text << " " << text.size() << std::endl;
const std::string_view view = text;
const std::string copy = text;
String missing;
String empty("");
if (!missing && empty && missing != empty)
std::cout << "null is not empty" << std::endl;
}#include <StormByte/string/wstring.hxx>
#include <iostream>
using StormByte::String::WString;
int main() {
WString text(L"wide");
std::wcout << text << L" " << text.size() << std::endl;
}#include <StormByte/string/string.hxx>
#include <StormByte/string/wstring.hxx>
#include <iostream>
using StormByte::String::String;
using StormByte::String::WString;
int main() {
String utf8("café");
WString wide(utf8);
String back(wide);
WString also = static_cast<WString>(utf8);
if (back == utf8 && also == wide)
std::cout << "round-trip" << std::endl;
}#include <StormByte/string/string.hxx>
#include <iostream>
using StormByte::String::String;
int main() {
std::cout << String::ToUpper("café") << std::endl;
std::cout << String("CAFÉ").ToLower() << std::endl;
}That prints CAFÉ and café. ß, Greek, Cyrillic and anything outside Latin-1 stay unchanged.
#include <StormByte/string/string.hxx>
#include <iostream>
using StormByte::String::String;
int main() {
std::cout << String::SanitizeNewlines("a\r\nb\n") << std::endl;
std::cout << String(" a\tb\n").RemoveWhitespace() << std::endl;
}SanitizeNewlines turns CR LF into LF. Other bytes are kept. RemoveWhitespace drops what isspace / iswspace reports.
#include <StormByte/string/string.hxx>
#include <iostream>
using StormByte::String::String;
int main() {
std::cout << String::IsInteger("42") << " "
<< String("-3").IsInteger() << " "
<< String::IsInteger("1a") << std::endl;
}An optional leading + / - plus digits. Empty, "-", whitespace and letters fail.
Whitespace-separated tokens. Leading and trailing space is skipped. The output container is the caller’s.
#include <StormByte/string/string.hxx>
#include <iostream>
#include <vector>
using StormByte::String::String;
int main() {
std::vector<String> tokens;
String::Split(" a bb\tc ", tokens);
for (const String& token : tokens)
std::cout << token << std::endl;
const auto also = String("x y").Split();
}Tokens on a delimiter. Empty fields stay in the queue.
#include <StormByte/string/string.hxx>
#include <iostream>
#include <queue>
using StormByte::String::String;
int main() {
std::queue<String> parts;
String::Explode("a,,b", ',', parts);
while (!parts.empty()) {
std::cout << "[" << parts.front() << "]" << std::endl;
parts.pop();
}
}That prints [a], [], [b].
#include <StormByte/string/string.hxx>
#include <algorithm>
#include <iostream>
#include <ranges>
using StormByte::String::String;
int main() {
const String text("mississippi");
if (std::ranges::find(text, 'p') != text.end())
std::cout << std::ranges::count(text, 'i') << std::endl;
const std::string_view view = text;
if (std::ranges::equal(view, std::string_view("mississippi")))
std::cout << "view" << std::endl;
}WString is the same with wchar_t literals (L"…", L',', std::wstring_view).
String and WString plug into Base’s Serializable<T>. Include <StormByte/string/serializable.hxx>.
The wire is the same as std::string / std::wstring: little-endian uint64 byte count, then raw UTF-8. A null String / WString is written as an empty payload. Decode always yields a non-null buffer ("" / L"" when the payload is empty).
Serialize() returns a StormByte::BinaryData. That blob lives on Base’s heap, so it can cross a DLL boundary. Do not put std::vector<std::byte> in a public signature for the same job.
#include <StormByte/binary_data.hxx>
#include <StormByte/string/serializable.hxx>
#include <StormByte/string/string.hxx>
#include <StormByte/string/wstring.hxx>
#include <iostream>
using StormByte::BinaryData;
using StormByte::Serializable;
using StormByte::String::String;
using StormByte::String::WString;
int main() {
const String text("StormByte");
const BinaryData blob = Serializable<String>(text).Serialize();
auto back = Serializable<String>::Deserialize(blob);
if (back && back.value() == text)
std::cout << back.value() << std::endl;
const WString wide(L"StormByte");
const BinaryData same_wire = Serializable<WString>(wide).Serialize();
if (blob == same_wire)
std::cout << "UTF-8 wire matches" << std::endl;
}Serializable<String>("StormByte") and Serializable<std::string>("StormByte") produce the same bytes. The same holds for WString and std::wstring.
Issues and pull requests belong on this repository. Fork and open a PR against master.
Read CONTRIBUTING.md before you send a patch (copyright assignment and review rules). Coding rules are in CODING_STYLE.md.
Since 1.0.0, original source in this repository is dual-licensed: GNU Lesser General Public License v3 or later, or a commercial license from the copyright holder (David C. Manuelda StormByte@gmail.com).
The grant applies only to original StormByte-String source in this repository. It does not cover other StormByte modules or third-party material shipped here (including everything under thirdparty/, and in particular the bundled StormByte Base tree), which remains under its own license. Neither license grants patent rights.
See LICENSE for the dual-license notice and COPYING.LGPLv3 for the full GNU LGPL version 3 text. Also https://www.gnu.org/licenses/lgpl-3.0.html.
Static linking under the LGPL is described under Installation.
StormByte is developed in spare time. Sponsorship is optional and does not buy features, priority or support.