[Summary]
StormByte Base is the C++26 foundation of the StormByte suite.
Every other module links this library.
This repository is not Buffer, Config, Crypto, Database, Logger, Multimedia, Network or System — those live in their own repos and depend on Base.
Public headers under StormByte/ cover exceptions, Expected, little-endian Serializable, strings, paths, UUID v4, bitmasks, clonable types, ThreadLock, Size, and StormByte::Type concepts.
If you landed here from a release link and have not read the tree:
- What this module is, how to build it, and short examples: README.md
- Since 2.0.0 license changed: original source in this repository is dual-licensed, LGPL v3 or later or a commercial license from the copyright holder. The grant does not cover other StormByte modules or
thirdparty/. LICENSE
Added
- Safe::String / Safe::WString — UTF-8 and wide owned text integrated from StormByte-String into Base, with range, lookup, case conversion and serialization support. Views are copied when retained;
Bytes()exposes a non-owning C pointer. Explicit construction fromSafe::CString/Safe::WCStringremains available, withStringTests/WStringTests. - Telemetry and Clock — base session telemetry (
Telemetry) with pureoperator Safe::Stringand a protected named clock drawer (Clock) backed by a thread-safe PIMPL store (Safe::Unique<Store>). Leaves derive and add their own domain metrics and counters;Clockprovides a lightweight stopwatch measuring intervals, count, cumulative time, and mean duration. Covered byTelemetryTests. STORMBYTE_PUBLIC_TYPE— invisibility.h. Default visibility on ELF / Mach-O, empty on Windows. Put on header-only types (templates included) whosetypeinfoand vtable every module emits, sotypeid/dynamic_castagree across DLLs (on ELF the loader also merges the copies) even when a consumer builds with-fvisibility=hidden. Windows needs nothing: MSVC compares RTTI by name and implicitly exports the base specializations of an exported class.STORMBYTE_FORCE_INLINE— inplatform.h.inlineplus__forceinlineoralways_inline, so the body is emitted in the caller.inlinealone is only a hint.- BinaryData — owned contiguous sequence of
std::byte, safe to use across a DLL boundary. Same kind of API asstd::vector<std::byte>(iterators,<algorithm>,std::ranges,std::span, insert / erase / assign /append/emplace_back/operator+=). Lengths and indices useStormByte::ByteSize. Storage lives on Base's heap; the public type is notstd::vector<std::byte>.at()throwsOutOfBoundsError. Construct fromspan, pointer+ByteSize, range, initializer list,string_view, and a caller-ownedstd::vector<std::byte>(lvalue copies and leaves the vector; rvalue copies onto Base's heap then clears the vector — looks like a move, not a heap steal). Convert out with implicitspanandexplicit operator std::vector<std::byte>(const&copies and leaves*this;&&copies onto the caller CRT then clears*this).append(BinaryData&&)/operator+=(BinaryData&&)are a real same-heap move when*thisis empty. Compare equal / unequal / three-way with anotherBinaryDataand withstd::span<const std::byte>(both operand orders).HexDump()andHexDump(Size columns)return aSafe::String: 8-digit offset, hex row, ASCII (non-printable as.).columnsis a row width, not a byte length.0prints every byte on one line. DefaultHexDump()is 16 columns.Type::Container/Type::Sized/Type::HasPushBack/Type::ByteInputRangematch;Type::Stringdoes not.Serializable<BinaryData>uses the container path (same wire asstd::vector<std::byte>:uint64LE count + payload). Covered byBinaryDataTestsandSerializationTests. - CString — owned NUL-terminated buffer, safe to use across a DLL boundary. Not a
std::string. Copy, move,Reset,swap,Length(StormByte::Size), observeroperator[](indices[0, Length()];Length()is the trailing NUL; null or pastLength()is undefined andasserts when assertions are on), explicitoperator bool(non-null pointer;""is valid empty text), explicitconst char*(same lifetime asstd::string::c_str()), explicitstd::string_view(empty view when null;[0, Length()); same lifetime), implicitstd::string,operator<<, content==/!=/<=>, andstd::hash. Construct fromconst char*(null stays null),std::string_viewandconst std::string&(copy onto Base's heap, not a steal; empty yields"", not null). Covered byCStringTests. - WCString — wide counterpart of
CString(wchar_t/std::wstring/std::wstring_view/std::wostream). Same comparison, hash, subscript and lifetime rules asstd::wstring::c_str().Length()isStormByte::Size. Construct fromconst wchar_t*(null stays null),std::wstring_viewandconst std::wstring&(copy onto Base's heap; empty yieldsL""). Covered byWCStringTests. - Error —
Domain,Category,Code(Success,Unknown) andFault. Modules specializeDomainfor their enums;make_error_codelives next to the enum so ADL feedsstd::error_code. Category singletons stay in the module.cxx.Faultholds the code and aSafe::String. Not thrown. Covered byErrorTests. - Shared —
StormByte::Safe::Shared, complementsstd::shared_ptr(StormByte/safe/pointers.hxx). It does not replace it. Exact type:Safe::Heap::MakeShared. Derived type:Shared<Base>::MakePointer<Derived>(the deleter still destroys the derived object). Daily operations matchstd::shared_ptr(copy,reset,swap, compare,use_count,owner_before). No constructor from a raw pointer or fromstd::shared_ptr, and norelease. Converts implicitly tostd::shared_ptr<T>(same control block, deleter stays Base). No conversion back.StaticPointerCast,DynamicPointerCast,ConstPointerCastandReinterpretPointerCastkeep that control block. Covered bySafePointersTests. - Unique —
StormByte::Safe::Unique, complementsstd::unique_ptr(StormByte/safe/pointers.hxx). It does not replace it. Exact type:Safe::Heap::MakeUnique. Derived type:Unique<Base>::MakePointer<Derived>, and~Basemust be virtual or the call does not compile. Noreleaseand no constructor from a raw pointer. Converts on move tostd::unique_ptr<T, Safe::Heap::ObjectDeleter>only. Astd::unique_ptr<T>parameter has to change.Safe::Heapstays private and is not installed. Covered bySafePointersTests. - Weak —
StormByte::Safe::Weak, complementsstd::weak_ptr. Constructed only fromShared.lockreturnsShared, or empty when expired. No constructor fromstd::weak_ptr. Covered bySafePointersTests. - ClonableTests — clone / move /
MakePointercoverage forSharedandUnique.Derived::PointerTypeused as storage. Implicit conversion tostd::shared_ptr/unique_ptrwith Base deleter.ValidSmartPointerrejectsstd::shared_ptrand defaultstd::unique_ptr. A hidden-visibility shared library (ClonablePlugin) derives fromClonable; the tests check one sharedtypeid,dynamic_castandClone/Moveacross that boundary. - Size — abstract unit count (
uint64_tstorage), same width on every host and safe across a DLL. Not an octet length. Implicit conversion only tostd::size_t(clamp tosize_t::max); every other integral destination isexplicitand clamps toT::max. NoValue(), nooperator bool. Full arithmetic with anyType::Integraland with anotherSize; the result is alwaysSize. Mixed==/<=>with any integral and withByteSizeare hidden friends son == 5is not ambiguous. A negative integer operand is undefined andasserts when assertions are on. Overflow wraps in release.operator Safe::String/operator Safe::WStringprint the raw count (atoistyle). Explicit instantiations of the templates live in the StormByte DLL. Covered bySizeTests. - ByteSize — octet length (
uint64_tstorage), same width on every host and safe across a DLL. Implicit conversion only tostd::size_t(clamp); every other integral destination isexplicit. NoValue(), nooperator bool. Area products (ByteSize * ByteSize) are deleted. Scaling bySizeor by an integer is allowed and yieldsByteSize. IEC factories and free constants (B,KiB,MiB,GiB,TiB,PiB,EiB) so1 * KiBandByteSize::KiB(1)are 1024 octets. SI (KB…EB) likewise.operator Safe::String/operator Safe::WStringprint IEC text (0 B,1023 B,1.00 KiB,1.50 MiB). Explicit instantiations live in the StormByte DLL. Covered byByteSizeTests.
Changed
- Text API migration —
CString/WCStringmove toStormByte/safe/and remain public for explicit buffer use. Base text-producing APIs (Size,ByteSize, UUID, Base64,BinaryData,Telemetry) now exposeSafe::String/Safe::WString. Error messages and exceptions storeSafe::String; exception plain-text constructors acceptstd::string_vieworconst Safe::String&and copy the text. Both buffer and text-wrapper serialization retain the standard string wire format. - Shared vs static follows CMake
BUILD_SHARED_LIBS. There is noSTORMBYTE_SHAREDCMake option. When the library is shared, the compile definitionSTORMBYTE_SHAREDis still set sovisibility.hcan distinguishdllexport/dllimport/ static. - License change — original source in this repository is dual-licensed: GNU LGPL v3 or later, or a commercial license from the copyright holder. The grant applies only to original StormByte source in this repository. It does not cover other StormByte modules or third-party material (including
thirdparty/). No patent rights are granted. - Visibility —
STORMBYTE_PUBLICcomes first on a function declaration (STORMBYTE_PUBLIC Safe::CString Foo();). clang-cl rejects__declspecafter a reference return.class STORMBYTE_PUBLICstays on the type. Exported templates are declaredextern template STORMBYTE_PUBLIC/extern template class STORMBYTE_PUBLICin the header and instantiated in the.cxxwithSTORMBYTE_INSTANTIATE(dllexporton Windows, empty on ELF so GCC does not warn-Wattributes). Theexternline in the header is what ELF uses to export; do not putSTORMBYTE_INSTANTIATEon that line. - Breaking: Exception.
StormByte::Componentis gone.what()isStormByte: <message>, orStormByte.<path>: <message>when a parent passes the segments underStormByte(Crypto.Crypter), joined with.. A parent passesException::Path(astd::string_viewof its segments) and forwards the format and the arguments. A bare string is not a path, because that is ambiguous with the format constructor. It does not format.Exceptioncallsstd::formatin the caller's translation unit and copies aconst char*into aSafe::String. The view is not stored and nostd::stringenters the DLL. A final leaf inherits the parent constructors and adds no segment. A literal with no arguments is the message as-is.DeserializeError,OutOfBoundsErrorandBase64Errorare leaves of the root (StormByte: …). Each of those types, and the root, defines its destructor in Base's.cxxso thetypeinfois unique across a DLL. Named types in other modules do the same. - Expected — the error is a
Safe::Shared<E>, not astd::shared_ptrbuilt withstd::make_shared.UnexpectedusesSafe::Heap::MakeShared, orSafe::Shared::MakePointerfor a derived error. The call staysUnexpected<E>("… {}", args…). Formatting runs in the caller;Eis constructed from the resulting string.Unexpected(result.error())forwards thatSharedand does not allocate.Shared<E>converts tostd::shared_ptr<E>. Astd::shared_ptris not accepted.Expected<T, E>is stillstd::expected. Covered byExpectedTests. - Breaking:
Clonable— moves toStormByte::Safe::Clonable(StormByte/safe/clonable.hxx;StormByte/clonable.hxxis gone). PolymorphicClone/Move. It is not an owner:MakePointerforwards toShared::MakePointerorUnique::MakePointer, which allocate the object and theshared_ptrcontrol block on Base's heap (Safe::Heap::Allocate/Safe::Heap::Freein a private TU that is not installed).Clonable<T>isClonable<T, Shared<T>>. Unique ownership isClonable<T, Unique<T>>.Clonable<T, std::shared_ptr<T>>andClonable<T, std::unique_ptr<T>>no longer compile.PointerTypeisShared<T>/Unique<T>; overrides that already returnPointerTypefromMakePointerdo not change. After construction,Sharedconverts implicitly tostd::shared_ptr<T>so existingshared_ptrcall sites keep working.Uniquedoes not convert tostd::unique_ptr<T>.ValidSmartPointerusesType::SameAs. A class in another DLL may derive fromClonable:Clonable,Shared,Unique,Weak,Heap::ObjectDeleterandHeap::AllocatorcarrySTORMBYTE_PUBLIC_TYPE, so their per-moduletypeinfoand vtables merge instead of staying hidden in each DLL (before,Clonable<T>was hidden in a-fvisibility=hiddenconsumer, and address-comparing runtimes such as libc++ disagreed ontypeid/dynamic_cast). Covered byClonableTests. - Breaking:
StormByte::Safe—Shared,Unique,Weak, the pointer casts,HeapandClonable/ValidSmartPointerlive inStormByte::Safe, underStormByte/safe/(pointers.hxx,clonable.hxx).StormByte/safe_pointers.hxxandStormByte/clonable.hxxare gone. No aliases are kept inStormByte. - Type::String — also matches
StormByte::Safe::StringandStormByte::Safe::WString. They stay out ofType::Container. - Type::Sized —
size()may be implicitly convertible tostd::size_t(STL containers) or beStormByte::Size/StormByte::ByteSize. Covered byTypeTraitsTests. - Type::Numeral — also matches
StormByte::SizeandStormByte::ByteSize. - Type::Array — StormByte flavor of a fixed-size sequence;
Serializableuses it instead of stockstd::is_same/tuple_sizeprobes.BinaryDatais not an array. - GenerateUUIDv4 — returns
Safe::Stringinstead ofstd::string. - Base64Encode — both overloads return
Safe::Stringinstead ofstd::string. Base64Decode returnsBinaryDataand still takesstd::string_view. - Size vs ByteSize everywhere — public Base APIs no longer take or return a raw
std::size_t/std::uint64_twhen the value is a count. Character counts (Safe::CString::Length,Safe::WCString::Length, subscripts) areSize. Octet counts (BinaryData::size/operator[]/ ctors /Serializable<T>::Size, wire lengths) areByteSize. Mixed arithmetic and comparison stay typed: aSizeresult stays aSize; aByteSizeresult stays aByteSize. - Serializable — container and string codecs use
ByteSizefor the on-wire length. Concepts used to branch (Type::Array,Type::Container,Type::String,Type::Numeral) are StormByte flavor, not stockstd::*traits. - Tests — suite section headers (
// -------------------) match in the body and inmain. UUID, Base64, Bitmask, Clonable, Exception, Expected, ThreadLock, TestHandlers, Iterable, Serialization, SafePointers, TypeTraits, Size, ByteSize and BinaryData cover the public surface (format, padding, invalid input, clone independence, non-owner unlock, bounds, wire, corruption, units,*///%,<algorithm>/ ranges / iterators,HexDumpcolumns, span comparison,operator+=, IEC text).Type::Sizedcovers a container whosesize()returnsStormByte::SizeorStormByte::ByteSize.Base64Decodeof an encode result uses an explicitstd::string_view.SerializationTestscoverBinaryDataroundtrip, the same wire asstd::vector<std::byte>, truncation, a huge size field, bit-flip / byte-overwrite / random corruption, trailing garbage andstd::optional<BinaryData>.
Removed
- Old root String helpers (
StormByte::Stringin the former Base API) — replaced by the ownedStormByte::Safe::String/WStringtypes in Base. - System (
StormByte::Systemin Base:TempFileName,CurrentPath,ExecutablePath,Sleep) — leaves Base. Absorbed by the existing StormByte-System module. - UTF8Error / SystemError — leave Base with String and System. Derived exceptions that remain are
DeserializeError,OutOfBoundsErrorandBase64Error. - CoreApiTests — coverage lives in
ClonableTestsandErrorTests.
Fixed
FindStormByte.Stringis no longer a package component; Base now provides the owned text types.BufferpullsLoggerandSystem;DatabasepullsLogger.Crypto,MultimediaandNetworkname onlyBuffer; the closure findsLoggerandSystem.Configlinks only the core.ByteSize/Size. The integer constructors stay in the header. GCC does not emit aconstexprconstructor that is both anextern templateand an explicit instantiation, soSizeTestscrashed andByteSize(unsigned long long)was missing from the shared library. The operators are still one copy in the DLL.++/--build the step with the private constructor, so they do not instantiateunsigned intearly.Size/ByteSizetostd::string. The conversion callsoperator Safe::String()by name. On GCC,static_cast<Safe::String>picksSafe::String(std::string)and that calls the same operator again until the stack dies.Safe::CString/Safe::WCString. Each constructs from the other. Narrow text is UTF-8.ByteSize's wide form uses that conversion.swprintfand%sis a narrow string on glibc and a wide string on the Windows CRT, so1.00 KiBdid not match.- Caller containers.
Safe::CString::operator std::string,Safe::WCString::operator std::wstring,Size/ByteSizeoperator std::string, theiroperator<<, andBinaryData::operator std::vectorareSTORMBYTE_FORCE_INLINE. On an exported class,inlinecan still be a call into the DLL, so the container was allocated here and freed by the caller. The vector operators were out of line; they now copy throughspan()in the caller, thenclear()on Base's heap.