|
CX Framework
Cross-platform C utility framework
|
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.
stype is a const STypeInfo*. The STypeInfo struct carries all information about a type in one place:
The id field uses a hierarchical bit-field:
STCLASS_BASIC, STCLASS_CX, STCLASS_DYNAMIC, STCLASS_USERSTST_INT, STST_UINT, STST_FLOAT, STST_PTR; for CX: STST_OPAQUE, STST_MISC, STST_OBJ, STST_CONTAINERsizeof(type) for primitives, sequential for CX typesUse st->id to inspect the type class. Compare against stTypeId(name) constants (e.g., stTypeId(int32), stTypeId(sarray)) for type-specific dispatch.
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:
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.
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:
Temporary is clear, the descriptor is already canonical - returned as-is.Temporary is set, _stGetCanonical() looks up or inserts the descriptor in the global type registry (keyed by id + size), returns the canonical heap-allocated copy with Temporary cleared.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.
Generic function parameters use macros that expand to type-value pairs:
Stored values in containers use stStored() to load from raw memory:
Type identity checks use stEq() or direct pointer comparison on known-canonical types:
Plain opaque (POD blob, no custom ops):
STypeInfo embeds an STypeOps struct with seven optional function pointers:
Compile-time checking is enforced through macro expansion:
Runtime checking occurs when:
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.
This is the foundation for type-safe variadic arguments and heterogeneous collections throughout the framework.