CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches

Core Concept

The stype system provides runtime type information through pointers to canonical STypeInfo descriptors. This enables generic programming patterns similar to C++ templates while maintaining C compatibility and avoiding code bloat from template instantiation.

Each type has exactly one canonical const STypeInfo in static storage. An stype value is simply a pointer to that descriptor - equality is pointer equality (via stEq, which canonicalizes before comparing). Type names like int32, string, and opaque(MyStruct) expand to pointers to their canonical descriptors through the stType() macro.

Type Descriptor

stype is a const STypeInfo*. The STypeInfo struct carries all information about a type in one place:

typedef struct STypeInfo {
uint32 id; // hierarchical type class | subtype | discriminant
uint16 size; // storage size in bytes
uint16 flags; // STypeFlag_Object, STypeFlag_PassPtr,
// STypeFlag_Temporary
STypeOps ops; // embedded operations: dtor, cmp, hash, copy, convert
STypeOps ops
operations embedded directly - no separate allocation
Definition stype.h:1414
uint16 size
storage size in bytes
Definition stype.h:1411
uint16 flags
STypeFlag_Object, STypeFlag_PassPtr, STypeFlag_Temporary.
Definition stype.h:1412
uint32 id
hierarchical type ID: STCLASS | STST_subtype | discriminant
Definition stype.h:1410

Type ID Structure

The id field uses a hierarchical bit-field:

Use st->id to inspect the type class. Compare against stTypeId(name) constants (e.g., stTypeId(int32), stTypeId(sarray)) for type-specific dispatch.

Supported Types

Primitive Types:

CX Framework Types:

Object Classes (cxautogen):

Object classes generated by the cxautogen tool can be used as type names directly in container operations. MyClass is a valid type name for saInit, saPush, htInsert, and similar APIs. The named class type is an alias for object, so all class instances share the same underlying descriptor and containers remain polymorphic: a container initialized with MyBase will accept MyDerived instances, since the type identity is the same throughout the object hierarchy.

When a named class is used at an insertion site, the argument is checked against the expected class at compile time (and in debug builds at runtime). Compare:

// Generic object — any object instance accepted:
sa_object arr;
saInit(&arr, object, 16);
saPush(&arr, object, inst);
// Named class — instance checked against MyClass:
sa_object arr;
saInit(&arr, MyClass, 16);
saPush(&arr, MyClass, inst);
#define saInit(out, type, capacity,...)
Definition sarray.h:315
#define saPush(handle, type, elem,...)
Definition sarray.h:460

The storage type for a named class (SType_MyClass) is MyClass*, so accessors such as hteVal return the correct pointer type without an explicit cast.

Canonical Descriptors and the Type Registry

All built-in type descriptors are static const STypeInfo with STypeFlag_Temporary clear. For dynamic types - plain opaque(T) with no custom ops, or parameterized container types like an sarray of a specific element - the descriptor is constructed as a compound literal with STypeFlag_Temporary set. When such a descriptor is passed to a container init function, stCanonical() is called:

After the first use, all subsequent calls with equivalent parameters resolve to the same pointer, so type identity is pointer equality everywhere.

For custom types with user-defined ops, the correct pattern is a global or file-static canonical descriptor - see Custom Type Integration.

Usage Patterns

Generic function parameters use macros that expand to type-value pairs:

htInsert(&ht, string, _SL("key"), int32, 42);
// Expands to stType(string)/stArg(string,...) + stType(int32)/stArg(int32,...)
#define htInsert(htbl, ktype, key, vtype, val,...)
Definition hashtable.h:443
#define _SL(s)
Inline ASCII string literal with compile-time embedded length (STR_LEN8). Content must be < 200 bytes...
Definition strliteral.h:207

Stored values in containers use stStored() to load from raw memory:

stgeneric val = stStored(hdr->elemtype, ELEMPTR(hdr, i));
#define stStored(st, storage)
Definition stype.h:1559

Type identity checks use stEq() or direct pointer comparison on known-canonical types:

if (stEq(saElemType(arr), stType(int32))) { ... }
if (v.type == stType(string)) { ... } // safe when v.type came from stCanonical
#define saElemType(ref)
Definition sarray.h:274
bool stEq(stype s1, stype s2)
Definition stype.h:1785
#define stType(name)
Definition stype.h:972

Plain opaque (POD blob, no custom ops):

sa_MyPOD arr;
saInit(&arr, opaque(MyPOD), 16); // Temporary; type registry provides canonical pointer
saPush(&arr, opaque, val); // opaque push: size checked against container's elem type

Type Operations

STypeInfo embeds an STypeOps struct with seven optional function pointers:

Type Safety

Compile-time checking is enforced through macro expansion:

Runtime checking occurs when:

Variant Types (stvar)

The stvar structure combines a value with its type descriptor for runtime polymorphism. Access the descriptor through stvarType() (or check it with stvarIs()) rather than reading the _type field directly - its low bit is an ownership tag, so the raw field is not a usable stype pointer.

stvar v = stvar(int32, 42);
if (stvarIs(&v, int32)) {
int32 val = v.data.st_int32;
}
#define stvarIs(svar, type)
Definition stvar.h:523
#define stvar(typen, val)
Definition stvar.h:162

This is the foundation for type-safe variadic arguments and heterogeneous collections throughout the framework.