# Structive **Structive enhances ordinary C++ structs with an explicit structural metadata and managed-property layer without replacing their native data model.** [中文文档](README.zh-CN.md) · [Design Philosophy](docs/DESIGN.md) · [Core Guide](docs/CORE_GUIDE.md) · [Extension Guide](docs/EXTENSIONS.md) ## What Structive is Structive is a C++20 property and structural-description system built around two deliberately separate layers: ```text C++ object model ordinary members, member functions, native layout and direct access │ ├── Type_Descriptor → Object_Schema │ compile-time structure, keys, attributes, constraints, access capabilities │ └── Property_Object per-instance managed access, synchronization, traversal and runtime access ``` A type can remain recognizably ordinary C++: ```cpp #include using namespace structive; struct Device : Property_Object { double temperature{25.0}; double pressure{101.3}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( defaults( external_access, persistence_access ), field<&Device::temperature>( key<"temperature">, unit<"C">, min_value<-50.0>, max_value<200.0> ), field<&Device::pressure>( key<"pressure">, unit<"kPa"> ) ); } }; ``` The members are still real members. Structive adds a second, explicit layer that generic systems can understand. ## Core idea Structive follows one central rule: > **Enhance the struct; do not replace the struct.** This has several consequences: - A registered field remains an ordinary C++ member. - Unregistered members remain completely outside the property system. - Member pointers are the preferred compile-time identity for business code. - String keys exist for runtime and adapter boundaries. - Metadata does not force storage wrappers such as `Property` into every field. - Validation, synchronization, persistence capability and presentation metadata remain separate concerns. The result is intended to let the same C++ type participate in UI, persistence, serialization, RPC or tooling layers without making the core object depend on those systems. ## Project layers ```text core/ └── structive::property_core ├── INTERFACE target ├── Type_Descriptor and Object_Schema ├── member/computed accessors ├── unified Attribute protocol ├── constraints and explicit validation ├── access capability metadata ├── synchronization plans and guards ├── typed and runtime access └── traversal extensions/ └── structive::property_extensions ├── STATIC target ├── depends on property_core ├── defines extension-owned Attribute categories └── currently provides presentation metadata interpretation ``` The dependency direction is one-way: **extensions depend on core; core never includes or links extensions.** ## Why not `Property` members? Structive intentionally does not require this: ```cpp struct Device { Property temperature; }; ``` Instead, the storage stays native: ```cpp struct Device : Property_Object { double temperature; }; ``` and the structural meaning is declared separately with `Type_Descriptor`. This preserves normal C++ member semantics, keeps raw object access available when it is intentionally needed, and lets Structive remain an enhancement layer rather than a replacement object model. ## Schema and managed object are different concepts `Type_Descriptor` describes the **type**. `Property_Object` adds state and behavior to an **instance**. The schema contains the registered property tuple, object defaults and the default synchronization plan. `Property_Object` shares one resolved default lock topology per type and keeps only instance synchronization state that is actually required: real mutex storage for locking policies and a compact override layout only when an instance explicitly supplies `Property_Synchronization`. This distinction matters for performance and architecture: structural description is type-level information; managed synchronization is instance-level state. ## Unified Attribute model There is one Attribute protocol. Core metadata and extension metadata use the same mechanism. A single-valued Attribute category can be defined as: ```cpp struct Label_Category {}; template struct Label_Attribute { using attribute_category = Label_Category; static constexpr bool single_valued = true; static constexpr bool inheritable = false; static constexpr auto value = Value; }; ``` Then it can be attached directly to a property: ```cpp field<&Device::temperature>( key<"temperature">, presentation::label<"Temperature"> ) ``` Core stores and traverses the Attribute but does not interpret categories it does not own. The owning extension interprets its own category. This is the primary extension boundary of Structive. ## Managed access is not forced encapsulation Both of these are valid but mean different things: ```cpp device.temperature = 30.0; device.write<&Device::temperature>(30.0); ``` The first is the **raw C++ path**. It bypasses Structive-managed synchronization, access capabilities and other managed behavior. The second is the **managed path**. It resolves the registered property and uses the configured synchronization behavior. Structive intentionally keeps both. A codebase should choose the appropriate path according to its ownership and concurrency rules. ## Intrinsic capability and boundary projections A property first has an **intrinsic capability** derived from its accessor: - readable if the accessor can read it; - writable if the accessor can write it. Normal managed application code uses that intrinsic capability directly: ```cpp device.write<&Device::temperature>(30.0); auto value = device.read<&Device::temperature>(); ``` Core then defines two boundary projections over the same property definition: - `external`: controlled by `External_Access` metadata; - `persistence`: controlled by `Persistence_Access` metadata. ```cpp device.external().write<&Device::temperature>(31.0); device.persistence().load<&Device::temperature>(32.0); auto stored = device.persistence().store<&Device::temperature>(); ``` There is no separate `internal` capability mode. The default managed API is the intrinsic property capability itself. `Managed_Access_Mode` therefore identifies only boundary projections. A projection can only narrow abilities that the accessor actually provides; it cannot make an intrinsically unreadable or unwritable property readable or writable. ## Validation is explicit Constraints are metadata. `write()` does **not** automatically execute them. ```cpp auto error = validate_property_value<&Device::temperature>(device.schema(), 500.0); if (error) { // error->property_key // error->code } ``` This is intentional. Field validation, cross-field invariants, transactions and rollback are different concerns and should not be hidden inside a generic setter. ## Synchronization is explicit and composable The default modes are: ```cpp sync_all_independent sync_all_shared sync_all_unsynchronized ``` Properties may then be overridden or grouped: ```cpp synchronization( sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range") ) ``` A group means those properties resolve to the same lock slot. Multi-property guards deduplicate lock slots and acquire them in stable order. ```cpp auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>(); auto old_min = guard.get<&Device::min_speed>(); guard.set<&Device::min_speed>(20.0); ``` Synchronization does not imply validation, transaction, rollback or event emission. ## Computed properties Structive supports synchronized computed properties: ```cpp computed_property([](const auto& view) { return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>(); }, key<"speed_span">, external_access) ``` A synchronized computed property reads dependencies through the synchronization view. Those dependencies must resolve to the same synchronization slot as the computed property. This makes the dependency relationship explicit in the synchronization plan. Advanced trusted accessor forms also exist for member-function getters and getter/setter pairs. They intentionally operate through trusted object access and therefore should be used only when their synchronization semantics are understood by the caller. ## Runtime access `Property_Object_Base` provides type-erased runtime access for adapter-style code: ```cpp Property_Object_Base& erased = device; auto type = erased.runtime_object_type(); auto count = erased.runtime_property_count(); ``` Runtime reads and writes use property keys, access modes and `std::type_info`, and report one of: ```text ok unknown_property not_readable not_writable type_mismatch ``` This path is intended for boundaries such as generic serialization, HTTP/RPC adapters, scripting bridges or tooling. Compile-time business code should generally prefer member pointers. ## Current core-owned metadata The current core defines: - `key<"...">` - `external_access<...>` - `persistence_access<...>` - `unit<"...">` - `sensitive<>` - `min_value<...>` - `max_value<...>` - `finite` - `constraint<"code">(...)` `external_access`, `persistence_access` and `sensitive` are inheritable and can be supplied through `defaults(...)`. Property-level declarations override object defaults for the same category. ## Current extension metadata The presentation extension currently defines: - `presentation::label<"...">` - `presentation::description<"...">` - `presentation::group<"...">` - `presentation::order` `presentation::describe()` interprets those attributes and falls back to the property key when no label is declared. That fallback belongs to the presentation extension, not to Property Core. ## Build Structive currently exposes CMake targets for use through the project tree: ```cmake add_subdirectory(path/to/Structive) target_link_libraries(my_target PRIVATE structive::property_core) ``` For linked extensions: ```cmake target_link_libraries(my_target PRIVATE structive::property_extensions) ``` Build and test the repository with: ```bash cmake -S . -B build -DBUILD_TESTING=ON cmake --build build ctest --test-dir build --output-on-failure ``` ## Documentation - [Design Philosophy and Principles](docs/DESIGN.md) - [Core Guide](docs/CORE_GUIDE.md) - [Extension Architecture](docs/EXTENSIONS.md) - [中文首页](README.zh-CN.md) - [设计理念与原则(中文)](docs/DESIGN.zh-CN.md) - [核心使用指南(中文)](docs/CORE_GUIDE.zh-CN.md) - [扩展体系(中文)](docs/EXTENSIONS.zh-CN.md)