12 KiB
Structive Design Philosophy and Principles
1. Purpose
Structive exists to give ordinary C++ structs explicit structural meaning while preserving the native C++ object model.
It is not a replacement language, reflection runtime, security boundary, ORM or object framework. It is a structural metadata and managed-property layer that remains close to normal C++.
The design target is:
ordinary C++ data
+ explicit schema
+ intrinsic property capability
+ metadata
+ optional managed synchronization
+ optional runtime adaptation
2. Primary principle: enhance, do not replace
The first rule is:
Enhance the struct; do not replace the struct.
A registered field remains a real member. Unregistered state remains normal C++. Raw access remains possible when the C++ type itself permits it.
Structive must not require every field to become a wrapper such as Property<T>, nor should it force application objects into a second object model.
Rule
If a Structive feature can be implemented as metadata or a thin managed layer without changing the native member model, prefer that design.
3. Type information and instance behavior are separate
Structive has two architectural layers.
3.1 Type layer
Type_Descriptor<T> and Object_Schema contain structural facts:
- registered properties;
- keys;
- intrinsic readable/writable capability;
- Attributes;
- Constraints;
- default synchronization description.
This information belongs to the type.
3.2 Instance layer
Property_Object<T> provides instance behavior:
- managed read/write;
- lock storage for writable synchronization domains;
- multi-property guards;
- traversal;
- type-erased runtime access;
- optional per-instance synchronization override.
Rule
Do not move type-level facts into every object instance unless the fact genuinely varies per instance.
4. Registration is explicit
Structive does not assume every member is a property.
struct Device : Property_Object<Device> {
int temperature;
int internal_cache;
};
If only temperature is registered, internal_cache does not exist in the Structive schema.
Rule
Registration defines participation. Absence from the schema means absence from Structive.
5. Intrinsic capability belongs to the property itself
Structive has no built-in access-control subsystem.
A property only describes what it intrinsically supports:
none
read
write
read_write
The capability is normally derived from the accessor. Metadata may explicitly narrow it.
field<&Device::serial_number>(key<"serial_number">, read_only)
This says:
Within the Structive managed model, this property is readable and not writable.
It does not say which user, service, GUI or process is allowed to see it.
Rule
Property capability describes structure, not authorization.
6. Core must not own external access policy
Structive deliberately does not define:
internal
external
persistence
role
context
permission
as managed access modes.
A GUI can decide which properties are editable. An RPC service can decide which fields are exposed. A persistence layer can decide which fields it saves. Those decisions belong to those systems.
Core exposes structural facts; consumers define policy.
Rule
Do not add access policy to Core merely because an adapter needs a policy. The adapter owns that policy.
7. Raw access and managed access are distinct contracts
These are intentionally different:
device.temperature = 30;
device.write<&Device::temperature>(30);
Raw access follows normal C++ rules. Managed access follows the Structive schema and synchronization model.
If code writes a read_only public member directly, it has intentionally bypassed the Structive contract.
Rule
Structive protects cooperative managed code. It does not pretend to prevent deliberate raw C++ access.
8. Read-only metadata must produce a real optimization
A stored property that cannot be written through Structive cannot race with another Structive managed writer, because no managed writer exists.
Therefore a stored intrinsic read-only property:
- receives no lock slot;
- contributes no mutex;
- ignores broad synchronization defaults;
- performs managed reads without lock lookup;
- performs managed reads without
shared_lockconstruction.
This is a structural optimization derived from schema information.
Rule
If the schema proves that synchronization state is unnecessary, do not allocate or execute it.
9. Compile-time knowledge should remove runtime work
Typed APIs know the selected property at compile time:
device.read<&Device::serial_number>();
For a stored read-only property, the compiler-visible implementation path bypasses synchronization entirely.
Similarly, typed writes to a read-only property are removed by constraints rather than accepted and rejected at runtime.
Dynamic key APIs use runtime checks because the key is not known until runtime.
Rule
Static facts should become constexpr, requires or if constexpr, not runtime branches.
10. Synchronization describes mutable consistency domains
Synchronization exists to coordinate mutable managed state.
The default rules are:
independent
shared
unsynchronized
Groups allow several mutable properties to share one lock domain.
Read-only stored properties are removed from the final resolved lock topology even if a broad rule names them.
Rule
Synchronization topology is about mutable consistency, not property visibility.
11. Computed read-only properties are a special case
A computed property may itself be read-only while depending on writable fields.
The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies.
Synchronized_Computed_Accessor therefore uses a synchronized read view. Writable dependencies must share the computed consistency domain when the computed expression needs an atomic snapshot.
Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer.
Rule
Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency.
12. One Attribute protocol
Core and extensions share one Attribute mechanism.
An Attribute owns a category and may define whether it is single-valued and inheritable.
Core must not create parallel metadata systems for UI, serialization, diagnostics or domain-specific features.
Rule
New metadata domains should extend the Attribute protocol instead of adding a second metadata framework.
13. Category ownership must be clear
The component that defines an Attribute category owns its semantics.
Presentation owns presentation categories. Core can store them, traverse them and expose them generically, but it must not interpret them.
This keeps dependency direction one-way:
Extension → Core
Core -X→ Extension
Rule
Core stores unknown extension metadata without learning extension semantics.
14. Defaults are metadata inheritance only
defaults(...) is for inheritable Attributes. It should not mutate objects or hide procedural behavior.
Property-level metadata can override an inheritable default for the same single-valued category.
Intrinsic capability is not inheritable because the capability describes each property itself.
Rule
Defaults may reduce metadata repetition, but they must not become an invisible behavior engine.
15. Validation is explicit
Constraints describe valid values. They do not automatically execute inside every write.
auto error = validate_property_value<&Device::temperature>(schema, candidate);
Field validation, cross-field invariants, transaction boundaries and rollback are separate operations.
Rule
Do not turn write() into an implicit workflow containing validation, events, transactions and rollback.
16. Runtime access is adaptation, not the primary programming model
Property_Object_Base provides key-based type-erased access for dynamic systems.
Runtime access only uses intrinsic capability. There is no runtime permission mode.
An adapter decides whether it should expose a property and whether it should invoke runtime read or write.
Rule
Use member-pointer typed access inside normal C++ business code. Use runtime access at dynamic boundaries.
17. Runtime and compile-time errors have different jobs
Compile-time typed operations should reject impossible structural operations through constraints.
Examples:
- writing an intrinsic read-only property;
- requesting a typed unique guard for a read-only property;
- reading a write-only property.
Runtime key APIs report dynamic failures through Runtime_Access_Result. The runtime write boundary is copy-input by design: intrinsic writable remains a structural fact, while runtime_copy_writable states whether the accessor can participate in that type-erased copy boundary. Move-only typed writes therefore do not get mislabeled as structurally non-writable.
Rule
Do not defer a statically knowable property error to runtime.
18. Synchronization policy remains replaceable
Shared_Mutex_Policy provides real shared/exclusive locking. No_Lock_Policy removes real mutex storage.
NoLock should not allocate fake mutex arrays or construct meaningless lock objects.
Rule
A policy that removes a capability should remove its storage and hot-path cost where possible.
19. Per-instance synchronization overrides preserve object-local policy
The default resolved topology is shared per type. An object may explicitly receive a Property_Synchronization override.
Only such an object stores an override layout. Copy/move construction preserves that layout; assignment preserves the target object's synchronization policy.
Rule
Do not make the default path pay the storage cost of a feature that only some instances use.
20. Extensions decide domain behavior
A persistence extension may choose fields based on its own metadata. A GUI may decide editability. An RPC layer may implement authorization.
Those systems may inspect Structive metadata such as readable, writable, sensitive or custom extension Attributes, but Structive does not decide their policy for them.
Rule
Core describes structure. Consumers decide behavior at their boundary.
21. Non-goals
Property Core is not intended to become:
- an authorization engine;
- an ORM;
- a JSON library;
- an RPC framework;
- a GUI binding framework;
- a transaction manager;
- an event bus;
- a scripting engine.
Such systems can consume Structive but should remain separate.
22. Evolution rules
When extending Structive, review the change against these rules:
- Does it enhance normal C++ rather than replace it?
- Is the information type-level or instance-level?
- Is this intrinsic structural capability or external policy?
- Can compile-time information remove runtime work?
- Does a read-only stored property remain outside the lock topology?
- Is synchronization limited to mutable consistency?
- Is validation still explicit?
- Does the extension own its own category semantics?
- Is runtime adaptation kept separate from typed business APIs?
- Is a new Core concept genuinely universal?
23. Architectural summary
The intended architecture is:
Native C++ struct
│
├── raw C++ access
│
└── Structive schema
│
├── intrinsic readable/writable capability
├── Attributes
├── Constraints
├── synchronization description
│ └── only mutable consistency domains create locks
└── managed object
├── typed read/write
├── guards
├── traversal
└── intrinsic runtime access
External systems
├── GUI policy
├── RPC policy
├── persistence policy
└── other domain policy
External policies consume Structive facts; they are not Structive Core access modes.
The compact statement of the design is:
Structive describes what a property is and what it intrinsically supports. It does not decide who may use it.