🟩 Primitive types, asserts, compiler macros, Function, Span, Result
SaneCppFoundation.h is the small, allocation-free vocabulary shared by the other Sane C++ Libraries. It provides primitive and platform types, non-owning views, explicit error propagation, fixed-storage callbacks, assertions, compiler portability macros, and a few ownership tools for native resources.
Foundation is useful on its own when those constraints are desirable, but it is primarily the seam that lets the rest of SC expose public C++ APIs without the STL, exceptions, RTTI, system headers, or hidden heap allocation. Detailed API reference remains available in the Foundation topic; this page explains the choices a caller needs to make.
Dependencies
- Dependencies: (none)
- All dependencies: (none)
The mental model
Foundation separates access from ownership.
Span<T>is a pointer and an element count.StringSpanadds an explicit ASCII, UTF-8, UTF-16, or native encoding and records whether a terminator follows the viewed bytes. Neither type owns or extends the lifetime of its data.StringPathis the deliberate exception: it owns one fixed-capacity, native-encoded path buffer.Resultis a[[nodiscard]]success/failure value.SC_TRYforwards a failure without exceptions. It deliberately carries only a stable ASCII error-message pointer, not an arbitrary error payload.Function<Signature>stores a free function, member binding, functor, or lambda inline. Its default callable storage is2 * sizeof(void*); an oversized capture is a compile-time error rather than a heap allocation.UniqueHandleowns one native-style handle and releases it deterministically.Deferredruns local cleanup at scope exit.OpaqueObjectreserves platform-sized inline storage so a public header can hide an implementation defined in a.cppfile without allocating a PIMPL.
The common thread is fixed, visible cost. A caller supplies the memory behind a view, chooses the lifetime of a callback capture, and sees failure in the return type. Foundation does not turn borrowed data into owned data behind the API.
Representative use
Function is representative of the library's approach: several callable forms share one value type, but the storage budget stays part of the type and is checked at compile time.
// A regular class with a member function
struct SomeClass
{
float memberValue = 2.0;
int memberFunc(float a) { return static_cast<int>(a + memberValue); }
};
// A Functor with operator ()
struct SomeFunctor
{
float memberValue = 2.0;
int operator()(float a) { return static_cast<int>(a + memberValue); }
};
// Free function
int someFunc(float a) { return static_cast<int>(a * 2); }
// Class too big to be grabbed by copy
struct BigClass
{
SC::uint64_t values[4];
};
void SC::FunctionTest::functionDocumentationSnippet()
{
SomeClass someClass;
Function<int(float)> func;
func = &someFunc; // Bind free func
func.bind<SomeClass, &SomeClass::memberFunc>(someClass); // Bind member func
func = [](float a) -> int { return static_cast<int>(a + 1.5); }; // Bind lambda func
func = SomeFunctor{2.5}; // Bind a functor
// If you feel brave enough you can retrieve the bound functor by knowing its type
SC_ASSERT_RELEASE(func.dynamicCastTo<SomeFunctor>()->memberValue == 2.5f);
// This will static_assert because sizeof(BigClass) is bigger than LAMBDA_SIZE
// BigClass bigClass;
// func = [bigClass](float a) -> int { return static_cast<int>(a);};
}For buffer-oriented APIs, pass an array or (pointer, count) as a Span<T>. Slicing reports invalid ranges instead of creating an unchecked view. A mutable span permits mutation of the referenced objects; a Span<const T> does not. In both cases, the backing array must outlive every use of the span.
StringSpan is intentionally narrower than a general string library. It is suitable for passing encoded text and native paths through low-level APIs, and can compare ASCII, UTF-8, and UTF-16 code points. It does not own, normalize, format, or tokenize the text. Its conversion support is limited to writing native-encoded, null-terminated text into caller-provided storage; it is not a general-purpose Unicode conversion API.
Allocation, errors, and lifetime
Foundation itself performs no dynamic allocation. That promise has practical consequences:
- A
SpanorStringSpancan dangle. Returning one is safe only when its backing storage remains alive.StringPathinstead owns its buffer, but rejects content beyond its platform-specificMaxPathcapacity. - A
Resultcreated withResult::Error("literal")is self-contained enough for normal propagation because the literal has static storage.Result::FromStableCharPointerinstead makes the caller responsible for keeping the message pointer valid until it is inspected. - A
Functionowns its inline functor or lambda, but references captured by that callable still follow normal C++ lifetime rules. Increase the second template argument for a deliberately larger inline budget, or capture a pointer to longer-lived state; there is no allocating fallback. UniqueHandleis move-only.detach()stops the wrapper from releasing the handle; because it does not return the raw value, the caller must already retain that value. Its destructor cannot report a release failure, so code that needs that result should callclose()explicitly.OpaqueObjecttrades heap-free encapsulation for a fixed ABI budget. Each platform size and alignment must cover the private object; growing that private object beyond the declared budget requires changing the public definition.
These are low-level tools, not runtime safety mechanisms: bounds are carried by Span, but operator[] is not a checked accessor, and borrowed lifetime remains a design obligation.
Where Foundation stops
Foundation has no library dependencies and is intentionally smaller than its neighboring SC libraries:
- Use Memory when data must grow dynamically or be owned through an explicit allocator (
Buffer,String, orSmallString). Depending on Foundation alone is a useful signal that a library cannot allocate through SC Memory; Foundation's own ownership types use fixed inline storage or native handles instead. - Use Containers for owning typed collections such as
Vector; continue to exposeSpanat boundaries when ownership does not need to cross them. - Use Strings for
StringView, iteration, conversion, formatting, parsing, and path manipulation.StringSpanexists so lower-level libraries can carry encoded text without taking that larger dependency. - Platform libraries build their public interfaces from
Result,Span,Function,UniqueHandle, andOpaqueObject, keeping operating-system types and headers in implementation files.
Foundation is therefore a good fit for small, portable API boundaries with caller-controlled storage. It is not a replacement for owning containers, rich diagnostics, general Unicode processing, or dynamically sized type erasure.
Portability layer
Foundation also supplies SC primitive aliases, compiler/feature detection, export and warning macros, assertions, placement construction, type traits, and small move/forward utilities. These are mostly infrastructure for SC headers, not an attempt to reproduce the full standard library. See Compiler Macros and the Foundation reference when integrating at that level.
Maturity
🟩 Usable. Foundation is exercised across the supported compilers and platforms because almost every SC library builds on it. Its surface grows when another library needs a dependency-free primitive; it is not intended to accumulate convenience APIs speculatively.
Blog
Some relevant blog posts are:
Roadmap
🟦 Complete Features:
- Things will be added as needed
Statistics
LOC counts exclude comments. Library counts files physically under Libraries/Foundation. Single File counts SaneCppFoundation.h. Standalone counts SaneCppFoundationStandalone.h and intentionally includes dependency payloads.
| Metric | Header | Source | Sum |
|---|---|---|---|
| Library | 27 | 64 | 91 |
| Single File | 1597 | 216 | 1813 |
| Standalone | 1597 | 216 | 1813 |